diff options
211 files changed, 10386 insertions, 1894 deletions
diff --git a/Android.mk b/Android.mk index 335fb732b214..7b8d0bc92f11 100644 --- a/Android.mk +++ b/Android.mk @@ -158,6 +158,11 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/os/IResultReceiver.aidl \ core/java/com/android/internal/statusbar/IStatusBar.aidl \ core/java/com/android/internal/statusbar/IStatusBarService.aidl \ + core/java/com/android/internal/textservice/ISpellCheckerService.aidl \ + core/java/com/android/internal/textservice/ISpellCheckerSession.aidl \ + core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl \ + core/java/com/android/internal/textservice/ITextServicesManager.aidl \ + core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl \ core/java/com/android/internal/view/IInputContext.aidl \ core/java/com/android/internal/view/IInputContextCallback.aidl \ core/java/com/android/internal/view/IInputMethod.aidl \ @@ -190,6 +195,7 @@ LOCAL_SRC_FILES += \ telephony/java/com/android/internal/telephony/ISms.aidl \ telephony/java/com/android/internal/telephony/IWapPushManager.aidl \ wifi/java/android/net/wifi/IWifiManager.aidl \ + wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \ telephony/java/com/android/internal/telephony/IExtendedNetworkService.aidl \ voip/java/android/net/sip/ISipSession.aidl \ voip/java/android/net/sip/ISipSessionListener.aidl \ @@ -266,6 +272,11 @@ aidl_files := \ frameworks/base/core/java/android/view/Surface.aidl \ frameworks/base/core/java/android/view/WindowManager.aidl \ frameworks/base/core/java/android/widget/RemoteViews.aidl \ + frameworks/base/core/java/com/android/internal/textservice/ISpellCheckerService.aidl \ + frameworks/base/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl \ + frameworks/base/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl \ + frameworks/base/core/java/com/android/internal/textservice/ITextServicesManager.aidl \ + frameworks/base/core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl \ frameworks/base/core/java/com/android/internal/view/IInputContext.aidl \ frameworks/base/core/java/com/android/internal/view/IInputMethod.aidl \ frameworks/base/core/java/com/android/internal/view/IInputMethodCallback.aidl \ diff --git a/api/current.txt b/api/current.txt index eb3bb5823135..a71c86662acd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21,6 +21,7 @@ package android { field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN"; field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD"; field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS"; + field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE"; field public static final java.lang.String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER"; field public static final java.lang.String BLUETOOTH = "android.permission.BLUETOOTH"; field public static final java.lang.String BLUETOOTH_ADMIN = "android.permission.BLUETOOTH_ADMIN"; @@ -184,14 +185,14 @@ package android { public static final class R.attr { ctor public R.attr(); field public static final int absListViewStyle = 16842858; // 0x101006a - field public static final int accessibilityEventTypes = 16843648; // 0x1010380 - field public static final int accessibilityFeedbackType = 16843650; // 0x1010382 - field public static final int accessibilityFlags = 16843652; // 0x1010384 + field public static final int accessibilityEventTypes = 16843647; // 0x101037f + field public static final int accessibilityFeedbackType = 16843649; // 0x1010381 + field public static final int accessibilityFlags = 16843651; // 0x1010383 field public static final int accountPreferences = 16843423; // 0x101029f field public static final int accountType = 16843407; // 0x101028f field public static final int action = 16842797; // 0x101002d field public static final int actionBarSize = 16843499; // 0x10102eb - field public static final int actionBarSplitStyle = 16843670; // 0x1010396 + field public static final int actionBarSplitStyle = 16843669; // 0x1010395 field public static final int actionBarStyle = 16843470; // 0x10102ce field public static final int actionBarTabBarStyle = 16843508; // 0x10102f4 field public static final int actionBarTabStyle = 16843507; // 0x10102f3 @@ -207,10 +208,10 @@ package android { field public static final int actionModeCopyDrawable = 16843538; // 0x1010312 field public static final int actionModeCutDrawable = 16843537; // 0x1010311 field public static final int actionModePasteDrawable = 16843539; // 0x1010313 - field public static final int actionModeSelectAllDrawable = 16843646; // 0x101037e - field public static final int actionModeStyle = 16843682; // 0x10103a2 + field public static final int actionModeSelectAllDrawable = 16843645; // 0x101037d + field public static final int actionModeStyle = 16843681; // 0x10103a1 field public static final int actionOverflowButtonStyle = 16843510; // 0x10102f6 - field public static final int actionProviderClass = 16843671; // 0x1010397 + field public static final int actionProviderClass = 16843670; // 0x1010396 field public static final int actionViewClass = 16843516; // 0x10102fc field public static final int activatedBackgroundIndicator = 16843517; // 0x10102fd field public static final int activityCloseEnterAnimation = 16842938; // 0x10100ba @@ -222,7 +223,7 @@ package android { field public static final int alertDialogIcon = 16843605; // 0x1010355 field public static final int alertDialogStyle = 16842845; // 0x101005d field public static final int alertDialogTheme = 16843529; // 0x1010309 - field public static final int alignmentMode = 16843640; // 0x1010378 + field public static final int alignmentMode = 16843639; // 0x1010377 field public static final int allContactsName = 16843468; // 0x10102cc field public static final int allowBackup = 16843392; // 0x1010280 field public static final int allowClearUserData = 16842757; // 0x1010005 @@ -256,8 +257,8 @@ package android { field public static final int background = 16842964; // 0x10100d4 field public static final int backgroundDimAmount = 16842802; // 0x1010032 field public static final int backgroundDimEnabled = 16843295; // 0x101021f - field public static final int backgroundSplit = 16843673; // 0x1010399 - field public static final int backgroundStacked = 16843672; // 0x1010398 + field public static final int backgroundSplit = 16843672; // 0x1010398 + field public static final int backgroundStacked = 16843671; // 0x1010397 field public static final int backupAgent = 16843391; // 0x101027f field public static final int baseline = 16843548; // 0x101031c field public static final int baselineAlignBottom = 16843042; // 0x1010122 @@ -266,7 +267,7 @@ package android { field public static final int borderlessButtonStyle = 16843563; // 0x101032b field public static final int bottom = 16843184; // 0x10101b0 field public static final int bottomBright = 16842957; // 0x10100cd - field public static final int bottomChevronDrawable = 16843659; // 0x101038b + field public static final int bottomChevronDrawable = 16843658; // 0x101038a field public static final int bottomDark = 16842953; // 0x10100c9 field public static final int bottomLeftRadius = 16843179; // 0x10101ab field public static final int bottomMedium = 16842958; // 0x10100ce @@ -285,7 +286,7 @@ package android { field public static final int cacheColorHint = 16843009; // 0x1010101 field public static final int calendarViewShown = 16843596; // 0x101034c field public static final int calendarViewStyle = 16843613; // 0x101035d - field public static final int canRetrieveWindowContent = 16843653; // 0x1010385 + field public static final int canRetrieveWindowContent = 16843652; // 0x1010384 field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230 field public static final deprecated int capitalize = 16843113; // 0x1010169 field public static final int centerBright = 16842956; // 0x10100cc @@ -314,18 +315,18 @@ package android { field public static final int codes = 16843330; // 0x1010242 field public static final int collapseColumns = 16843083; // 0x101014b field public static final int color = 16843173; // 0x10101a5 - field public static final int colorActivatedHighlight = 16843678; // 0x101039e + field public static final int colorActivatedHighlight = 16843677; // 0x101039d field public static final int colorBackground = 16842801; // 0x1010031 field public static final int colorBackgroundCacheHint = 16843435; // 0x10102ab - field public static final int colorFocusedHighlight = 16843677; // 0x101039d + field public static final int colorFocusedHighlight = 16843676; // 0x101039c field public static final int colorForeground = 16842800; // 0x1010030 field public static final int colorForegroundInverse = 16843270; // 0x1010206 - field public static final int colorLongPressedHighlight = 16843676; // 0x101039c - field public static final int colorMultiSelectHighlight = 16843679; // 0x101039f - field public static final int colorPressedHighlight = 16843675; // 0x101039b - field public static final int columnCount = 16843637; // 0x1010375 + field public static final int colorLongPressedHighlight = 16843675; // 0x101039b + field public static final int colorMultiSelectHighlight = 16843678; // 0x101039e + field public static final int colorPressedHighlight = 16843674; // 0x101039a + field public static final int columnCount = 16843636; // 0x1010374 field public static final int columnDelay = 16843215; // 0x10101cf - field public static final int columnOrderPreserved = 16843638; // 0x1010376 + field public static final int columnOrderPreserved = 16843637; // 0x1010375 field public static final int columnWidth = 16843031; // 0x1010117 field public static final int compatibleWidthLimitDp = 16843621; // 0x1010365 field public static final int completionHint = 16843122; // 0x1010172 @@ -379,11 +380,11 @@ package android { field public static final int drawSelectorOnTop = 16843004; // 0x10100fc field public static final int drawable = 16843161; // 0x1010199 field public static final int drawableBottom = 16843118; // 0x101016e - field public static final int drawableEnd = 16843681; // 0x10103a1 + field public static final int drawableEnd = 16843680; // 0x10103a0 field public static final int drawableLeft = 16843119; // 0x101016f field public static final int drawablePadding = 16843121; // 0x1010171 field public static final int drawableRight = 16843120; // 0x1010170 - field public static final int drawableStart = 16843680; // 0x10103a0 + field public static final int drawableStart = 16843679; // 0x101039f field public static final int drawableTop = 16843117; // 0x101016d field public static final int drawingCacheQuality = 16842984; // 0x10100e8 field public static final int dropDownAnchor = 16843363; // 0x1010263 @@ -440,7 +441,7 @@ package android { field public static final int fastScrollTextColor = 16843609; // 0x1010359 field public static final int fastScrollThumbDrawable = 16843574; // 0x1010336 field public static final int fastScrollTrackDrawable = 16843577; // 0x1010339 - field public static final int feedbackCount = 16843665; // 0x1010391 + field public static final int feedbackCount = 16843664; // 0x1010390 field public static final int fillAfter = 16843197; // 0x10101bd field public static final int fillBefore = 16843196; // 0x10101bc field public static final int fillEnabled = 16843343; // 0x101024f @@ -493,7 +494,7 @@ package android { field public static final int hand_hour = 16843011; // 0x1010103 field public static final int hand_minute = 16843012; // 0x1010104 field public static final int handle = 16843354; // 0x101025a - field public static final int handleDrawable = 16843655; // 0x1010387 + field public static final int handleDrawable = 16843654; // 0x1010386 field public static final int handleProfiling = 16842786; // 0x1010022 field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e field public static final int hardwareAccelerated = 16843475; // 0x10102d3 @@ -502,12 +503,12 @@ package android { field public static final int headerDividersEnabled = 16843310; // 0x101022e field public static final int height = 16843093; // 0x1010155 field public static final int hint = 16843088; // 0x1010150 - field public static final int hitRadius = 16843662; // 0x101038e + field public static final int hitRadius = 16843661; // 0x101038d field public static final int homeAsUpIndicator = 16843531; // 0x101030b field public static final int homeLayout = 16843549; // 0x101031d field public static final int horizontalDivider = 16843053; // 0x101012d field public static final int horizontalGap = 16843327; // 0x101023f - field public static final int horizontalOffset = 16843667; // 0x1010393 + field public static final int horizontalOffset = 16843666; // 0x1010392 field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353 field public static final int horizontalSpacing = 16843028; // 0x1010114 field public static final int host = 16842792; // 0x1010028 @@ -553,7 +554,7 @@ package android { field public static final int installLocation = 16843447; // 0x10102b7 field public static final int interpolator = 16843073; // 0x1010141 field public static final int isAlwaysSyncable = 16843571; // 0x1010333 - field public static final int isAuxiliary = 16843647; // 0x101037f + field public static final int isAuxiliary = 16843646; // 0x101037e field public static final int isDefault = 16843297; // 0x1010221 field public static final int isIndicator = 16843079; // 0x1010147 field public static final int isModifier = 16843334; // 0x1010246 @@ -606,8 +607,8 @@ package android { field public static final int layout_centerInParent = 16843151; // 0x101018f field public static final int layout_centerVertical = 16843153; // 0x1010191 field public static final int layout_column = 16843084; // 0x101014c - field public static final int layout_columnFlexibility = 16843645; // 0x101037d - field public static final int layout_columnSpan = 16843644; // 0x101037c + field public static final int layout_columnFlexibility = 16843644; // 0x101037c + field public static final int layout_columnSpan = 16843643; // 0x101037b field public static final int layout_gravity = 16842931; // 0x10100b3 field public static final int layout_height = 16842997; // 0x10100f5 field public static final int layout_margin = 16842998; // 0x10100f6 @@ -615,9 +616,9 @@ package android { field public static final int layout_marginLeft = 16842999; // 0x10100f7 field public static final int layout_marginRight = 16843001; // 0x10100f9 field public static final int layout_marginTop = 16843000; // 0x10100f8 - field public static final int layout_row = 16843641; // 0x1010379 - field public static final int layout_rowFlexibility = 16843643; // 0x101037b - field public static final int layout_rowSpan = 16843642; // 0x101037a + field public static final int layout_row = 16843640; // 0x1010378 + field public static final int layout_rowFlexibility = 16843642; // 0x101037a + field public static final int layout_rowSpan = 16843641; // 0x1010379 field public static final int layout_scale = 16843155; // 0x1010193 field public static final int layout_span = 16843085; // 0x101014d field public static final int layout_toLeftOf = 16843138; // 0x1010182 @@ -627,7 +628,7 @@ package android { field public static final int layout_x = 16843135; // 0x101017f field public static final int layout_y = 16843136; // 0x1010180 field public static final int left = 16843181; // 0x10101ad - field public static final int leftChevronDrawable = 16843656; // 0x1010388 + field public static final int leftChevronDrawable = 16843655; // 0x1010387 field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 field public static final int lines = 16843092; // 0x1010154 @@ -639,8 +640,8 @@ package android { field public static final int listDividerAlertDialog = 16843525; // 0x1010305 field public static final int listPopupWindowStyle = 16843519; // 0x10102ff field public static final int listPreferredItemHeight = 16842829; // 0x101004d - field public static final int listPreferredItemHeightLarge = 16843668; // 0x1010394 - field public static final int listPreferredItemHeightSmall = 16843669; // 0x1010395 + field public static final int listPreferredItemHeightLarge = 16843667; // 0x1010393 + field public static final int listPreferredItemHeightSmall = 16843668; // 0x1010394 field public static final int listSelector = 16843003; // 0x10100fb field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208 field public static final int listViewStyle = 16842868; // 0x1010074 @@ -671,8 +672,8 @@ package android { field public static final int minHeight = 16843072; // 0x1010140 field public static final int minLevel = 16843185; // 0x10101b1 field public static final int minLines = 16843094; // 0x1010156 - field public static final int minResizeHeight = 16843684; // 0x10103a4 - field public static final int minResizeWidth = 16843683; // 0x10103a3 + field public static final int minResizeHeight = 16843683; // 0x10103a3 + field public static final int minResizeWidth = 16843682; // 0x10103a2 field public static final int minSdkVersion = 16843276; // 0x101020c field public static final int minWidth = 16843071; // 0x101013f field public static final int mode = 16843134; // 0x101017e @@ -688,7 +689,7 @@ package android { field public static final int nextFocusUp = 16842979; // 0x10100e3 field public static final int noHistory = 16843309; // 0x101022d field public static final int normalScreens = 16843397; // 0x1010285 - field public static final int notificationTimeout = 16843651; // 0x1010383 + field public static final int notificationTimeout = 16843650; // 0x1010382 field public static final int numColumns = 16843032; // 0x1010118 field public static final int numStars = 16843076; // 0x1010144 field public static final deprecated int numeric = 16843109; // 0x1010165 @@ -702,11 +703,11 @@ package android { field public static final int orderingFromXml = 16843239; // 0x10101e7 field public static final int orientation = 16842948; // 0x10100c4 field public static final int outAnimation = 16843128; // 0x1010178 - field public static final int outerRadius = 16843661; // 0x101038d + field public static final int outerRadius = 16843660; // 0x101038c field public static final int overScrollFooter = 16843459; // 0x10102c3 field public static final int overScrollHeader = 16843458; // 0x10102c2 field public static final int overScrollMode = 16843457; // 0x10102c1 - field public static final int packageNames = 16843649; // 0x1010381 + field public static final int packageNames = 16843648; // 0x1010380 field public static final int padding = 16842965; // 0x10100d5 field public static final int paddingBottom = 16842969; // 0x10100d9 field public static final int paddingLeft = 16842966; // 0x10100d6 @@ -791,17 +792,17 @@ package android { field public static final int restoreAnyVersion = 16843450; // 0x10102ba field public static final deprecated int restoreNeedsApplication = 16843421; // 0x101029d field public static final int right = 16843183; // 0x10101af - field public static final int rightChevronDrawable = 16843657; // 0x1010389 + field public static final int rightChevronDrawable = 16843656; // 0x1010388 field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093 field public static final int ringtoneType = 16843257; // 0x10101f9 field public static final int rotation = 16843558; // 0x1010326 field public static final int rotationX = 16843559; // 0x1010327 field public static final int rotationY = 16843560; // 0x1010328 - field public static final int rowCount = 16843635; // 0x1010373 + field public static final int rowCount = 16843634; // 0x1010372 field public static final int rowDelay = 16843216; // 0x10101d0 field public static final int rowEdgeFlags = 16843329; // 0x1010241 field public static final int rowHeight = 16843058; // 0x1010132 - field public static final int rowOrderPreserved = 16843636; // 0x1010374 + field public static final int rowOrderPreserved = 16843635; // 0x1010373 field public static final int saveEnabled = 16842983; // 0x10100e7 field public static final int scaleGravity = 16843262; // 0x10101fe field public static final int scaleHeight = 16843261; // 0x10101fd @@ -867,7 +868,7 @@ package android { field public static final int smallIcon = 16843422; // 0x101029e field public static final int smallScreens = 16843396; // 0x1010284 field public static final int smoothScrollbar = 16843313; // 0x1010231 - field public static final int snapMargin = 16843664; // 0x1010390 + field public static final int snapMargin = 16843663; // 0x101038f field public static final int soundEffectsEnabled = 16843285; // 0x1010215 field public static final int spacing = 16843027; // 0x1010113 field public static final int spinnerDropDownItemStyle = 16842887; // 0x1010087 @@ -915,7 +916,7 @@ package android { field public static final int subtitleTextStyle = 16843513; // 0x10102f9 field public static final int suggestActionMsg = 16843228; // 0x10101dc field public static final int suggestActionMsgColumn = 16843229; // 0x10101dd - field public static final int suggestionsEnabled = 16843634; // 0x1010372 + field public static final int suggestionsEnabled = 16843633; // 0x1010371 field public static final int summary = 16843241; // 0x10101e9 field public static final int summaryColumn = 16843426; // 0x10102a2 field public static final int summaryOff = 16843248; // 0x10101f0 @@ -932,7 +933,7 @@ package android { field public static final int tag = 16842961; // 0x10100d1 field public static final int targetActivity = 16843266; // 0x1010202 field public static final int targetClass = 16842799; // 0x101002f - field public static final int targetDrawables = 16843654; // 0x1010386 + field public static final int targetDrawables = 16843653; // 0x1010385 field public static final int targetPackage = 16842785; // 0x1010021 field public static final int targetSdkVersion = 16843376; // 0x1010270 field public static final int taskAffinity = 16842770; // 0x1010012 @@ -947,7 +948,7 @@ package android { field public static final int tension = 16843370; // 0x101026a field public static final int testOnly = 16843378; // 0x1010272 field public static final int text = 16843087; // 0x101014f - field public static final int textAllCaps = 16843674; // 0x101039a + field public static final int textAllCaps = 16843673; // 0x1010399 field public static final int textAppearance = 16842804; // 0x1010034 field public static final int textAppearanceButton = 16843271; // 0x1010207 field public static final int textAppearanceInverse = 16842805; // 0x1010035 @@ -988,9 +989,8 @@ package android { field public static final int textEditPasteWindowLayout = 16843540; // 0x1010314 field public static final int textEditSideNoPasteWindowLayout = 16843615; // 0x101035f field public static final int textEditSidePasteWindowLayout = 16843614; // 0x101035e - field public static final int textEditSuggestionItemLayout = 16843633; // 0x1010371 - field public static final int textEditSuggestionsBottomWindowLayout = 16843631; // 0x101036f - field public static final int textEditSuggestionsTopWindowLayout = 16843632; // 0x1010370 + field public static final int textEditSuggestionItemLayout = 16843632; // 0x1010370 + field public static final int textEditSuggestionsWindowLayout = 16843631; // 0x101036f field public static final int textFilterEnabled = 16843007; // 0x10100ff field public static final int textIsSelectable = 16843542; // 0x1010316 field public static final int textOff = 16843045; // 0x1010125 @@ -1023,7 +1023,7 @@ package android { field public static final int toYScale = 16843205; // 0x10101c5 field public static final int top = 16843182; // 0x10101ae field public static final int topBright = 16842955; // 0x10100cb - field public static final int topChevronDrawable = 16843658; // 0x101038a + field public static final int topChevronDrawable = 16843657; // 0x1010389 field public static final int topDark = 16842951; // 0x10100c7 field public static final int topLeftRadius = 16843177; // 0x10101a9 field public static final int topOffset = 16843352; // 0x1010258 @@ -1039,7 +1039,7 @@ package android { field public static final int unfocusedMonthDateColor = 16843588; // 0x1010344 field public static final int unselectedAlpha = 16843278; // 0x101020e field public static final int updatePeriodMillis = 16843344; // 0x1010250 - field public static final int useDefaultMargins = 16843639; // 0x1010377 + field public static final int useDefaultMargins = 16843638; // 0x1010376 field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310 field public static final int useLevel = 16843167; // 0x101019f field public static final int userVisible = 16843409; // 0x1010291 @@ -1053,10 +1053,10 @@ package android { field public static final int verticalCorrection = 16843322; // 0x101023a field public static final int verticalDivider = 16843054; // 0x101012e field public static final int verticalGap = 16843328; // 0x1010240 - field public static final int verticalOffset = 16843666; // 0x1010392 + field public static final int verticalOffset = 16843665; // 0x1010391 field public static final int verticalScrollbarPosition = 16843572; // 0x1010334 field public static final int verticalSpacing = 16843029; // 0x1010115 - field public static final int vibrationDuration = 16843663; // 0x101038f + field public static final int vibrationDuration = 16843662; // 0x101038e field public static final int visibility = 16842972; // 0x10100dc field public static final int visible = 16843156; // 0x1010194 field public static final int vmSafeMode = 16843448; // 0x10102b8 @@ -1073,7 +1073,7 @@ package android { field public static final int wallpaperIntraOpenExitAnimation = 16843416; // 0x1010298 field public static final int wallpaperOpenEnterAnimation = 16843411; // 0x1010293 field public static final int wallpaperOpenExitAnimation = 16843412; // 0x1010294 - field public static final int waveDrawable = 16843660; // 0x101038c + field public static final int waveDrawable = 16843659; // 0x101038b field public static final int webTextViewStyle = 16843449; // 0x10102b9 field public static final int webViewStyle = 16842885; // 0x1010085 field public static final int weekDayTextAppearance = 16843592; // 0x1010348 @@ -4785,6 +4785,7 @@ package android.content { field public static final java.lang.String SENSOR_SERVICE = "sensor"; field public static final java.lang.String STORAGE_SERVICE = "storage"; field public static final java.lang.String TELEPHONY_SERVICE = "phone"; + field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices"; field public static final java.lang.String UI_MODE_SERVICE = "uimode"; field public static final java.lang.String USB_SERVICE = "usb"; field public static final java.lang.String VIBRATOR_SERVICE = "vibrator"; @@ -10406,8 +10407,8 @@ package android.media { method public void setAudioEncodingBitRate(int); method public void setAudioSamplingRate(int); method public void setAudioSource(int) throws java.lang.IllegalStateException; - method public void setAuxiliaryOutputFile(java.io.FileDescriptor); - method public void setAuxiliaryOutputFile(java.lang.String); + method public deprecated void setAuxiliaryOutputFile(java.io.FileDescriptor); + method public deprecated void setAuxiliaryOutputFile(java.lang.String); method public void setCamera(android.hardware.Camera); method public void setCaptureRate(double); method public void setLocation(float, float); @@ -17943,6 +17944,31 @@ package android.security { } +package android.service.textservice { + + public abstract class SpellCheckerService extends android.app.Service { + ctor public SpellCheckerService(); + method public void cancel(); + method public abstract android.view.textservice.SuggestionsInfo getSuggestions(android.view.textservice.TextInfo, int, java.lang.String); + method public android.view.textservice.SuggestionsInfo[] getSuggestionsMultiple(android.view.textservice.TextInfo[], java.lang.String, int, boolean); + method public final android.os.IBinder onBind(android.content.Intent); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.textservice.SpellCheckerService"; + } + + public class SpellCheckerSession { + method public void close(); + method public android.view.textservice.SpellCheckerInfo getSpellChecker(); + method public void getSuggestions(android.view.textservice.TextInfo, int); + method public void getSuggestions(android.view.textservice.TextInfo[], int, boolean); + method public boolean isSessionDisconnected(); + } + + public static abstract interface SpellCheckerSession.SpellCheckerSessionListener { + method public abstract void onGetSuggestions(android.view.textservice.SuggestionsInfo[]); + } + +} + package android.service.wallpaper { public abstract class WallpaperService extends android.app.Service { @@ -21006,6 +21032,11 @@ package android.view { method public void onPrepareSubMenu(android.view.SubMenu); } + public abstract interface CollapsibleActionView { + method public abstract void onActionViewCollapsed(); + method public abstract void onActionViewExpanded(); + } + public abstract interface ContextMenu implements android.view.Menu { method public abstract void clearHeader(); method public abstract android.view.ContextMenu setHeaderIcon(int); @@ -22202,6 +22233,7 @@ package android.view { method public final android.view.View findViewWithTag(java.lang.Object); method public void findViewsWithText(java.util.ArrayList<android.view.View>, java.lang.CharSequence); method protected boolean fitSystemWindows(android.graphics.Rect); + method public boolean fitsSystemWindows(); method public android.view.View focusSearch(int); method public void forceLayout(); method public float getAlpha(); @@ -22443,6 +22475,7 @@ package android.view { method public void setEnabled(boolean); method public void setFadingEdgeLength(int); method public void setFilterTouchesWhenObscured(boolean); + method public void setFitsSystemWindows(boolean); method public void setFocusable(boolean); method public void setFocusableInTouchMode(boolean); method public void setHapticFeedbackEnabled(boolean); @@ -22716,10 +22749,8 @@ package android.view { ctor public ViewDebug(); method public static void dumpCapturedView(java.lang.String, java.lang.Object); method public static void startHierarchyTracing(java.lang.String, android.view.View); - method public static void startLooperProfiling(java.io.File); method public static void startRecyclerTracing(java.lang.String, android.view.View); method public static void stopHierarchyTracing(); - method public static void stopLooperProfiling(); method public static void stopRecyclerTracing(); method public static void trace(android.view.View, android.view.ViewDebug.RecyclerTraceType, int...); method public static void trace(android.view.View, android.view.ViewDebug.HierarchyTraceType); @@ -24020,6 +24051,54 @@ package android.view.inputmethod { } +package android.view.textservice { + + public final class SpellCheckerInfo implements android.os.Parcelable { + method public int describeContents(); + method public android.content.ComponentName getComponent(); + method public java.lang.String getId(); + method public java.lang.String getPackageName(); + method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager); + method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final class SuggestionsInfo implements android.os.Parcelable { + ctor public SuggestionsInfo(int, java.lang.String[]); + ctor public SuggestionsInfo(int, java.lang.String[], int, int); + ctor public SuggestionsInfo(android.os.Parcel); + method public int describeContents(); + method public int getCookie(); + method public int getSequence(); + method public java.lang.String getSuggestionAt(int); + method public int getSuggestionsAttributes(); + method public int getSuggestionsCount(); + method public void setCookieAndSequence(int, int); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int RESULT_ATTR_IN_THE_DICTIONARY = 1; // 0x1 + field public static final int RESULT_ATTR_LOOKS_TYPO = 2; // 0x2 + } + + public final class TextInfo implements android.os.Parcelable { + ctor public TextInfo(java.lang.String); + ctor public TextInfo(java.lang.String, int, int); + ctor public TextInfo(android.os.Parcel); + method public int describeContents(); + method public int getCookie(); + method public int getSequence(); + method public java.lang.String getText(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final class TextServicesManager { + method public android.service.textservice.SpellCheckerSession newSpellCheckerSession(java.util.Locale, android.service.textservice.SpellCheckerSession.SpellCheckerSessionListener, boolean); + } + +} + package android.webkit { public final deprecated class CacheManager { @@ -26122,7 +26201,7 @@ package android.widget { method public int timePassed(); } - public class SearchView extends android.widget.LinearLayout { + public class SearchView extends android.widget.LinearLayout implements android.view.CollapsibleActionView { ctor public SearchView(android.content.Context); ctor public SearchView(android.content.Context, android.util.AttributeSet); method public java.lang.CharSequence getQuery(); @@ -26131,6 +26210,8 @@ package android.widget { method public boolean isIconified(); method public boolean isQueryRefinementEnabled(); method public boolean isSubmitButtonEnabled(); + method public void onActionViewCollapsed(); + method public void onActionViewExpanded(); method public void setIconified(boolean); method public void setIconifiedByDefault(boolean); method public void setMaxWidth(int); diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 3fb17362f4c1..6dfa12be9592 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -468,10 +468,16 @@ public class Am { String profileFile = null; boolean start = false; boolean wall = false; + int profileType = 0; String process = null; String cmd = nextArgRequired(); + if ("looper".equals(cmd)) { + cmd = nextArgRequired(); + profileType = 1; + } + if ("start".equals(cmd)) { start = true; wall = "--wall".equals(nextOption()); @@ -516,7 +522,7 @@ public class Am { } else if (start) { //removeWallOption(); } - if (!mAm.profileControl(process, start, profileFile, fd)) { + if (!mAm.profileControl(process, start, profileFile, fd, profileType)) { wall = false; throw new AndroidException("PROFILE FAILED on process " + process); } @@ -1076,8 +1082,8 @@ public class Am { " am broadcast <INTENT>\n" + " am instrument [-r] [-e <NAME> <VALUE>] [-p] [-w]\n" + " [--no-window-animation] <COMPONENT>\n" + - " am profile start <PROCESS> <FILE>\n" + - " am profile stop <PROCESS>\n" + + " am profile [looper] start <PROCESS> <FILE>\n" + + " am profile [looper] stop <PROCESS>\n" + " am dumpheap [flags] <PROCESS> <FILE>\n" + " am monitor [--gdb <port>]\n" + " am screen-compat [on|off] <PACKAGE>\n" + diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index b42f1c5a10bd..34f0a64f0b72 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -803,9 +803,9 @@ int main(int argc, char **argv) { printf("type '%s':\n", kMimeTypes[k]); Vector<CodecCapabilities> results; + // will retrieve hardware and software codecs CHECK_EQ(QueryCodecs(omx, kMimeTypes[k], true, // queryDecoders - false, // hwCodecOnly &results), (status_t)OK); for (size_t i = 0; i < results.size(); ++i) { diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 2a731a3999fc..b7cd829563c9 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1104,10 +1104,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); String process = data.readString(); boolean start = data.readInt() != 0; + int profileType = data.readInt(); String path = data.readString(); ParcelFileDescriptor fd = data.readInt() != 0 ? data.readFileDescriptor() : null; - boolean res = profileControl(process, start, path, fd); + boolean res = profileControl(process, start, path, fd, profileType); reply.writeNoException(); reply.writeInt(res ? 1 : 0); return true; @@ -2888,13 +2889,14 @@ class ActivityManagerProxy implements IActivityManager } public boolean profileControl(String process, boolean start, - String path, ParcelFileDescriptor fd) throws RemoteException + String path, ParcelFileDescriptor fd, int profileType) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(process); data.writeInt(start ? 1 : 0); + data.writeInt(profileType); data.writeString(path); if (fd != null) { data.writeInt(1); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index f6cd866d4e34..9bbbd6c76753 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -676,11 +676,12 @@ public final class ActivityThread { queueOrSendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token); } - public void profilerControl(boolean start, String path, ParcelFileDescriptor fd) { + public void profilerControl(boolean start, String path, ParcelFileDescriptor fd, + int profileType) { ProfilerControlData pcd = new ProfilerControlData(); pcd.path = path; pcd.fd = fd; - queueOrSendMessage(H.PROFILER_CONTROL, pcd, start ? 1 : 0); + queueOrSendMessage(H.PROFILER_CONTROL, pcd, start ? 1 : 0, profileType); } public void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd) { @@ -954,7 +955,7 @@ public final class ActivityThread { } public void scheduleTrimMemory(int level) { - queueOrSendMessage(H.TRIM_MEMORY, level); + queueOrSendMessage(H.TRIM_MEMORY, null, level); } } @@ -1148,7 +1149,7 @@ public final class ActivityThread { handleActivityConfigurationChanged((IBinder)msg.obj); break; case PROFILER_CONTROL: - handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj); + handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj, msg.arg2); break; case CREATE_BACKUP_AGENT: handleCreateBackupAgent((CreateBackupAgentData)msg.obj); @@ -1184,8 +1185,10 @@ public final class ActivityThread { break; case UPDATE_PACKAGE_COMPATIBILITY_INFO: handleUpdatePackageCompatibilityInfo((UpdateCompatibilityData)msg.obj); + break; case TRIM_MEMORY: handleTrimMemory(msg.arg1); + break; } if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what); } @@ -3469,11 +3472,18 @@ public final class ActivityThread { performConfigurationChanged(r.activity, mCompatConfiguration); } - final void handleProfilerControl(boolean start, ProfilerControlData pcd) { + final void handleProfilerControl(boolean start, ProfilerControlData pcd, int profileType) { if (start) { try { - Debug.startMethodTracing(pcd.path, pcd.fd.getFileDescriptor(), - 8 * 1024 * 1024, 0); + switch (profileType) { + case 1: + ViewDebug.startLooperProfiling(pcd.path, pcd.fd.getFileDescriptor()); + break; + default: + Debug.startMethodTracing(pcd.path, pcd.fd.getFileDescriptor(), + 8 * 1024 * 1024, 0); + break; + } } catch (RuntimeException e) { Slog.w(TAG, "Profiling failed on path " + pcd.path + " -- can the process access this path?"); @@ -3485,7 +3495,15 @@ public final class ActivityThread { } } } else { - Debug.stopMethodTracing(); + switch (profileType) { + case 1: + ViewDebug.stopLooperProfiling(); + break; + default: + Debug.stopMethodTracing(); + break; + + } } } diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index 942f245bebf8..9a5b527d7c5b 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -376,10 +376,11 @@ public abstract class ApplicationThreadNative extends Binder { data.enforceInterface(IApplicationThread.descriptor); boolean start = data.readInt() != 0; + int profileType = data.readInt(); String path = data.readString(); ParcelFileDescriptor fd = data.readInt() != 0 ? data.readFileDescriptor() : null; - profilerControl(start, path, fd); + profilerControl(start, path, fd, profileType); return true; } @@ -936,10 +937,11 @@ class ApplicationThreadProxy implements IApplicationThread { } public void profilerControl(boolean start, String path, - ParcelFileDescriptor fd) throws RemoteException { + ParcelFileDescriptor fd, int profileType) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeInt(start ? 1 : 0); + data.writeInt(profileType); data.writeString(path); if (fd != null) { data.writeInt(1); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index d2323e7defec..a99cec28bdf6 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -59,6 +59,8 @@ import android.net.IThrottleManager; import android.net.Uri; import android.net.wifi.IWifiManager; import android.net.wifi.WifiManager; +import android.net.wifi.p2p.IWifiP2pManager; +import android.net.wifi.p2p.WifiP2pManager; import android.nfc.NfcManager; import android.os.Binder; import android.os.Bundle; @@ -83,6 +85,7 @@ import android.view.ContextThemeWrapper; import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputMethodManager; +import android.view.textservice.TextServicesManager; import android.accounts.AccountManager; import android.accounts.IAccountManager; import android.app.admin.DevicePolicyManager; @@ -320,6 +323,11 @@ class ContextImpl extends Context { return InputMethodManager.getInstance(ctx); }}); + registerService(TEXT_SERVICES_MANAGER_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return TextServicesManager.getInstance(); + }}); + registerService(KEYGUARD_SERVICE, new ServiceFetcher() { public Object getService(ContextImpl ctx) { // TODO: why isn't this caching it? It wasn't @@ -432,6 +440,13 @@ class ContextImpl extends Context { return new WifiManager(service, ctx.mMainThread.getHandler()); }}); + registerService(WIFI_P2P_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(WIFI_P2P_SERVICE); + IWifiP2pManager service = IWifiP2pManager.Stub.asInterface(b); + return new WifiP2pManager(service); + }}); + registerService(WINDOW_SERVICE, new ServiceFetcher() { public Object getService(ContextImpl ctx) { return WindowManagerImpl.getDefault(ctx.mPackageInfo.mCompatibilityInfo); diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index c82c9ecf7a8f..789d3a692775 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -1695,6 +1695,7 @@ final class FragmentManagerImpl extends FragmentManager { public void dispatchDestroy() { mDestroyed = true; + execPendingActions(); moveToState(Fragment.INITIALIZING, false); mActivity = null; } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 93c821c04469..64d77e80750c 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -284,7 +284,7 @@ public interface IActivityManager extends IInterface { // Turn on/off profiling in a particular process. public boolean profileControl(String process, boolean start, - String path, ParcelFileDescriptor fd) throws RemoteException; + String path, ParcelFileDescriptor fd, int profileType) throws RemoteException; public boolean shutdown(int timeout) throws RemoteException; diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 9de0bf4ffa0e..d0607d09040d 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -105,7 +105,7 @@ public interface IApplicationThread extends IInterface { throws RemoteException; void scheduleLowMemory() throws RemoteException; void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException; - void profilerControl(boolean start, String path, ParcelFileDescriptor fd) + void profilerControl(boolean start, String path, ParcelFileDescriptor fd, int profileType) throws RemoteException; void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd) throws RemoteException; diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 019652c024d9..1ef99a1e6845 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -388,6 +388,10 @@ public class AppWidgetManager { TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics); info.minHeight = TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics); + info.minResizeWidth = + TypedValue.complexToDimensionPixelSize(info.minResizeWidth, mDisplayMetrics); + info.minResizeHeight = + TypedValue.complexToDimensionPixelSize(info.minResizeHeight, mDisplayMetrics); } return providers; } @@ -411,6 +415,10 @@ public class AppWidgetManager { TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics); info.minHeight = TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics); + info.minResizeWidth = + TypedValue.complexToDimensionPixelSize(info.minResizeWidth, mDisplayMetrics); + info.minResizeHeight = + TypedValue.complexToDimensionPixelSize(info.minResizeHeight, mDisplayMetrics); } return info; } diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index b8c5b028b165..9c352d58dfb2 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -187,6 +187,8 @@ public class AppWidgetProviderInfo implements Parcelable { } this.minWidth = in.readInt(); this.minHeight = in.readInt(); + this.minResizeWidth = in.readInt(); + this.minResizeHeight = in.readInt(); this.updatePeriodMillis = in.readInt(); this.initialLayout = in.readInt(); if (0 != in.readInt()) { @@ -208,6 +210,8 @@ public class AppWidgetProviderInfo implements Parcelable { } out.writeInt(this.minWidth); out.writeInt(this.minHeight); + out.writeInt(this.minResizeWidth); + out.writeInt(this.minResizeHeight); out.writeInt(this.updatePeriodMillis); out.writeInt(this.initialLayout); if (this.configure != null) { diff --git a/core/java/android/content/ComponentCallbacks.java b/core/java/android/content/ComponentCallbacks.java index 92b98fd81b39..37cc1416ed81 100644 --- a/core/java/android/content/ComponentCallbacks.java +++ b/core/java/android/content/ComponentCallbacks.java @@ -56,11 +56,8 @@ public interface ComponentCallbacks { static final int TRIM_MEMORY_COMPLETE = 80; /** @hide */ - static final int TRIM_MEMORY_MODERATE = 60; + static final int TRIM_MEMORY_MODERATE = 50; /** @hide */ - static final int TRIM_MEMORY_BACKGROUND = 40; - - /** @hide */ - static final int TRIM_MEMORY_INVISIBLE = 20; + static final int TRIM_MEMORY_BACKGROUND = 20; } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index fed6d815c9f2..cdda910d5fb2 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1572,6 +1572,17 @@ public abstract class Context { public static final String WIFI_SERVICE = "wifi"; /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.wifi.p2p.WifiP2pManager} for handling management of + * Wi-Fi p2p. + * + * @see #getSystemService + * @see android.net.wifi.p2p.WifiP2pManager + * @hide + */ + public static final String WIFI_P2P_SERVICE = "wifip2p"; + + /** * Use with {@link #getSystemService} to retrieve a * {@link android.media.AudioManager} for handling management of volume, * ringer modes and audio routing. @@ -1612,6 +1623,15 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.view.textservice.TextServicesManager} for accessing + * text services. + * + * @see #getSystemService + */ + public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.appwidget.AppWidgetManager} for accessing AppWidgets. * * @hide diff --git a/core/java/android/inputmethodservice/ExtractEditLayout.java b/core/java/android/inputmethodservice/ExtractEditLayout.java index eafff49528f5..5cfa998857b7 100644 --- a/core/java/android/inputmethodservice/ExtractEditLayout.java +++ b/core/java/android/inputmethodservice/ExtractEditLayout.java @@ -172,7 +172,10 @@ public class ExtractEditLayout extends LinearLayout { @Override public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { - return mCallback.onActionItemClicked(this, item); + if (mCallback != null) { + return mCallback.onActionItemClicked(this, item); + } + return false; } @Override diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index ce6f697be6be..a564d9771f7b 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -653,6 +653,17 @@ public class ConnectivityManager { } } + /** + * {@hide} + */ + public int setUsbTethering(boolean enable) { + try { + return mService.setUsbTethering(enable); + } catch (RemoteException e) { + return TETHER_ERROR_SERVICE_UNAVAIL; + } + } + /** {@hide} */ public static final int TETHER_ERROR_NO_ERROR = 0; /** {@hide} */ diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java index a866436ed77d..b035c51d3da5 100644 --- a/core/java/android/net/EthernetDataTracker.java +++ b/core/java/android/net/EthernetDataTracker.java @@ -103,6 +103,10 @@ public class EthernetDataTracker implements NetworkStateTracker { public void interfaceRemoved(String iface) { mTracker.interfaceRemoved(iface); } + + public void limitReached(String limitName, String iface) { + // Ignored. + } } private EthernetDataTracker() { diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index d95fc8de70c3..b1d99a4abe42 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -88,6 +88,8 @@ interface IConnectivityManager String[] getTetherableBluetoothRegexs(); + int setUsbTethering(boolean enable); + void requestNetworkTransitionWakelock(in String forWhom); void reportInetCondition(int networkType, int percentage); diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index 4436e6e444ee..a97f2030a146 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -52,4 +52,14 @@ interface INetworkManagementEventObserver { * @param iface The interface. */ void interfaceRemoved(String iface); + + /** + * A networking quota limit has been reached. The quota might not + * be specific to an interface. + * + * @param limitName The name of the limit that triggered. + * @param iface The interface on which the limit was detected. + */ + void limitReached(String limitName, String iface); + } diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 9826becf3180..132f3bad2d26 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -58,8 +58,8 @@ public class LinkProperties implements Parcelable { private ProxyProperties mHttpProxy; public static class CompareResult<T> { - public ArrayList<T> removed = new ArrayList<T>(); - public ArrayList<T> added = new ArrayList<T>(); + public Collection<T> removed = new ArrayList<T>(); + public Collection<T> added = new ArrayList<T>(); @Override public String toString() { diff --git a/core/java/android/net/VpnBuilder.java b/core/java/android/net/VpnBuilder.java new file mode 100644 index 000000000000..458252345152 --- /dev/null +++ b/core/java/android/net/VpnBuilder.java @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2011 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.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.internal.net.VpnConfig; + +import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.DatagramSocket; +import java.net.Socket; +import java.util.ArrayList; + +/** + * VpnBuilder is a framework which enables applications to build their + * own VPN solutions. In general, it creates a virtual network interface, + * configures addresses and routing rules, and returns a file descriptor + * to the application. Each read from the descriptor retrieves an outgoing + * packet which was routed to the interface. Each write to the descriptor + * injects an incoming packet just like it was received from the interface. + * The framework is running on Internet Protocol (IP), so packets are + * always started with IP headers. The application then completes a VPN + * connection by processing and exchanging packets with a remote server + * over a secured tunnel. + * + * <p>Letting applications intercept packets raises huge security concerns. + * Besides, a VPN application can easily break the network, and two of them + * may conflict with each other. The framework takes several actions to + * address these issues. Here are some key points: + * <ul> + * <li>User action is required to create a VPN connection.</li> + * <li>There can be only one VPN connection running at the same time. The + * existing interface is deactivated when a new one is created.</li> + * <li>A system-managed notification is shown during the lifetime of a + * VPN connection.</li> + * <li>A system-managed dialog gives the information of the current VPN + * connection. It also provides a button to disconnect.</li> + * <li>The network is restored automatically when the file descriptor is + * closed. It also covers the cases when a VPN application is crashed + * or killed by the system.</li> + * </ul> + * + * <p>There are two primary methods in this class: {@link #prepare} and + * {@link #establish}. The former deals with the user action and stops + * the existing VPN connection created by another application. The latter + * creates a VPN interface using the parameters supplied to this builder. + * An application must call {@link #prepare} to grant the right to create + * an interface, and it can be revoked at any time by another application. + * The application got revoked is notified by an {@link #ACTION_VPN_REVOKED} + * broadcast. Here are the general steps to create a VPN connection: + * <ol> + * <li>When the user press the button to connect, call {@link #prepare} + * and launch the intent if necessary.</li> + * <li>Register a receiver for {@link #ACTION_VPN_REVOKED} broadcasts. + * <li>Connect to the remote server and negotiate the network parameters + * of the VPN connection.</li> + * <li>Use those parameters to configure a VpnBuilder and create a VPN + * interface by calling {@link #establish}.</li> + * <li>Start processing packets between the returned file descriptor and + * the VPN tunnel.</li> + * <li>When an {@link #ACTION_VPN_REVOKED} broadcast is received, the + * interface is already deactivated by the framework. Close the file + * descriptor and shut down the VPN tunnel gracefully. + * </ol> + * Methods in this class can be used in activities and services. However, + * the intent returned from {@link #prepare} must be launched from an + * activity. The broadcast receiver can be registered at any time, but doing + * it before calling {@link #establish} effectively avoids race conditions. + * + * <p class="note">Using this class requires + * {@link android.Manifest.permission#VPN} permission. + * @hide + */ +public class VpnBuilder { + + /** + * Broadcast intent action indicating that the VPN application has been + * revoked. This can be only received by the target application on the + * receiver explicitly registered using {@link Context#registerReceiver}. + * + * <p>This is a protected intent that can only be sent by the system. + */ + public static final String ACTION_VPN_REVOKED = VpnConfig.ACTION_VPN_REVOKED; + + /** + * Use IConnectivityManager instead since those methods are hidden and + * not available in ConnectivityManager. + */ + private static IConnectivityManager getService() { + return IConnectivityManager.Stub.asInterface( + ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + } + + /** + * Prepare to establish a VPN connection. This method returns {@code null} + * if the VPN application is already prepared. Otherwise, it returns an + * {@link Intent} to a system activity. The application should launch the + * activity using {@link Activity#startActivityForResult} to get itself + * prepared. The activity may pop up a dialog to require user action, and + * the result will come back to the application through its + * {@link Activity#onActivityResult}. The application becomes prepared if + * the result is {@link Activity#RESULT_OK}, and it is granted to create a + * VPN interface by calling {@link #establish}. + * + * <p>Only one application can be granted at the same time. The right + * is revoked when another application is granted. The application + * losing the right will be notified by an {@link #ACTION_VPN_REVOKED} + * broadcast, and its VPN interface will be deactivated by the system. + * The application should then notify the remote server and disconnect + * gracefully. Unless the application becomes prepared again, subsequent + * calls to {@link #establish} will return {@code null}. + * + * @see #establish + * @see #ACTION_VPN_REVOKED + */ + public static Intent prepare(Context context) { + try { + if (getService().prepareVpn(context.getPackageName(), null)) { + return null; + } + } catch (RemoteException e) { + // ignore + } + return VpnConfig.getIntentForConfirmation(); + } + + private VpnConfig mConfig = new VpnConfig(); + private StringBuilder mAddresses = new StringBuilder(); + private StringBuilder mRoutes = new StringBuilder(); + + /** + * Set the name of this session. It will be displayed in system-managed + * dialogs and notifications. This is recommended not required. + */ + public VpnBuilder setSession(String session) { + mConfig.session = session; + return this; + } + + /** + * Set the {@link PendingIntent} to an activity for users to configure + * the VPN connection. If it is not set, the button to configure will + * not be shown in system-managed dialogs. + */ + public VpnBuilder setConfigureIntent(PendingIntent intent) { + mConfig.configureIntent = intent; + return this; + } + + /** + * Set the maximum transmission unit (MTU) of the VPN interface. If it + * is not set, the default value in the operating system will be used. + * + * @throws IllegalArgumentException if the value is not positive. + */ + public VpnBuilder setMtu(int mtu) { + if (mtu <= 0) { + throw new IllegalArgumentException("Bad mtu"); + } + mConfig.mtu = mtu; + return this; + } + + /** + * Private method to validate address and prefixLength. + */ + private static void check(InetAddress address, int prefixLength) { + if (address.isLoopbackAddress()) { + throw new IllegalArgumentException("Bad address"); + } + if (address instanceof Inet4Address) { + if (prefixLength < 0 || prefixLength > 32) { + throw new IllegalArgumentException("Bad prefixLength"); + } + } else if (address instanceof Inet6Address) { + if (prefixLength < 0 || prefixLength > 128) { + throw new IllegalArgumentException("Bad prefixLength"); + } + } else { + throw new IllegalArgumentException("Unsupported family"); + } + } + + /** + * Convenience method to add a network address to the VPN interface + * using a numeric address string. See {@link InetAddress} for the + * definitions of numeric address formats. + * + * @throws IllegalArgumentException if the address is invalid. + * @see #addAddress(InetAddress, int) + */ + public VpnBuilder addAddress(String address, int prefixLength) { + return addAddress(InetAddress.parseNumericAddress(address), prefixLength); + } + + /** + * Add a network address to the VPN interface. Both IPv4 and IPv6 + * addresses are supported. At least one address must be set before + * calling {@link #establish}. + * + * @throws IllegalArgumentException if the address is invalid. + */ + public VpnBuilder addAddress(InetAddress address, int prefixLength) { + check(address, prefixLength); + + if (address.isAnyLocalAddress()) { + throw new IllegalArgumentException("Bad address"); + } + + mAddresses.append(String.format(" %s/%d", address.getHostAddress(), prefixLength)); + return this; + } + + /** + * Convenience method to add a network route to the VPN interface + * using a numeric address string. See {@link InetAddress} for the + * definitions of numeric address formats. + * + * @see #addRoute(InetAddress, int) + * @throws IllegalArgumentException if the route is invalid. + */ + public VpnBuilder addRoute(String address, int prefixLength) { + return addRoute(InetAddress.parseNumericAddress(address), prefixLength); + } + + /** + * Add a network route to the VPN interface. Both IPv4 and IPv6 + * routes are supported. + * + * @throws IllegalArgumentException if the route is invalid. + */ + public VpnBuilder addRoute(InetAddress address, int prefixLength) { + check(address, prefixLength); + + int offset = prefixLength / 8; + byte[] bytes = address.getAddress(); + if (offset < bytes.length) { + if ((byte)(bytes[offset] << (prefixLength % 8)) != 0) { + throw new IllegalArgumentException("Bad address"); + } + while (++offset < bytes.length) { + if (bytes[offset] != 0) { + throw new IllegalArgumentException("Bad address"); + } + } + } + + mRoutes.append(String.format(" %s/%d", address.getHostAddress(), prefixLength)); + return this; + } + + /** + * Convenience method to add a DNS server to the VPN connection + * using a numeric address string. See {@link InetAddress} for the + * definitions of numeric address formats. + * + * @throws IllegalArgumentException if the address is invalid. + * @see #addDnsServer(InetAddress) + */ + public VpnBuilder addDnsServer(String address) { + return addDnsServer(InetAddress.parseNumericAddress(address)); + } + + /** + * Add a DNS server to the VPN connection. Both IPv4 and IPv6 + * addresses are supported. If none is set, the DNS servers of + * the default network will be used. + * + * @throws IllegalArgumentException if the address is invalid. + */ + public VpnBuilder addDnsServer(InetAddress address) { + if (address.isLoopbackAddress() || address.isAnyLocalAddress()) { + throw new IllegalArgumentException("Bad address"); + } + if (mConfig.dnsServers == null) { + mConfig.dnsServers = new ArrayList<String>(); + } + mConfig.dnsServers.add(address.getHostAddress()); + return this; + } + + /** + * Add a search domain to the DNS resolver. + */ + public VpnBuilder addSearchDomain(String domain) { + if (mConfig.searchDomains == null) { + mConfig.searchDomains = new ArrayList<String>(); + } + mConfig.searchDomains.add(domain); + return this; + } + + /** + * Create a VPN interface using the parameters supplied to this builder. + * The interface works on IP packets, and a file descriptor is returned + * for the application to access them. Each read retrieves an outgoing + * packet which was routed to the interface. Each write injects an + * incoming packet just like it was received from the interface. The file + * descriptor is put into non-blocking mode by default to avoid blocking + * Java threads. To use the file descriptor completely in native space, + * see {@link ParcelFileDescriptor#detachFd()}. The application MUST + * close the file descriptor when the VPN connection is terminated. The + * VPN interface will be removed and the network will be restored by the + * framework automatically. + * + * <p>To avoid conflicts, there can be only one active VPN interface at + * the same time. Usually network parameters are never changed during the + * lifetime of a VPN connection. It is also common for an application to + * create a new file descriptor after closing the previous one. However, + * it is rare but not impossible to have two interfaces while performing a + * seamless handover. In this case, the old interface will be deactivated + * when the new one is configured successfully. Both file descriptors are + * valid but now outgoing packets will be routed to the new interface. + * Therefore, after draining the old file descriptor, the application MUST + * close it and start using the new file descriptor. If the new interface + * cannot be created, the existing interface and its file descriptor remain + * untouched. + * + * <p>An exception will be thrown if the interface cannot be created for + * any reason. However, this method returns {@code null} if the application + * is not prepared or is revoked by another application. This helps solve + * possible race conditions while handling {@link #ACTION_VPN_REVOKED} + * broadcasts. + * + * @return {@link ParcelFileDescriptor} of the VPN interface, or + * {@code null} if the application is not prepared. + * @throws IllegalArgumentException if a parameter is not accepted by the + * operating system. + * @throws IllegalStateException if a parameter cannot be applied by the + * operating system. + * @see #prepare + */ + public ParcelFileDescriptor establish() { + mConfig.addresses = mAddresses.toString(); + mConfig.routes = mRoutes.toString(); + + try { + return getService().establishVpn(mConfig); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** + * Protect a socket from VPN connections. The socket will be bound to the + * current default network interface, so its traffic will not be forwarded + * through VPN. This method is useful if some connections need to be kept + * outside of VPN. For example, a VPN tunnel should protect itself if its + * destination is covered by VPN routes. Otherwise its outgoing packets + * will be sent back to the VPN interface and cause an infinite loop. + * + * <p>The socket is NOT closed by this method. + * + * @return {@code true} on success. + */ + public static boolean protect(int socket) { + ParcelFileDescriptor dup = null; + try { + dup = ParcelFileDescriptor.fromFd(socket); + return getService().protectVpn(dup); + } catch (Exception e) { + return false; + } finally { + try { + dup.close(); + } catch (Exception e) { + // ignore + } + } + } + + /** + * Protect a {@link Socket} from VPN connections. + * + * @return {@code true} on success. + * @see #protect(int) + */ + public static boolean protect(Socket socket) { + return protect(socket.getFileDescriptor$().getInt$()); + } + + /** + * Protect a {@link DatagramSocket} from VPN connections. + * + * @return {@code true} on success. + * @see #protect(int) + */ + public static boolean protect(DatagramSocket socket) { + return protect(socket.getFileDescriptor$().getInt$()); + } +} diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index cd39d5cc6dee..bc372447d63e 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -361,7 +361,8 @@ public class Handler { /** * Remove any pending posts of Runnable <var>r</var> with Object - * <var>token</var> that are in the message queue. + * <var>token</var> that are in the message queue. If <var>token</var> is null, + * all callbacks will be removed. */ public final void removeCallbacks(Runnable r, Object token) { @@ -517,7 +518,8 @@ public class Handler { /** * Remove any pending posts of messages with code 'what' and whose obj is - * 'object' that are in the message queue. + * 'object' that are in the message queue. If <var>token</var> is null, + * all messages will be removed. */ public final void removeMessages(int what, Object object) { mQueue.removeMessages(this, what, object, true); @@ -525,7 +527,8 @@ public class Handler { /** * Remove any pending posts of callbacks and sent messages whose - * <var>obj</var> is <var>token</var>. + * <var>obj</var> is <var>token</var>. If <var>token</var> is null, + * all callbacks and messages will be removed. */ public final void removeCallbacksAndMessages(Object token) { mQueue.removeCallbacksAndMessages(this, token); diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index f23052604528..1174e3b696f5 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -180,7 +180,7 @@ interface INetworkManagementService /** * Stop Wifi Access Point */ - void stopAccessPoint(); + void stopAccessPoint(String wlanIface); /** * Set Access Point config diff --git a/core/java/android/preference/CheckBoxPreference.java b/core/java/android/preference/CheckBoxPreference.java index 437e55332a81..166b21b66160 100644 --- a/core/java/android/preference/CheckBoxPreference.java +++ b/core/java/android/preference/CheckBoxPreference.java @@ -61,8 +61,8 @@ public class CheckBoxPreference extends TwoStatePreference { View checkboxView = view.findViewById(com.android.internal.R.id.checkbox); if (checkboxView != null && checkboxView instanceof Checkable) { ((Checkable) checkboxView).setChecked(mChecked); - - sendAccessibilityEventForView(checkboxView); + // Post this so this view is bound and attached when firing the event. + postSendAccessibilityEventForView(checkboxView); } syncSummaryView(view); diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index c90de176896b..78c9010af8fd 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -563,6 +563,12 @@ public abstract class PreferenceActivity extends ListActivity implements // Single pane, showing just a prefs fragment. findViewById(com.android.internal.R.id.headers).setVisibility(View.GONE); mPrefsContainer.setVisibility(View.VISIBLE); + if (initialTitle != 0) { + CharSequence initialTitleStr = getText(initialTitle); + CharSequence initialShortTitleStr = initialShortTitle != 0 + ? getText(initialShortTitle) : null; + showBreadCrumbs(initialTitleStr, initialShortTitleStr); + } } else if (mHeaders.size() > 0) { setListAdapter(new HeaderAdapter(this, mHeaders)); if (!mSinglePane) { @@ -1093,6 +1099,10 @@ public abstract class PreferenceActivity extends ListActivity implements } else { getListView().clearChoices(); } + showBreadCrumbs(header); + } + + void showBreadCrumbs(Header header) { if (header != null) { CharSequence title = header.getBreadCrumbTitle(getResources()); if (title == null) title = header.getTitle(getResources()); diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java index f681526a3ce4..3dbd5229cb83 100644 --- a/core/java/android/preference/SwitchPreference.java +++ b/core/java/android/preference/SwitchPreference.java @@ -102,8 +102,8 @@ public class SwitchPreference extends TwoStatePreference { View checkableView = view.findViewById(com.android.internal.R.id.switchWidget); if (checkableView != null && checkableView instanceof Checkable) { ((Checkable) checkableView).setChecked(mChecked); - - sendAccessibilityEventForView(checkableView); + // Post this so this view is bound and attached when firing the event. + postSendAccessibilityEventForView(checkableView); if (checkableView instanceof Switch) { final Switch switchView = (Switch) checkableView; diff --git a/core/java/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java index 8e21c4c493a5..55ef108e2932 100644 --- a/core/java/android/preference/TwoStatePreference.java +++ b/core/java/android/preference/TwoStatePreference.java @@ -16,7 +16,6 @@ package android.preference; -import android.app.Service; import android.content.Context; import android.content.SharedPreferences; import android.content.res.TypedArray; @@ -39,28 +38,20 @@ public abstract class TwoStatePreference extends Preference { private CharSequence mSummaryOff; boolean mChecked; private boolean mSendAccessibilityEventViewClickedType; - private AccessibilityManager mAccessibilityManager; private boolean mDisableDependentsState; + private SendAccessibilityEventTypeViewClicked mSendAccessibilityEventTypeViewClicked; + public TwoStatePreference(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - - mAccessibilityManager = - (AccessibilityManager) getContext().getSystemService(Service.ACCESSIBILITY_SERVICE); } public TwoStatePreference(Context context, AttributeSet attrs) { - super(context, attrs); - - mAccessibilityManager = - (AccessibilityManager) getContext().getSystemService(Service.ACCESSIBILITY_SERVICE); + this(context, attrs, 0); } public TwoStatePreference(Context context) { - super(context); - - mAccessibilityManager = - (AccessibilityManager) getContext().getSystemService(Service.ACCESSIBILITY_SERVICE); + this(context, null); } @Override @@ -198,20 +189,23 @@ public abstract class TwoStatePreference extends Preference { } /** - * Send an accessibility event for the given view if appropriate + * Post send an accessibility event for the given view if appropriate. + * * @param view View that should send the event */ - void sendAccessibilityEventForView(View view) { + void postSendAccessibilityEventForView(View view) { // send an event to announce the value change of the state. It is done here // because clicking a preference does not immediately change the checked state // for example when enabling the WiFi - if (mSendAccessibilityEventViewClickedType && - mAccessibilityManager.isEnabled() && - view.isEnabled()) { + if (mSendAccessibilityEventViewClickedType + && AccessibilityManager.getInstance(getContext()).isEnabled() + && view.isEnabled()) { mSendAccessibilityEventViewClickedType = false; - - int eventType = AccessibilityEvent.TYPE_VIEW_CLICKED; - view.sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType)); + if (mSendAccessibilityEventTypeViewClicked == null) { + mSendAccessibilityEventTypeViewClicked = new SendAccessibilityEventTypeViewClicked(); + } + mSendAccessibilityEventTypeViewClicked.mView = view; + view.post(mSendAccessibilityEventTypeViewClicked); } } @@ -306,4 +300,13 @@ public abstract class TwoStatePreference extends Preference { } }; } + + private final class SendAccessibilityEventTypeViewClicked implements Runnable { + private View mView; + + @Override + public void run() { + mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + } + } } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 382fcf34757c..e23d6f357506 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -184,6 +184,16 @@ public class CallLog { public static final String VOICEMAIL_URI = "voicemail_uri"; /** + * Whether this item has been read or otherwise consumed by the user. + * <p> + * Unlike the {@link #NEW} field, which requires the user to have acknowledged the + * existence of the entry, this implies the user has interacted with the entry. + * <P>Type: INTEGER (boolean)</P> + * @hide + */ + public static final String IS_READ = "is_read"; + + /** * Adds a call to the call log. * * @param ci the CallerInfo object to get the target contact from. Can be null diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index c2998916add4..a0f1eece6367 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -1439,6 +1439,13 @@ public final class ContactsContract { CONTENT_URI, "strequent"); /** + * The content:// style URI for showing frequently contacted person listing. + * @hide + */ + public static final Uri CONTENT_FREQUENT_URI = Uri.withAppendedPath( + CONTENT_URI, "frequent"); + + /** * The content:// style URI used for "type-to-filter" functionality on the * {@link #CONTENT_STREQUENT_URI} URI. The filter string will be used to match * various parts of the contact name. The filter argument should be passed @@ -6507,6 +6514,32 @@ public final class ContactsContract { public static final String SUMMARY_COUNT = "summ_count"; /** + * A boolean query parameter that can be used with {@link Groups#CONTENT_SUMMARY_URI}. + * It will additionally return {@link #SUMMARY_GROUP_COUNT_PER_ACCOUNT}. + * + * @hide + */ + public static final String PARAM_RETURN_GROUP_COUNT_PER_ACCOUNT = + "return_group_count_per_account"; + + /** + * The total number of groups of the account that a group belongs to. + * This column is available only when the parameter + * {@link #PARAM_RETURN_GROUP_COUNT_PER_ACCOUNT} is specified in + * {@link Groups#CONTENT_SUMMARY_URI}. + * + * For example, when the account "A" has two groups "group1" and "group2", and the account + * "B" has a group "group3", the rows for "group1" and "group2" return "2" and the row for + * "group3" returns "1" for this column. + * + * Note: This counts only non-favorites, non-auto-add, and not deleted groups. + * + * Type: INTEGER + * @hide + */ + public static final String SUMMARY_GROUP_COUNT_PER_ACCOUNT = "group_count_per_account"; + + /** * The total number of {@link Contacts} that have both * {@link CommonDataKinds.GroupMembership} in this group, and also have phone numbers. * Read-only value that is only present when querying diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e48c858a1e2a..cd4e32e8ce32 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -16,8 +16,6 @@ package android.provider; - - import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.SearchManager; @@ -48,7 +46,6 @@ import java.net.URISyntaxException; import java.util.HashMap; import java.util.HashSet; - /** * The Settings provider contains global system-level device preferences. */ @@ -3735,6 +3732,15 @@ public final class Settings { */ public static final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service"; + + /** + * The {@link ComponentName} string of the service to be used as the spell checker + * service which is one of the services managed by the text service manager. + * + * @hide + */ + public static final String SPELL_CHECKER_SERVICE = "spell_checker_service"; + /** * What happens when the user presses the Power button while in-call * and the screen is on.<br/> @@ -3927,6 +3933,10 @@ public final class Settings { /** Timeout in milliseconds to wait for NTP server. {@hide} */ public static final String NTP_TIMEOUT = "ntp_timeout"; + /** Autofill server address (Used in WebView/browser). {@hide} */ + public static final String WEB_AUTOFILL_QUERY_URL = + "web_autofill_query_url"; + /** * @hide */ diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index 2ad7395866fa..2e5a495a282d 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -128,6 +128,12 @@ public class VoicemailContract { */ public static final String NEW = Calls.NEW; /** + * Whether this item has been read or otherwise consumed by the user. + * <P>Type: INTEGER (boolean)</P> + * @hide + */ + public static final String IS_READ = Calls.IS_READ; + /** * The mail box state of the voicemail. This field is currently not used by the system. * <P> Possible values: {@link #STATE_INBOX}, {@link #STATE_DELETED}, * {@link #STATE_UNDELETED}. diff --git a/core/java/android/server/BluetoothAdapterStateMachine.java b/core/java/android/server/BluetoothAdapterStateMachine.java new file mode 100644 index 000000000000..ae9146593e0e --- /dev/null +++ b/core/java/android/server/BluetoothAdapterStateMachine.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2011 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.server; + +import android.bluetooth.BluetoothAdapter; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Message; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.util.IState; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.PrintWriter; + +/** + * Bluetooth Adapter StateMachine + * All the states are at the same level, ie, no hierarchy. + * (BluetootOn) + * | ^ + * TURN_OFF | | BECOME_PAIRABLE + * AIRPLANE_MODE_ON | | + * V | + * (Switching) + * | ^ + * BECOME_NON_PAIRABLE& | | TURN_ON(_CONTINUE)/TURN_ON_FOR_PRIVILEGED + * ALL_DEVICES_DISCONNECTED | | + * V | + * (HotOff) + * / ^ + * / | SERVICE_RECORD_LOADED + * | | + * TURN_COLD | (Warmup) + * \ ^ + * \ | TURN_HOT/TURN_ON + * | | AIRPLANE_MODE_OFF(when Bluetooth was on before) + * V | + * (PowerOff) <----- initial state + * + */ +final class BluetoothAdapterStateMachine extends StateMachine { + private static final String TAG = "BluetoothAdapterStateMachine"; + private static final boolean DBG = false; + + // Message(what) to take an action + // + // We get this message when user tries to turn on BT + public static final int USER_TURN_ON = 1; + // We get this message when user tries to turn off BT + public static final int USER_TURN_OFF = 2; + + // Message(what) to report a event that the state machine need to respond to + // + // Event indicates sevice records have been loaded + public static final int SERVICE_RECORD_LOADED = 51; + // Event indicates all the remote Bluetooth devices has been disconnected + public static final int ALL_DEVICES_DISCONNECTED = 52; + // Event indicates the Bluetooth is connectable + public static final int BECOME_PAIRABLE = 53; + // Event indicates the Bluetooth is non-connectable. + public static final int BECOME_NON_PAIRABLE = 54; + // Event indicates airplane mode is turned on + public static final int AIRPLANE_MODE_ON = 55; + // Event indicates airplane mode is turned off + public static final int AIRPLANE_MODE_OFF = 56; + + // private internal messages + // + // Turn on Bluetooth Module, Load firmware, and do all the preparation + // needed to get the Bluetooth Module ready but keep it not discoverable + // and not connectable. This way the Bluetooth Module can be quickly + // switched on if needed + private static final int TURN_HOT = 101; + // USER_TURN_ON is changed to TURN_ON_CONTINUE after we broadcast the + // state change intent so that we will not broadcast the intent again in + // other state + private static final int TURN_ON_CONTINUE = 102; + // Unload firmware, turning off Bluetooth module power + private static final int TURN_COLD = 103; + // For NFC, turn on bluetooth for certain process + private static final int TURN_ON_FOR_PRIVILEGED = 104; + + private Context mContext; + private BluetoothService mBluetoothService; + private BluetoothEventLoop mEventLoop; + + private BluetoothOn mBluetoothOn; + private Switching mSwitching; + private HotOff mHotOff; + private WarmUp mWarmUp; + private PowerOff mPowerOff; + + // this is the BluetoothAdapter state that reported externally + private int mPublicState; + + BluetoothAdapterStateMachine(Context context, BluetoothService bluetoothService, + BluetoothAdapter bluetoothAdapter) { + super(TAG); + mContext = context; + mBluetoothService = bluetoothService; + mEventLoop = new BluetoothEventLoop(context, bluetoothAdapter, bluetoothService, this); + + mBluetoothOn = new BluetoothOn(); + mSwitching = new Switching(); + mHotOff = new HotOff(); + mWarmUp = new WarmUp(); + mPowerOff = new PowerOff(); + + addState(mBluetoothOn); + addState(mSwitching); + addState(mHotOff); + addState(mWarmUp); + addState(mPowerOff); + setInitialState(mPowerOff); + mPublicState = BluetoothAdapter.STATE_OFF; + + if (mContext.getResources().getBoolean + (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { + sendMessage(TURN_HOT); + } + } + + /** + * Bluetooth module's power is off, firmware is not loaded. + */ + private class PowerOff extends State { + private boolean mPersistSwitchOn = false; + + @Override + public void enter() { + if (DBG) log("Enter PowerOff: " + mPersistSwitchOn); + mPersistSwitchOn = false; + } + @Override + public boolean processMessage(Message message) { + if (DBG) log("PowerOff process message: " + message.what); + + boolean retValue = HANDLED; + switch(message.what) { + case USER_TURN_ON: + // starts turning on BT module, broadcast this out + transitionTo(mWarmUp); + broadcastState(BluetoothAdapter.STATE_TURNING_ON); + if (prepareBluetooth()) { + // this is user request, save the setting + if ((Boolean) message.obj) { + mPersistSwitchOn = true; + } + // We will continue turn the BT on all the way to the BluetoothOn state + deferMessage(obtainMessage(TURN_ON_CONTINUE)); + } else { + Log.e(TAG, "failed to prepare bluetooth, abort turning on"); + transitionTo(mPowerOff); + broadcastState(BluetoothAdapter.STATE_OFF); + } + break; + case TURN_HOT: + if (prepareBluetooth()) { + transitionTo(mWarmUp); + } + break; + case AIRPLANE_MODE_OFF: + if (getBluetoothPersistedSetting()) { + // starts turning on BT module, broadcast this out + transitionTo(mWarmUp); + broadcastState(BluetoothAdapter.STATE_TURNING_ON); + if (prepareBluetooth()) { + // We will continue turn the BT on all the way to the BluetoothOn state + deferMessage(obtainMessage(TURN_ON_CONTINUE)); + transitionTo(mWarmUp); + } else { + Log.e(TAG, "failed to prepare bluetooth, abort turning on"); + transitionTo(mPowerOff); + broadcastState(BluetoothAdapter.STATE_OFF); + } + } + break; + case AIRPLANE_MODE_ON: // ignore + case USER_TURN_OFF: // ignore + break; + default: + return NOT_HANDLED; + } + return retValue; + } + + /** + * Turn on Bluetooth Module, Load firmware, and do all the preparation + * needed to get the Bluetooth Module ready but keep it not discoverable + * and not connectable. + * The last step of this method sets up the local service record DB. + * There will be a event reporting the status of the SDP setup. + */ + private boolean prepareBluetooth() { + if (mBluetoothService.enableNative() != 0) { + return false; + } + + // try to start event loop, give 2 attempts + int retryCount = 2; + boolean eventLoopStarted = false; + while ((retryCount-- > 0) && !eventLoopStarted) { + mEventLoop.start(); + // it may take a moment for the other thread to do its + // thing. Check periodically for a while. + int pollCount = 5; + while ((pollCount-- > 0) && !eventLoopStarted) { + if (mEventLoop.isEventLoopRunning()) { + eventLoopStarted = true; + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + break; + } + } + } + + if (!eventLoopStarted) { + mBluetoothService.disableNative(); + return false; + } + + // get BluetoothService ready + if (!mBluetoothService.prepareBluetooth()) { + mEventLoop.stop(); + mBluetoothService.disableNative(); + return false; + } + + return true; + } + } + + /** + * Turning on Bluetooth module's power, loading firmware, starting + * event loop thread to listen on Bluetooth module event changes. + */ + private class WarmUp extends State { + + @Override + public void enter() { + if (DBG) log("Enter WarmUp"); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log("WarmUp process message: " + message.what); + + boolean retValue = HANDLED; + switch(message.what) { + case SERVICE_RECORD_LOADED: + transitionTo(mHotOff); + break; + case USER_TURN_ON: // handle this at HotOff state + case TURN_ON_CONTINUE: // Once in HotOff state, continue turn bluetooth + // on to the BluetoothOn state + case AIRPLANE_MODE_ON: + case AIRPLANE_MODE_OFF: + deferMessage(message); + break; + case USER_TURN_OFF: // ignore + break; + default: + return NOT_HANDLED; + } + return retValue; + } + + } + + /** + * Bluetooth Module has powered, firmware loaded, event loop started, + * SDP loaded, but the modules stays non-discoverable and + * non-connectable. + */ + private class HotOff extends State { + private boolean mPersistSwitchOn = false; + + @Override + public void enter() { + if (DBG) log("Enter HotOff: " + mPersistSwitchOn); + mPersistSwitchOn = false; + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log("HotOff process message: " + message.what); + + boolean retValue = HANDLED; + switch(message.what) { + case USER_TURN_ON: + if ((Boolean) message.obj) { + mPersistSwitchOn = true; + } + // let it fall to TURN_ON_CONTINUE: + case TURN_ON_CONTINUE: + mBluetoothService.switchConnectable(true); + transitionTo(mSwitching); + broadcastState(BluetoothAdapter.STATE_TURNING_ON); + break; + case AIRPLANE_MODE_ON: + case TURN_COLD: + mBluetoothService.shutoffBluetooth(); + mEventLoop.stop(); + transitionTo(mPowerOff); + // ASSERT no support of config_bluetooth_adapter_quick_switch + broadcastState(BluetoothAdapter.STATE_OFF); + break; + case AIRPLANE_MODE_OFF: + if (getBluetoothPersistedSetting()) { + mBluetoothService.switchConnectable(true); + transitionTo(mSwitching); + broadcastState(BluetoothAdapter.STATE_TURNING_ON); + } + break; + case USER_TURN_OFF: // ignore + break; + default: + return NOT_HANDLED; + } + return retValue; + } + + } + + private class Switching extends State { + + @Override + public void enter() { + int what = getCurrentMessage().what; + if (DBG) log("Enter Switching: " + what); + } + @Override + public boolean processMessage(Message message) { + if (DBG) log("Switching process message: " + message.what); + + boolean retValue = HANDLED; + switch(message.what) { + case BECOME_PAIRABLE: + if (mPowerOff.mPersistSwitchOn || mHotOff.mPersistSwitchOn) { + persistSwitchSetting(true); + mPowerOff.mPersistSwitchOn = mHotOff.mPersistSwitchOn = false; + } + String[] propVal = {"Pairable", mBluetoothService.getProperty("Pairable")}; + mEventLoop.onPropertyChanged(propVal); + + // run bluetooth now that it's turned on + mBluetoothService.runBluetooth(); + transitionTo(mBluetoothOn); + broadcastState(BluetoothAdapter.STATE_ON); + break; + case BECOME_NON_PAIRABLE: + if (mBluetoothService.getAdapterConnectionState() == + BluetoothAdapter.STATE_DISCONNECTED) { + transitionTo(mHotOff); + finishSwitchingOff(); + } + break; + case ALL_DEVICES_DISCONNECTED: + if (mBluetoothService.getScanMode() == BluetoothAdapter.SCAN_MODE_NONE) { + transitionTo(mHotOff); + finishSwitchingOff(); + } + break; + case USER_TURN_ON: + case AIRPLANE_MODE_OFF: + case AIRPLANE_MODE_ON: + case USER_TURN_OFF: + deferMessage(message); + break; + default: + return NOT_HANDLED; + } + return retValue; + } + + private void finishSwitchingOff() { + if (mBluetoothOn.mPersistBluetoothOff) { + persistSwitchSetting(false); + mBluetoothOn.mPersistBluetoothOff = false; + } + mBluetoothService.finishDisable(); + if (mContext.getResources().getBoolean + (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { + broadcastState(BluetoothAdapter.STATE_OFF); + } else { + deferMessage(obtainMessage(TURN_COLD)); + } + } + } + + private class BluetoothOn extends State { + private boolean mPersistBluetoothOff = false; + + @Override + public void enter() { + if (DBG) log("Enter BluetoothOn: " + mPersistBluetoothOff); + mPersistBluetoothOff = false; + } + @Override + public boolean processMessage(Message message) { + if (DBG) log("BluetoothOn process message: " + message.what); + + boolean retValue = HANDLED; + switch(message.what) { + case USER_TURN_OFF: + if ((Boolean) message.obj) { + mPersistBluetoothOff = true; + } + // let it fall through to AIRPLANE_MODE_ON + case AIRPLANE_MODE_ON: + transitionTo(mSwitching); + broadcastState(BluetoothAdapter.STATE_TURNING_OFF); + mBluetoothService.switchConnectable(false); + mBluetoothService.disconnectDevices(); + // we turn all the way to PowerOff with AIRPLANE_MODE_ON + if (message.what == AIRPLANE_MODE_ON) { + deferMessage(obtainMessage(AIRPLANE_MODE_ON)); + } + break; + case AIRPLANE_MODE_OFF: // ignore + case USER_TURN_ON: // ignore + break; + default: + return NOT_HANDLED; + } + return retValue; + } + + } + + /** + * Return the public BluetoothAdapter state + */ + int getBluetoothAdapterState() { + return mPublicState; + } + + BluetoothEventLoop getBluetoothEventLoop() { + return mEventLoop; + } + + private void persistSwitchSetting(boolean setOn) { + long origCallerIdentityToken = Binder.clearCallingIdentity(); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.BLUETOOTH_ON, + setOn ? 1 : 0); + Binder.restoreCallingIdentity(origCallerIdentityToken); + } + + private boolean getBluetoothPersistedSetting() { + ContentResolver contentResolver = mContext.getContentResolver(); + return (Settings.Secure.getInt(contentResolver, + Settings.Secure.BLUETOOTH_ON, 0) > 0); + } + + private void broadcastState(int newState) { + + if (DBG) log("Bluetooth state " + mPublicState + " -> " + newState); + if (mPublicState == newState) { + return; + } + + Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); + intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mPublicState); + intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mPublicState = newState; + + mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM); + } + + private void dump(PrintWriter pw) { + IState currentState = getCurrentState(); + if (currentState == mPowerOff) { + pw.println("Bluetooth OFF - power down\n"); + } else if (currentState == mWarmUp) { + pw.println("Bluetooth OFF - warm up\n"); + } else if (currentState == mHotOff) { + pw.println("Bluetooth OFF - hot but off\n"); + } else if (currentState == mSwitching) { + pw.println("Bluetooth Switching\n"); + } else if (currentState == mBluetoothOn) { + pw.println("Bluetooth ON\n"); + } else { + pw.println("ERROR: Bluetooth UNKNOWN STATE "); + } + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index f345a6a953d8..107a2a9cf16d 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -52,6 +52,7 @@ class BluetoothEventLoop { private final HashMap<String, Integer> mAuthorizationAgentRequestData; private final BluetoothService mBluetoothService; private final BluetoothAdapter mAdapter; + private final BluetoothAdapterStateMachine mBluetoothState; private BluetoothA2dp mA2dp; private BluetoothInputDevice mInputDevice; private final Context mContext; @@ -107,9 +108,11 @@ class BluetoothEventLoop { private static native void classInitNative(); /* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter, - BluetoothService bluetoothService) { + BluetoothService bluetoothService, + BluetoothAdapterStateMachine bluetoothState) { mBluetoothService = bluetoothService; mContext = context; + mBluetoothState = bluetoothState; mPasskeyAgentRequestData = new HashMap<String, Integer>(); mAuthorizationAgentRequestData = new HashMap<String, Integer>(); mAdapter = adapter; @@ -299,8 +302,8 @@ class BluetoothEventLoop { /** * Called by native code on a PropertyChanged signal from - * org.bluez.Adapter. This method is also called from Java at - * {@link BluetoothService.EnableThread#run()} to set the "Pairable" + * org.bluez.Adapter. This method is also called from + * {@link BluetoothAdapterStateMachine} to set the "Pairable" * property when Bluetooth is enabled. * * @param propValues a string array containing the key and one or more @@ -334,6 +337,15 @@ class BluetoothEventLoop { return; adapterProperties.setProperty(name, propValues[1]); + + if (name.equals("Pairable")) { + if (pairable.equals("true")) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.BECOME_PAIRABLE); + } else { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.BECOME_NON_PAIRABLE); + } + } + int mode = BluetoothService.bluezStringToScanMode( pairable.equals("true"), discoverable.equals("true")); diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index ab569a1dea89..34f1971a265c 100755 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -91,7 +91,7 @@ public class BluetoothService extends IBluetooth.Stub { private BluetoothPan mPan; private boolean mIsAirplaneSensitive; private boolean mIsAirplaneToggleable; - private int mBluetoothState; + private BluetoothAdapterStateMachine mBluetoothState; private boolean mRestart = false; // need to call enable() after disable() private boolean mIsDiscovering; private int[] mAdapterSdpHandles; @@ -111,9 +111,8 @@ public class BluetoothService extends IBluetooth.Stub { private static final String SHARED_PREFERENCE_DOCK_ADDRESS = "dock_bluetooth_address"; private static final String SHARED_PREFERENCES_NAME = "bluetooth_service_settings"; - private static final int MESSAGE_FINISH_DISABLE = 1; - private static final int MESSAGE_UUID_INTENT = 2; - private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3; + private static final int MESSAGE_UUID_INTENT = 1; + private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 2; // The time (in millisecs) to delay the pairing attempt after the first // auto pairing attempt fails. We use an exponential delay with @@ -206,7 +205,6 @@ public class BluetoothService extends IBluetooth.Stub { disableNative(); } - mBluetoothState = BluetoothAdapter.STATE_OFF; mIsDiscovering = false; mBondState = new BluetoothBondState(context, this); @@ -306,7 +304,9 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized void initAfterRegistration() { mAdapter = BluetoothAdapter.getDefaultAdapter(); - mEventLoop = new BluetoothEventLoop(mContext, mAdapter, this); + mBluetoothState = new BluetoothAdapterStateMachine(mContext, this, mAdapter); + mBluetoothState.start(); + mEventLoop = mBluetoothState.getBluetoothEventLoop(); } public synchronized void initAfterA2dpRegistration() { @@ -329,16 +329,16 @@ public class BluetoothService extends IBluetooth.Stub { } private boolean isEnabledInternal() { - return mBluetoothState == BluetoothAdapter.STATE_ON; + return (getBluetoothStateInternal() == BluetoothAdapter.STATE_ON); } public int getBluetoothState() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return mBluetoothState; + return getBluetoothStateInternal(); } int getBluetoothStateInternal() { - return mBluetoothState; + return mBluetoothState.getBluetoothAdapterState(); } /** @@ -356,7 +356,9 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean disable(boolean saveSetting) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); - switch (mBluetoothState) { + int adapterState = getBluetoothStateInternal(); + + switch (adapterState) { case BluetoothAdapter.STATE_OFF: return true; case BluetoothAdapter.STATE_ON: @@ -364,27 +366,12 @@ public class BluetoothService extends IBluetooth.Stub { default: return false; } - if (mEnableThread != null && mEnableThread.isAlive()) { - return false; - } - - setBluetoothState(BluetoothAdapter.STATE_TURNING_OFF); - - if (mAdapterSdpHandles != null) removeReservedServiceRecordsNative(mAdapterSdpHandles); - setBluetoothTetheringNative(false, BluetoothPanProfileHandler.NAP_ROLE, - BluetoothPanProfileHandler.NAP_BRIDGE); - - // Allow 3 seconds for profiles to gracefully disconnect - // TODO: Introduce a callback mechanism so that each profile can notify - // BluetoothService when it is done shutting down - disconnectDevices(); - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MESSAGE_FINISH_DISABLE, saveSetting ? 1 : 0, 0), 3000); + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_OFF, saveSetting); return true; } - private synchronized void disconnectDevices() { + synchronized void disconnectDevices() { // Disconnect devices handled by BluetoothService. for (BluetoothDevice device: getConnectedInputDevices()) { disconnectInputDevice(device); @@ -395,14 +382,11 @@ public class BluetoothService extends IBluetooth.Stub { } } - private synchronized void finishDisable(boolean saveSetting) { - if (mBluetoothState != BluetoothAdapter.STATE_TURNING_OFF) { - return; - } - mEventLoop.stop(); - tearDownNativeDataNative(); - disableNative(); - + /** + * The Bluetooth has been turned off, but hot. Do bonding, profile, + * and internal cleanup + */ + synchronized void finishDisable() { // mark in progress bondings as cancelled for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDING)) { mBondState.setBondState(address, BluetoothDevice.BOND_NONE, @@ -430,12 +414,6 @@ public class BluetoothService extends IBluetooth.Stub { mAdapterUuids = null; mAdapterSdpHandles = null; - if (saveSetting) { - persistBluetoothOnSetting(false); - } - - setBluetoothState(BluetoothAdapter.STATE_OFF); - // Log bluetooth off to battery stats. long ident = Binder.clearCallingIdentity(); try { @@ -451,6 +429,18 @@ public class BluetoothService extends IBluetooth.Stub { } } + /** + * power off Bluetooth + */ + synchronized void shutoffBluetooth() { + tearDownNativeDataNative(); + disableNative(); + if (mRestart) { + mRestart = false; + enable(); + } + } + /** Bring up BT and persist BT on in settings */ public boolean enable() { return enable(true); @@ -471,21 +461,29 @@ public class BluetoothService extends IBluetooth.Stub { if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) { return false; } - if (mBluetoothState != BluetoothAdapter.STATE_OFF) { - return false; - } - if (mEnableThread != null && mEnableThread.isAlive()) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_ON, saveSetting); + return true; + } + + /** + * Turn on Bluetooth Module, Load firmware, and do all the preparation + * needed to get the Bluetooth Module ready but keep it not discoverable + * and not connectable. + */ + /* package */ synchronized boolean prepareBluetooth() { + if (!setupNativeDataNative()) { return false; } - setBluetoothState(BluetoothAdapter.STATE_TURNING_ON); - mEnableThread = new EnableThread(saveSetting); - mEnableThread.start(); + mIsDiscovering = false; + + switchConnectable(false); + updateSdpRecords(); return true; } /** Forcibly restart Bluetooth if it is on */ /* package */ synchronized void restart() { - if (mBluetoothState != BluetoothAdapter.STATE_ON) { + if (getBluetoothStateInternal() != BluetoothAdapter.STATE_ON) { return; } mRestart = true; @@ -494,30 +492,10 @@ public class BluetoothService extends IBluetooth.Stub { } } - private synchronized void setBluetoothState(int state) { - if (state == mBluetoothState) { - return; - } - - if (DBG) Log.d(TAG, "Bluetooth state " + mBluetoothState + " -> " + state); - - Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mBluetoothState); - intent.putExtra(BluetoothAdapter.EXTRA_STATE, state); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - - mBluetoothState = state; - - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } - private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MESSAGE_FINISH_DISABLE: - finishDisable(msg.arg1 != 0); - break; case MESSAGE_UUID_INTENT: String address = (String)msg.obj; if (address != null) { @@ -527,74 +505,24 @@ public class BluetoothService extends IBluetooth.Stub { break; case MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: address = (String)msg.obj; - if (address != null) { + if (address == null) return; + int attempt = mBondState.getAttempt(address); + + // Try only if attemps are in progress and cap it 2 attempts + // The 2 attempts cap is a fail safe if the stack returns + // an incorrect error code for bonding failures and if the pin + // is entered wrongly twice we should abort. + if (attempt > 0 && attempt <= 2) { + mBondState.attempt(address); createBond(address); return; } + if (attempt > 0) mBondState.clearPinAttempts(address); break; } } }; - private EnableThread mEnableThread; - - private class EnableThread extends Thread { - private final boolean mSaveSetting; - public EnableThread(boolean saveSetting) { - mSaveSetting = saveSetting; - } - public void run() { - boolean res = (enableNative() == 0); - if (res) { - int retryCount = 2; - boolean running = false; - while ((retryCount-- > 0) && !running) { - mEventLoop.start(); - // it may take a moment for the other thread to do its - // thing. Check periodically for a while. - int pollCount = 5; - while ((pollCount-- > 0) && !running) { - if (mEventLoop.isEventLoopRunning()) { - running = true; - break; - } - try { - Thread.sleep(100); - } catch (InterruptedException e) {} - } - } - if (!running) { - Log.e(TAG, "bt EnableThread giving up"); - res = false; - disableNative(); - } - } - - if (res) { - if (!setupNativeDataNative()) { - return; - } - if (mSaveSetting) { - persistBluetoothOnSetting(true); - } - - mIsDiscovering = false; - mBondState.readAutoPairingData(); - mBondState.initBondState(); - initProfileState(); - - // This should be the last step of the the enable thread. - // Because this adds SDP records which asynchronously - // broadcasts the Bluetooth On State in updateBluetoothState. - // So we want all internal state setup before this. - updateSdpRecords(); - } else { - setBluetoothState(BluetoothAdapter.STATE_OFF); - } - mEnableThread = null; - } - } - private synchronized void addReservedSdpRecords(final ArrayList<ParcelUuid> uuids) { //Register SDP records. int[] svcIdentifiers = new int[uuids.size()]; @@ -641,38 +569,37 @@ public class BluetoothService extends IBluetooth.Stub { * for adapter comes in with UUID property. * @param uuidsThe uuids of adapter as reported by Bluez. */ - synchronized void updateBluetoothState(String uuids) { - if (mBluetoothState == BluetoothAdapter.STATE_TURNING_ON) { - ParcelUuid[] adapterUuids = convertStringToParcelUuid(uuids); - - if (mAdapterUuids != null && - BluetoothUuid.containsAllUuids(adapterUuids, mAdapterUuids)) { - setBluetoothState(BluetoothAdapter.STATE_ON); - autoConnect(); - String[] propVal = {"Pairable", getProperty("Pairable")}; - mEventLoop.onPropertyChanged(propVal); - - // Log bluetooth on to battery stats. - long ident = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteBluetoothOn(); - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } + /*package*/ synchronized void updateBluetoothState(String uuids) { + ParcelUuid[] adapterUuids = convertStringToParcelUuid(uuids); - if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) { - disable(false); - } - } + if (mAdapterUuids != null && + BluetoothUuid.containsAllUuids(adapterUuids, mAdapterUuids)) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SERVICE_RECORD_LOADED); } } - private void persistBluetoothOnSetting(boolean bluetoothOn) { - long origCallerIdentityToken = Binder.clearCallingIdentity(); - Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON, - bluetoothOn ? 1 : 0); - Binder.restoreCallingIdentity(origCallerIdentityToken); + /** + * This method is called immediately after Bluetooth module is turned on. + * It starts auto-connection and places bluetooth on sign onto the battery + * stats + */ + /*package*/ void runBluetooth() { + mIsDiscovering = false; + mBondState.readAutoPairingData(); + mBondState.initBondState(); + initProfileState(); + + autoConnect(); + + // Log bluetooth on to battery stats. + long ident = Binder.clearCallingIdentity(); + try { + mBatteryStats.noteBluetoothOn(); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } finally { + Binder.restoreCallingIdentity(ident); + } } /*package*/ synchronized boolean attemptAutoPair(String address) { @@ -741,7 +668,6 @@ public class BluetoothService extends IBluetooth.Stub { BluetoothDevice.BOND_NONE, result); return; } - mBondState.attempt(address); } /*package*/ BluetoothDevice getRemoteDevice(String address) { @@ -810,6 +736,26 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean setScanMode(int mode, int duration) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, "Need WRITE_SECURE_SETTINGS permission"); + return setScanMode(mode, duration, true); + } + + /** + * @param on true set the local Bluetooth module to be connectable + * but not dicoverable + * false set the local Bluetooth module to be not connectable + * and not dicoverable + */ + /*package*/ synchronized void switchConnectable(boolean on) { + if (on) { + // 0 is a dummy value, does not apply for SCAN_MODE_CONNECTABLE + setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, 0, false); + } else { + // 0 is a dummy value, does not apply for SCAN_MODE_NONE + setScanMode(BluetoothAdapter.SCAN_MODE_NONE, 0, false); + } + } + + private synchronized boolean setScanMode(int mode, int duration, boolean allowOnlyInOnState) { boolean pairable; boolean discoverable; @@ -831,9 +777,15 @@ public class BluetoothService extends IBluetooth.Stub { Log.w(TAG, "Requested invalid scan mode " + mode); return false; } - setPropertyBoolean("Pairable", pairable); - setPropertyBoolean("Discoverable", discoverable); + if (allowOnlyInOnState) { + setPropertyBoolean("Pairable", pairable); + setPropertyBoolean("Discoverable", discoverable); + } else { + // allowed to set the property through native layer directly + setAdapterPropertyBooleanNative("Pairable", pairable ? 1 : 0); + setAdapterPropertyBooleanNative("Discoverable", discoverable ? 1 : 0); + } return true; } @@ -1561,14 +1513,10 @@ public class BluetoothService extends IBluetooth.Stub { ContentResolver resolver = context.getContentResolver(); // Query the airplane mode from Settings.System just to make sure that // some random app is not sending this intent and disabling bluetooth - boolean enabled = !isAirplaneModeOn(); - // If bluetooth is currently expected to be on, then enable or disable bluetooth - if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) { - if (enabled) { - enable(false); - } else { - disable(false); - } + if (isAirplaneModeOn()) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.AIRPLANE_MODE_ON); + } else { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.AIRPLANE_MODE_OFF); } } else if (Intent.ACTION_DOCK_EVENT.equals(action)) { int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, @@ -1642,8 +1590,7 @@ public class BluetoothService extends IBluetooth.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - dumpBluetoothState(pw); - if (mBluetoothState != BluetoothAdapter.STATE_ON) { + if (getBluetoothStateInternal() != BluetoothAdapter.STATE_ON) { return; } @@ -1832,25 +1779,6 @@ public class BluetoothService extends IBluetooth.Stub { } } - private void dumpBluetoothState(PrintWriter pw) { - switch(mBluetoothState) { - case BluetoothAdapter.STATE_OFF: - pw.println("Bluetooth OFF\n"); - break; - case BluetoothAdapter.STATE_TURNING_ON: - pw.println("Bluetooth TURNING ON\n"); - break; - case BluetoothAdapter.STATE_TURNING_OFF: - pw.println("Bluetooth TURNING OFF\n"); - break; - case BluetoothAdapter.STATE_ON: - pw.println("Bluetooth ON\n"); - break; - default: - pw.println("Bluetooth UNKNOWN STATE " + mBluetoothState); - } - } - private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = new BluetoothProfile.ServiceListener() { public void onServiceConnected(int profile, BluetoothProfile proxy) { @@ -2381,7 +2309,7 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized void sendConnectionStateChange(BluetoothDevice device, int state, int prevState) { // Since this is a binder call check if Bluetooth is on still - if (mBluetoothState == BluetoothAdapter.STATE_OFF) return; + if (getBluetoothStateInternal() == BluetoothAdapter.STATE_OFF) return; if (updateCountersAndCheckForConnectionStateChange(state, prevState)) { if (!validateProfileConnectionState(state) || @@ -2397,6 +2325,10 @@ public class BluetoothService extends IBluetooth.Stub { mAdapterConnectionState = state; + if (state == BluetoothProfile.STATE_DISCONNECTED) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.ALL_DEVICES_DISCONNECTED); + } + Intent intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, @@ -2590,8 +2522,8 @@ public class BluetoothService extends IBluetooth.Stub { /*package*/ native String getAdapterPathNative(); private native int isEnabledNative(); - private native int enableNative(); - private native int disableNative(); + /*package*/ native int enableNative(); + /*package*/ native int disableNative(); /*package*/ native Object[] getAdapterPropertiesNative(); private native Object[] getDevicePropertiesNative(String objectPath); diff --git a/core/java/android/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java new file mode 100644 index 000000000000..270f51208ce0 --- /dev/null +++ b/core/java/android/service/textservice/SpellCheckerService.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2011 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.service.textservice; + +import com.android.internal.textservice.ISpellCheckerService; +import com.android.internal.textservice.ISpellCheckerSession; +import com.android.internal.textservice.ISpellCheckerSessionListener; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.textservice.SuggestionsInfo; +import android.view.textservice.TextInfo; + +import java.lang.ref.WeakReference; + +/** + * SpellCheckerService provides an abstract base class for a spell checker. + * This class combines a service to the system with the spell checker service interface that + * spell checker must implement. + */ +public abstract class SpellCheckerService extends Service { + private static final String TAG = SpellCheckerService.class.getSimpleName(); + public static final String SERVICE_INTERFACE = + "android.service.textservice.SpellCheckerService"; + + private final SpellCheckerServiceBinder mBinder = new SpellCheckerServiceBinder(this); + + /** + * Get suggestions for specified text in TextInfo. + * This function will run on the incoming IPC thread. So, this is not called on the main thread, + * but will be called in series on another thread. + * @param textInfo the text metadata + * @param suggestionsLimit the number of limit of suggestions returned + * @param locale the locale for getting suggestions + * @return SuggestionInfo which contains suggestions for textInfo + */ + public abstract SuggestionsInfo getSuggestions( + TextInfo textInfo, int suggestionsLimit, String locale); + + /** + * A batch process of onGetSuggestions. + * This function will run on the incoming IPC thread. So, this is not called on the main thread, + * but will be called in series on another thread. + * @param textInfos an array of the text metadata + * @param locale the locale for getting suggestions + * @param suggestionsLimit the number of limit of suggestions returned + * @param sequentialWords true if textInfos can be treated as sequential words. + * @return an array of SuggestionInfo of onGetSuggestions + */ + public SuggestionsInfo[] getSuggestionsMultiple( + TextInfo[] textInfos, String locale, int suggestionsLimit, boolean sequentialWords) { + final int length = textInfos.length; + final SuggestionsInfo[] retval = new SuggestionsInfo[length]; + for (int i = 0; i < length; ++i) { + retval[i] = getSuggestions(textInfos[i], suggestionsLimit, locale); + retval[i].setCookieAndSequence(textInfos[i].getCookie(), textInfos[i].getSequence()); + } + return retval; + } + + /** + * Request to abort all tasks executed in SpellChecker. + * This function will run on the incoming IPC thread. So, this is not called on the main thread, + * but will be called in series on another thread. + */ + public void cancel() {} + + /** + * Implement to return the implementation of the internal spell checker + * service interface. Subclasses should not override. + */ + @Override + public final IBinder onBind(final Intent intent) { + return mBinder; + } + + private static class SpellCheckerSessionImpl extends ISpellCheckerSession.Stub { + private final WeakReference<SpellCheckerService> mInternalServiceRef; + private final String mLocale; + private final ISpellCheckerSessionListener mListener; + + public SpellCheckerSessionImpl( + SpellCheckerService service, String locale, ISpellCheckerSessionListener listener) { + mInternalServiceRef = new WeakReference<SpellCheckerService>(service); + mLocale = locale; + mListener = listener; + } + + @Override + public void getSuggestionsMultiple( + TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) { + final SpellCheckerService service = mInternalServiceRef.get(); + if (service == null) return; + try { + mListener.onGetSuggestions( + service.getSuggestionsMultiple(textInfos, mLocale, + suggestionsLimit, sequentialWords)); + } catch (RemoteException e) { + } + } + + @Override + public void cancel() { + final SpellCheckerService service = mInternalServiceRef.get(); + if (service == null) return; + service.cancel(); + } + } + + private static class SpellCheckerServiceBinder extends ISpellCheckerService.Stub { + private final WeakReference<SpellCheckerService> mInternalServiceRef; + + public SpellCheckerServiceBinder(SpellCheckerService service) { + mInternalServiceRef = new WeakReference<SpellCheckerService>(service); + } + + @Override + public ISpellCheckerSession getISpellCheckerSession( + String locale, ISpellCheckerSessionListener listener) { + final SpellCheckerService service = mInternalServiceRef.get(); + if (service == null) return null; + return new SpellCheckerSessionImpl(service, locale, listener); + } + } +} diff --git a/core/java/android/service/textservice/SpellCheckerSession.java b/core/java/android/service/textservice/SpellCheckerSession.java new file mode 100644 index 000000000000..400454da40f9 --- /dev/null +++ b/core/java/android/service/textservice/SpellCheckerSession.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2011 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.service.textservice; + +import com.android.internal.textservice.ISpellCheckerSession; +import com.android.internal.textservice.ISpellCheckerSessionListener; +import com.android.internal.textservice.ITextServicesManager; +import com.android.internal.textservice.ITextServicesSessionListener; + +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +import android.view.textservice.SpellCheckerInfo; +import android.view.textservice.SuggestionsInfo; +import android.view.textservice.TextInfo; + +import java.util.LinkedList; +import java.util.Locale; +import java.util.Queue; + +/** + * The SpellCheckerSession interface provides the per client functionality of SpellCheckerService. + */ +public class SpellCheckerSession { + private static final String TAG = SpellCheckerSession.class.getSimpleName(); + private static final boolean DBG = false; + + private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1; + + private final InternalListener mInternalListener; + private final ITextServicesManager mTextServicesManager; + private final SpellCheckerInfo mSpellCheckerInfo; + private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl; + + private boolean mIsUsed; + private SpellCheckerSessionListener mSpellCheckerSessionListener; + + /** Handler that will execute the main tasks */ + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ON_GET_SUGGESTION_MULTIPLE: + handleOnGetSuggestionsMultiple((SuggestionsInfo[]) msg.obj); + break; + } + } + }; + + /** + * Constructor + * @hide + */ + public SpellCheckerSession( + SpellCheckerInfo info, ITextServicesManager tsm, SpellCheckerSessionListener listener) { + if (info == null || listener == null || tsm == null) { + throw new NullPointerException(); + } + mSpellCheckerInfo = info; + mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler); + mInternalListener = new InternalListener(); + mTextServicesManager = tsm; + mIsUsed = true; + mSpellCheckerSessionListener = listener; + } + + /** + * @return true if the connection to a text service of this session is disconnected and not + * alive. + */ + public boolean isSessionDisconnected() { + return mSpellCheckerSessionListenerImpl.isDisconnected(); + } + + /** + * Get the spell checker service info this spell checker session has. + * @return SpellCheckerInfo for the specified locale. + */ + public SpellCheckerInfo getSpellChecker() { + return mSpellCheckerInfo; + } + + /** + * Finish this session and allow TextServicesManagerService to disconnect the bound spell + * checker. + */ + public void close() { + mIsUsed = false; + try { + mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl); + } catch (RemoteException e) { + // do nothing + } + } + + /** + * Get candidate strings for a substring of the specified text. + * @param textInfo text metadata for a spell checker + * @param suggestionsLimit the number of limit of suggestions returned + */ + public void getSuggestions(TextInfo textInfo, int suggestionsLimit) { + getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false); + } + + /** + * A batch process of getSuggestions + * @param textInfos an array of text metadata for a spell checker + * @param suggestionsLimit the number of limit of suggestions returned + * @param sequentialWords true if textInfos can be treated as sequential words. + */ + public void getSuggestions( + TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) { + // TODO: Handle multiple words suggestions by using WordBreakIterator + mSpellCheckerSessionListenerImpl.getSuggestionsMultiple( + textInfos, suggestionsLimit, sequentialWords); + } + + private void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionInfos) { + mSpellCheckerSessionListener.onGetSuggestions(suggestionInfos); + } + + private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub { + private static final int TASK_CANCEL = 1; + private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2; + private final Queue<SpellCheckerParams> mPendingTasks = + new LinkedList<SpellCheckerParams>(); + private final Handler mHandler; + + private boolean mOpened; + private ISpellCheckerSession mISpellCheckerSession; + + public SpellCheckerSessionListenerImpl(Handler handler) { + mOpened = false; + mHandler = handler; + } + + private static class SpellCheckerParams { + public final int mWhat; + public final TextInfo[] mTextInfos; + public final int mSuggestionsLimit; + public final boolean mSequentialWords; + public SpellCheckerParams(int what, TextInfo[] textInfos, int suggestionsLimit, + boolean sequentialWords) { + mWhat = what; + mTextInfos = textInfos; + mSuggestionsLimit = suggestionsLimit; + mSequentialWords = sequentialWords; + } + } + + private void processTask(SpellCheckerParams scp) { + switch (scp.mWhat) { + case TASK_CANCEL: + processCancel(); + break; + case TASK_GET_SUGGESTIONS_MULTIPLE: + processGetSuggestionsMultiple(scp); + break; + } + } + + public synchronized void onServiceConnected(ISpellCheckerSession session) { + mISpellCheckerSession = session; + mOpened = true; + if (DBG) + Log.d(TAG, "onServiceConnected - Success"); + while (!mPendingTasks.isEmpty()) { + processTask(mPendingTasks.poll()); + } + } + + public void getSuggestionsMultiple( + TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) { + processOrEnqueueTask( + new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE, textInfos, + suggestionsLimit, sequentialWords)); + } + + public boolean isDisconnected() { + return mOpened && mISpellCheckerSession == null; + } + + public boolean checkOpenConnection() { + if (mISpellCheckerSession != null) { + return true; + } + Log.e(TAG, "not connected to the spellchecker service."); + return false; + } + + private void processOrEnqueueTask(SpellCheckerParams scp) { + if (mISpellCheckerSession == null) { + mPendingTasks.offer(scp); + } else { + processTask(scp); + } + } + + private void processCancel() { + if (!checkOpenConnection()) { + return; + } + try { + mISpellCheckerSession.cancel(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to cancel " + e); + } + } + + private void processGetSuggestionsMultiple(SpellCheckerParams scp) { + if (!checkOpenConnection()) { + return; + } + try { + mISpellCheckerSession.getSuggestionsMultiple( + scp.mTextInfos, scp.mSuggestionsLimit, scp.mSequentialWords); + } catch (RemoteException e) { + Log.e(TAG, "Failed to get suggestions " + e); + } + } + + @Override + public void onGetSuggestions(SuggestionsInfo[] results) { + mHandler.sendMessage(Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE, results)); + } + } + + /** + * Callback for getting results from text services + */ + public interface SpellCheckerSessionListener { + /** + * Callback for "getSuggestions" + * @param results an array of results of getSuggestions + */ + public void onGetSuggestions(SuggestionsInfo[] results); + } + + private class InternalListener extends ITextServicesSessionListener.Stub { + @Override + public void onServiceConnected(ISpellCheckerSession session) { + mSpellCheckerSessionListenerImpl.onServiceConnected(session); + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (mIsUsed) { + Log.e(TAG, "SpellCheckerSession was not finished properly." + + "You should call finishShession() when you finished to use a spell checker."); + close(); + } + } + + /** + * @hide + */ + public ITextServicesSessionListener getTextServicesSessionListener() { + return mInternalListener; + } + + /** + * @hide + */ + public ISpellCheckerSessionListener getSpellCheckerSessionListener() { + return mSpellCheckerSessionListenerImpl; + } +} diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java index f1393721cb41..f2a86c969368 100644 --- a/core/java/android/util/JsonReader.java +++ b/core/java/android/util/JsonReader.java @@ -740,8 +740,8 @@ public final class JsonReader implements Closeable { limit += total; // if this is the first read, consume an optional byte order mark (BOM) if it exists - if (bufferStartLine == 1 && bufferStartColumn == 1 - && limit > 0 && buffer[0] == '\ufeff') { + if (bufferStartLine == 1 && bufferStartColumn == 1 + && limit > 0 && buffer[0] == '\ufeff') { pos++; bufferStartColumn--; } @@ -852,7 +852,7 @@ public final class JsonReader implements Closeable { private boolean skipTo(String toFind) throws IOException { outer: - for (; pos + toFind.length() < limit || fillBuffer(toFind.length()); pos++) { + for (; pos + toFind.length() <= limit || fillBuffer(toFind.length()); pos++) { for (int c = 0; c < toFind.length(); c++) { if (buffer[pos + c] != toFind.charAt(c)) { continue outer; diff --git a/core/java/android/view/CollapsibleActionView.java b/core/java/android/view/CollapsibleActionView.java new file mode 100644 index 000000000000..ab2365eb744c --- /dev/null +++ b/core/java/android/view/CollapsibleActionView.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011 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 android.view.MenuItem.OnActionExpandListener; + +/** + * When a {@link View} implements this interface it will receive callbacks + * when expanded or collapsed as an action view alongside the optional, + * app-specified callbacks to {@link OnActionExpandListener}. + * + * <p>See {@link MenuItem} for more information about action views. + * See {@link android.app.ActionBar} for more information about the action bar. + */ +public interface CollapsibleActionView { + /** + * Called when this view is expanded as an action view. + * See {@link MenuItem#expandActionView()}. + */ + public void onActionViewExpanded(); + + /** + * Called when this view is collapsed as an action view. + * See {@link MenuItem#collapseActionView()}. + */ + public void onActionViewCollapsed(); +} diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java index 4484d59a3ce4..f4c0249ab5a9 100644 --- a/core/java/android/view/DisplayList.java +++ b/core/java/android/view/DisplayList.java @@ -41,15 +41,6 @@ public abstract class DisplayList { abstract void end(); /** - * Indicates whether this display list can be replayed or not. - * - * @return True if the display list can be replayed, false otherwise. - * - * @see android.view.HardwareCanvas#drawDisplayList(DisplayList) - */ - abstract boolean isReady(); - - /** * Invalidates the display list, indicating that it should be repopulated * with new drawing commands prior to being used again. Calling this method * causes calls to {@link #isValid()} to return <code>false</code>. diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 80244bbe9244..d22fa6e501ec 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -51,6 +51,7 @@ class GLES20Canvas extends HardwareCanvas { // The native renderer will be destroyed when this object dies. // DO NOT overwrite this reference once it is set. + @SuppressWarnings("unused") private CanvasFinalizer mFinalizer; private int mWidth; @@ -97,12 +98,8 @@ class GLES20Canvas extends HardwareCanvas { protected GLES20Canvas(boolean record, boolean translucent) { mOpaque = !translucent; - setupRenderer(record); - } - - protected void setupRenderer(boolean record) { if (record) { - mRenderer = nGetDisplayListRenderer(mRenderer); + mRenderer = nCreateDisplayListRenderer(); } else { mRenderer = nCreateRenderer(); } @@ -114,43 +111,31 @@ class GLES20Canvas extends HardwareCanvas { if (mRenderer == 0) { throw new IllegalStateException("Could not create GLES20Canvas renderer"); } else { - mFinalizer = CanvasFinalizer.getFinalizer(mFinalizer, mRenderer); + mFinalizer = new CanvasFinalizer(mRenderer); } } + protected void resetDisplayListRenderer() { + nResetDisplayListRenderer(mRenderer); + } + private static native int nCreateRenderer(); private static native int nCreateLayerRenderer(int layer); - private static native int nGetDisplayListRenderer(int renderer); + private static native int nCreateDisplayListRenderer(); + private static native void nResetDisplayListRenderer(int renderer); private static native void nDestroyRenderer(int renderer); - private static class CanvasFinalizer { - int mRenderer; - - // Factory method returns new instance if old one is null, or old instance - // otherwise, destroying native renderer along the way as necessary - static CanvasFinalizer getFinalizer(CanvasFinalizer oldFinalizer, int renderer) { - if (oldFinalizer == null) { - return new CanvasFinalizer(renderer); - } - oldFinalizer.replaceNativeObject(renderer); - return oldFinalizer; - } + private static final class CanvasFinalizer { + private final int mRenderer; - private CanvasFinalizer(int renderer) { + public CanvasFinalizer(int renderer) { mRenderer = renderer; } - private void replaceNativeObject(int newRenderer) { - if (mRenderer != 0 && newRenderer != mRenderer) { - nDestroyRenderer(mRenderer); - } - mRenderer = newRenderer; - } - @Override protected void finalize() throws Throwable { try { - replaceNativeObject(0); + nDestroyRenderer(mRenderer); } finally { super.finalize(); } @@ -322,11 +307,11 @@ class GLES20Canvas extends HardwareCanvas { // Display list /////////////////////////////////////////////////////////////////////////// - int getDisplayList() { - return nGetDisplayList(mRenderer); + int getDisplayList(int displayList) { + return nGetDisplayList(mRenderer, displayList); } - private static native int nGetDisplayList(int renderer); + private static native int nGetDisplayList(int renderer, int displayList); static void destroyDisplayList(int displayList) { nDestroyDisplayList(displayList); @@ -337,7 +322,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public boolean drawDisplayList(DisplayList displayList, int width, int height, Rect dirty) { return nDrawDisplayList(mRenderer, - ((GLES20DisplayList) displayList).mNativeDisplayList, width, height, dirty); + ((GLES20DisplayList) displayList).getNativeDisplayList(), width, height, dirty); } private static native boolean nDrawDisplayList(int renderer, int displayList, @@ -345,7 +330,7 @@ class GLES20Canvas extends HardwareCanvas { @Override void outputDisplayList(DisplayList displayList) { - nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).mNativeDisplayList); + nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList()); } private static native void nOutputDisplayList(int renderer, int displayList); diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index aeff31f8a193..9e649cea63dc 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -16,52 +16,50 @@ package android.view; -import java.lang.ref.WeakReference; +import android.graphics.Bitmap; + +import java.util.ArrayList; /** * An implementation of display list for OpenGL ES 2.0. */ class GLES20DisplayList extends DisplayList { - private GLES20Canvas mCanvas; - - private boolean mStarted = false; - private boolean mRecorded = false; - private boolean mValid = false; + // These lists ensure that any Bitmaps recorded by a DisplayList are kept alive as long + // as the DisplayList is alive. The Bitmaps are populated by the GLES20RecordingCanvas. + final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(5); - int mNativeDisplayList; - WeakReference<View> hostView; + private GLES20RecordingCanvas mCanvas; + private boolean mValid; // The native display list will be destroyed when this object dies. // DO NOT overwrite this reference once it is set. - @SuppressWarnings("unused") private DisplayListFinalizer mFinalizer; - public GLES20DisplayList(View view) { - hostView = new WeakReference<View>(view); + int getNativeDisplayList() { + if (!mValid || mFinalizer == null) { + throw new IllegalStateException("The display list is not valid."); + } + return mFinalizer.mNativeDisplayList; } @Override HardwareCanvas start() { - if (mStarted) { - throw new IllegalStateException("Recording has already started"); - } - if (mCanvas != null) { - ((GLES20RecordingCanvas) mCanvas).reset(); - } else { - mCanvas = new GLES20RecordingCanvas(true); + throw new IllegalStateException("Recording has already started"); } - mStarted = true; - mRecorded = false; - mValid = true; + mValid = false; + mCanvas = GLES20RecordingCanvas.obtain(this); + mCanvas.start(); return mCanvas; } @Override void invalidate() { - mStarted = false; - mRecorded = false; + if (mCanvas != null) { + mCanvas.recycle(); + mCanvas = null; + } mValid = false; } @@ -73,48 +71,28 @@ class GLES20DisplayList extends DisplayList { @Override void end() { if (mCanvas != null) { - mStarted = false; - mRecorded = true; - - mNativeDisplayList = mCanvas.getDisplayList(); - mFinalizer = DisplayListFinalizer.getFinalizer(mFinalizer, mNativeDisplayList); + if (mFinalizer != null) { + mCanvas.end(mFinalizer.mNativeDisplayList); + } else { + mFinalizer = new DisplayListFinalizer(mCanvas.end(0)); + } + mCanvas.recycle(); + mCanvas = null; + mValid = true; } } - @Override - boolean isReady() { - return !mStarted && mRecorded; - } - private static class DisplayListFinalizer { - int mNativeDisplayList; - - // Factory method returns new instance if old one is null, or old instance - // otherwise, destroying native display list along the way as necessary - static DisplayListFinalizer getFinalizer(DisplayListFinalizer oldFinalizer, - int nativeDisplayList) { - if (oldFinalizer == null) { - return new DisplayListFinalizer(nativeDisplayList); - } - oldFinalizer.replaceNativeObject(nativeDisplayList); - return oldFinalizer; - } + final int mNativeDisplayList; - private DisplayListFinalizer(int nativeDisplayList) { + public DisplayListFinalizer(int nativeDisplayList) { mNativeDisplayList = nativeDisplayList; } - private void replaceNativeObject(int newNativeDisplayList) { - if (mNativeDisplayList != 0 && mNativeDisplayList != newNativeDisplayList) { - GLES20Canvas.destroyDisplayList(mNativeDisplayList); - } - mNativeDisplayList = newNativeDisplayList; - } - @Override protected void finalize() throws Throwable { try { - replaceNativeObject(0); + GLES20Canvas.destroyDisplayList(mNativeDisplayList); } finally { super.finalize(); } diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index ec94fe70ae91..078222be6fe9 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -24,8 +24,10 @@ import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; - -import java.util.ArrayList; +import android.util.Pool; +import android.util.Poolable; +import android.util.PoolableManager; +import android.util.Pools; /** * An implementation of a GL canvas that records drawing operations. @@ -33,62 +35,94 @@ import java.util.ArrayList; * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while * the DisplayList is still holding a native reference to the memory. */ -class GLES20RecordingCanvas extends GLES20Canvas { - // These lists ensure that any Bitmaps recorded by a DisplayList are kept alive as long - // as the DisplayList is alive. - @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) - private final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(5); +class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20RecordingCanvas> { + // The recording canvas pool should be large enough to handle a deeply nested + // view hierarchy because display lists are generated recursively. + private static final int POOL_LIMIT = 50; + + private static final Pool<GLES20RecordingCanvas> sPool = Pools.synchronizedPool( + Pools.finitePool(new PoolableManager<GLES20RecordingCanvas>() { + public GLES20RecordingCanvas newInstance() { + return new GLES20RecordingCanvas(); + } + @Override + public void onAcquired(GLES20RecordingCanvas element) { + } + @Override + public void onReleased(GLES20RecordingCanvas element) { + } + }, POOL_LIMIT)); + + private GLES20RecordingCanvas mNextPoolable; + private boolean mIsPooled; + + private GLES20DisplayList mDisplayList; - GLES20RecordingCanvas(boolean translucent) { - super(true, translucent); + private GLES20RecordingCanvas() { + super(true /*record*/, true /*translucent*/); + } + + static GLES20RecordingCanvas obtain(GLES20DisplayList displayList) { + GLES20RecordingCanvas canvas = sPool.acquire(); + canvas.mDisplayList = displayList; + return canvas; + } + + void recycle() { + mDisplayList = null; + resetDisplayListRenderer(); + sPool.release(this); + } + + void start() { + mDisplayList.mBitmaps.clear(); + } + + int end(int nativeDisplayList) { + return getDisplayList(nativeDisplayList); } private void recordShaderBitmap(Paint paint) { if (paint != null) { final Shader shader = paint.getShader(); if (shader instanceof BitmapShader) { - mBitmaps.add(((BitmapShader) shader).mBitmap); + mDisplayList.mBitmaps.add(((BitmapShader) shader).mBitmap); } } } - void reset() { - mBitmaps.clear(); - setupRenderer(true); - } - @Override public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) { super.drawPatch(bitmap, chunks, dst, paint); - mBitmaps.add(bitmap); + mDisplayList.mBitmaps.add(bitmap); // Shaders in the Paint are ignored when drawing a Bitmap } @Override public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { super.drawBitmap(bitmap, left, top, paint); - mBitmaps.add(bitmap); + mDisplayList.mBitmaps.add(bitmap); // Shaders in the Paint are ignored when drawing a Bitmap } @Override public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { super.drawBitmap(bitmap, matrix, paint); - mBitmaps.add(bitmap); + mDisplayList.mBitmaps.add(bitmap); // Shaders in the Paint are ignored when drawing a Bitmap } @Override public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { super.drawBitmap(bitmap, src, dst, paint); - mBitmaps.add(bitmap); + mDisplayList.mBitmaps.add(bitmap); // Shaders in the Paint are ignored when drawing a Bitmap } @Override public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { super.drawBitmap(bitmap, src, dst, paint); - mBitmaps.add(bitmap); + mDisplayList.mBitmaps.add(bitmap); // Shaders in the Paint are ignored when drawing a Bitmap } @@ -111,7 +145,7 @@ class GLES20RecordingCanvas extends GLES20Canvas { int vertOffset, int[] colors, int colorOffset, Paint paint) { super.drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint); - mBitmaps.add(bitmap); + mDisplayList.mBitmaps.add(bitmap); // Shaders in the Paint are ignored when drawing a Bitmap } @@ -270,4 +304,24 @@ class GLES20RecordingCanvas extends GLES20Canvas { colorOffset, indices, indexOffset, indexCount, paint); recordShaderBitmap(paint); } + + @Override + public GLES20RecordingCanvas getNextPoolable() { + return mNextPoolable; + } + + @Override + public void setNextPoolable(GLES20RecordingCanvas element) { + mNextPoolable = element; + } + + @Override + public boolean isPooled() { + return mIsPooled; + } + + @Override + public void setPooled(boolean isPooled) { + mIsPooled = isPooled; + } } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index b865b5007200..503b54bd437d 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -189,7 +189,7 @@ public abstract class HardwareRenderer { * * @return A new display list. */ - abstract DisplayList createDisplayList(View v); + abstract DisplayList createDisplayList(); /** * Creates a new hardware layer. A hardware layer built by calling this @@ -852,8 +852,8 @@ public abstract class HardwareRenderer { } @Override - DisplayList createDisplayList(View v) { - return new GLES20DisplayList(v); + DisplayList createDisplayList() { + return new GLES20DisplayList(); } @Override diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index c913bb30cd90..836867b80fb1 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -161,6 +161,9 @@ public class Surface implements Parcelable { */ public static final int FLAGS_ORIENTATION_ANIMATION_DISABLE = 0x000000001; + // The mSurfaceControl will only be present for Surfaces used by the window + // server or system processes. When this class is parceled we defer to the + // mSurfaceControl to do the parceling. Otherwise we parcel the mNativeSurface. @SuppressWarnings("unused") private int mSurfaceControl; @SuppressWarnings("unused") @@ -202,6 +205,19 @@ public class Surface implements Parcelable { native private static void nativeClassInit(); static { nativeClassInit(); } + /** + * Create Surface from a SurfaceTexture. + * + * @param surfaceTexture The {@link SurfaceTexture} that is updated by this Surface. + * @hide + */ + public Surface(SurfaceTexture surfaceTexture) { + if (DEBUG_RELEASE) { + mCreationStack = new Exception(); + } + mCanvas = new CompatibleCanvas(); + initFromSurfaceTexture(surfaceTexture); + } /** * create a surface @@ -505,5 +521,7 @@ public class Surface implements Parcelable { private native void init(Parcel source); + private native void initFromSurfaceTexture(SurfaceTexture surfaceTexture); + private native int getIdentity(); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0108ecf17b6d..dbb19e43aa3d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4280,6 +4280,36 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } /** + * Set whether or not this view should account for system screen decorations + * such as the status bar and inset its content. This allows this view to be + * positioned in absolute screen coordinates and remain visible to the user. + * + * <p>This should only be used by top-level window decor views. + * + * @param fitSystemWindows true to inset content for system screen decorations, false for + * default behavior. + * + * @attr ref android.R.styleable#View_fitsSystemWindows + */ + public void setFitsSystemWindows(boolean fitSystemWindows) { + setFlags(fitSystemWindows ? FITS_SYSTEM_WINDOWS : 0, FITS_SYSTEM_WINDOWS); + } + + /** + * Check for the FITS_SYSTEM_WINDOWS flag. If this method returns true, this view + * will account for system screen decorations such as the status bar and inset its + * content. This allows the view to be positioned in absolute screen coordinates + * and remain visible to the user. + * + * @return true if this view will adjust its content bounds for system screen decorations. + * + * @attr ref android.R.styleable#View_fitsSystemWindows + */ + public boolean fitsSystemWindows() { + return (mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS; + } + + /** * Returns the visibility status for this view. * * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. @@ -9099,7 +9129,10 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED; } - private void resolvePadding() { + /** + * @hide + */ + protected void resolvePadding() { // If the user specified the absolute padding (either with android:padding or // android:paddingLeft/Top/Right/Bottom), use this padding, otherwise // use the default padding or the padding from the background drawable @@ -9830,7 +9863,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit // we copy in child display lists into ours in drawChild() mRecreateDisplayList = true; if (mDisplayList == null) { - mDisplayList = mAttachInfo.mHardwareRenderer.createDisplayList(this); + mDisplayList = mAttachInfo.mHardwareRenderer.createDisplayList(); // If we're creating a new display list, make sure our parent gets invalidated // since they will need to recreate their display list to account for this // new child display list. @@ -12025,12 +12058,13 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit mPrivateFlags |= FORCE_LAYOUT; mPrivateFlags |= INVALIDATED; - if (mLayoutParams != null && mParent != null) { - mLayoutParams.resolveWithDirection(getResolvedLayoutDirection()); - } - - if (mParent != null && !mParent.isLayoutRequested()) { - mParent.requestLayout(); + if (mParent != null) { + if (mLayoutParams != null) { + mLayoutParams.resolveWithDirection(getResolvedLayoutDirection()); + } + if (!mParent.isLayoutRequested()) { + mParent.requestLayout(); + } } } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 3798c9d0a0eb..4acf48c6b316 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -25,6 +25,7 @@ import android.os.Debug; import android.os.Environment; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SystemClock; import android.util.DisplayMetrics; @@ -36,7 +37,7 @@ import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; -import java.io.FileNotFoundException; +import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; @@ -50,6 +51,9 @@ import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; @@ -426,22 +430,22 @@ public class ViewDebug { * and obtain the traces. Both methods must be invoked on the * same thread. * - * @param traceFile The path where to write the looper traces - * - * @see #stopLooperProfiling() + * @hide */ - public static void startLooperProfiling(File traceFile) { + public static void startLooperProfiling(String path, FileDescriptor fileDescriptor) { if (sLooperProfilerStorage.get() == null) { - LooperProfiler profiler = new LooperProfiler(traceFile); + LooperProfiler profiler = new LooperProfiler(path, fileDescriptor); sLooperProfilerStorage.set(profiler); Looper.myLooper().setMessageLogging(profiler); } - } + } /** * Stops profiling the looper associated with the current thread. * - * @see #startLooperProfiling(java.io.File) + * @see #startLooperProfiling(String, java.io.FileDescriptor) + * + * @hide */ public static void stopLooperProfiling() { LooperProfiler profiler = sLooperProfilerStorage.get(); @@ -453,21 +457,33 @@ public class ViewDebug { } private static class LooperProfiler implements Looper.Profiler, Printer { - private static final int LOOPER_PROFILER_VERSION = 1; - private static final String LOG_TAG = "LooperProfiler"; + private static final int TRACE_VERSION_NUMBER = 3; + private static final int ACTION_EXIT_METHOD = 0x1; + private static final int HEADER_SIZE = 32; + private static final String HEADER_MAGIC = "SLOW"; + private static final short HEADER_RECORD_SIZE = (short) 14; + private final long mTraceWallStart; private final long mTraceThreadStart; private final ArrayList<Entry> mTraces = new ArrayList<Entry>(512); - private final File mTraceFile; - private final HashMap<String, Short> mTraceNames = new HashMap<String, Short>(32); - private short mTraceId = 0; + private final HashMap<String, Integer> mTraceNames = new HashMap<String, Integer>(32); + private int mTraceId = 0; - LooperProfiler(File traceFile) { - mTraceFile = traceFile; + private final String mPath; + private ParcelFileDescriptor mFileDescriptor; + + LooperProfiler(String path, FileDescriptor fileDescriptor) { + mPath = path; + try { + mFileDescriptor = ParcelFileDescriptor.dup(fileDescriptor); + } catch (IOException e) { + Log.e(LOG_TAG, "Could not write trace file " + mPath, e); + throw new RuntimeException(e); + } mTraceWallStart = SystemClock.currentTimeMicro(); mTraceThreadStart = SystemClock.currentThreadTimeMicro(); } @@ -490,11 +506,11 @@ public class ViewDebug { mTraces.add(entry); } - private short getTraceId(Message message) { + private int getTraceId(Message message) { String name = message.getTarget().getMessageName(message); - Short traceId = mTraceNames.get(name); + Integer traceId = mTraceNames.get(name); if (traceId == null) { - traceId = mTraceId++; + traceId = mTraceId++ << 4; mTraceNames.put(name, traceId); } return traceId; @@ -507,62 +523,135 @@ public class ViewDebug { public void run() { saveTraces(); } - }, "LooperProfiler[" + mTraceFile + "]").start(); + }, "LooperProfiler[" + mPath + "]").start(); } private void saveTraces() { - FileOutputStream fos; - try { - fos = new FileOutputStream(mTraceFile); - } catch (FileNotFoundException e) { - Log.e(LOG_TAG, "Could not open trace file: " + mTraceFile); - return; - } - + FileOutputStream fos = new FileOutputStream(mFileDescriptor.getFileDescriptor()); DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos)); try { - out.writeInt(LOOPER_PROFILER_VERSION); - out.writeLong(mTraceWallStart); - out.writeLong(mTraceThreadStart); + writeHeader(out, mTraceWallStart, mTraceNames, mTraces); + out.flush(); - out.writeInt(mTraceNames.size()); - for (Map.Entry<String, Short> entry : mTraceNames.entrySet()) { - saveTraceName(entry.getKey(), entry.getValue(), out); - } + writeTraces(fos, out.size(), mTraceWallStart, mTraceThreadStart, mTraces); - out.writeInt(mTraces.size()); - for (Entry entry : mTraces) { - saveTrace(entry, out); - } - - Log.d(LOG_TAG, "Looper traces ready: " + mTraceFile); + Log.d(LOG_TAG, "Looper traces ready: " + mPath); } catch (IOException e) { - Log.e(LOG_TAG, "Could not write trace file: ", e); + Log.e(LOG_TAG, "Could not write trace file " + mPath, e); } finally { try { out.close(); } catch (IOException e) { - // Ignore + Log.e(LOG_TAG, "Could not write trace file " + mPath, e); + } + try { + mFileDescriptor.close(); + } catch (IOException e) { + Log.e(LOG_TAG, "Could not write trace file " + mPath, e); } } } - - private void saveTraceName(String name, short id, DataOutputStream out) throws IOException { - out.writeShort(id); - out.writeUTF(name); + + private static void writeTraces(FileOutputStream out, long offset, long wallStart, + long threadStart, ArrayList<Entry> entries) throws IOException { + + FileChannel channel = out.getChannel(); + + // Header + ByteBuffer buffer = ByteBuffer.allocateDirect(HEADER_SIZE); + buffer.put(HEADER_MAGIC.getBytes()); + buffer = buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putShort((short) TRACE_VERSION_NUMBER); // version + buffer.putShort((short) HEADER_SIZE); // offset to data + buffer.putLong(wallStart); // start time in usec + buffer.putShort(HEADER_RECORD_SIZE); // size of a record in bytes + // padding to 32 bytes + for (int i = 0; i < HEADER_SIZE - 18; i++) { + buffer.put((byte) 0); + } + + buffer.flip(); + channel.position(offset).write(buffer); + + buffer = ByteBuffer.allocateDirect(14).order(ByteOrder.LITTLE_ENDIAN); + for (Entry entry : entries) { + buffer.putShort((short) 1); // we simulate only one thread + buffer.putInt(entry.traceId); // entering method + buffer.putInt((int) (entry.threadStart - threadStart)); + buffer.putInt((int) (entry.wallStart - wallStart)); + + buffer.flip(); + channel.write(buffer); + buffer.clear(); + + buffer.putShort((short) 1); + buffer.putInt(entry.traceId | ACTION_EXIT_METHOD); // exiting method + buffer.putInt((int) (entry.threadStart + entry.threadTime - threadStart)); + buffer.putInt((int) (entry.wallStart + entry.wallTime - wallStart)); + + buffer.flip(); + channel.write(buffer); + buffer.clear(); + } + + channel.close(); } + + private static void writeHeader(DataOutputStream out, long start, + HashMap<String, Integer> names, ArrayList<Entry> entries) throws IOException { + + Entry last = entries.get(entries.size() - 1); + long wallTotal = (last.wallStart + last.wallTime) - start; + + startSection("version", out); + addValue(null, Integer.toString(TRACE_VERSION_NUMBER), out); + addValue("data-file-overflow", "false", out); + addValue("clock", "dual", out); + addValue("elapsed-time-usec", Long.toString(wallTotal), out); + addValue("num-method-calls", Integer.toString(entries.size()), out); + addValue("clock-call-overhead-nsec", "1", out); + addValue("vm", "dalvik", out); + + startSection("threads", out); + addThreadId(1, "main", out); + + startSection("methods", out); + addMethods(names, out); + + startSection("end", out); + } + + private static void addMethods(HashMap<String, Integer> names, DataOutputStream out) + throws IOException { + + for (Map.Entry<String, Integer> name : names.entrySet()) { + out.writeBytes(String.format("0x%08x\tEventQueue\t%s\t()V\tLooper\t-2\n", + name.getValue(), name.getKey())); + } + } + + private static void addThreadId(int id, String name, DataOutputStream out) + throws IOException { - private void saveTrace(Entry entry, DataOutputStream out) throws IOException { - out.writeShort(entry.traceId); - out.writeLong(entry.wallStart); - out.writeLong(entry.wallTime); - out.writeLong(entry.threadStart); - out.writeLong(entry.threadTime); + out.writeBytes(Integer.toString(id) + '\t' + name + '\n'); + } + + private static void addValue(String name, String value, DataOutputStream out) + throws IOException { + + if (name != null) { + out.writeBytes(name + "="); + } + out.writeBytes(value + '\n'); + } + + private static void startSection(String name, DataOutputStream out) throws IOException { + out.writeBytes("*" + name + '\n'); } static class Entry { - short traceId; + int traceId; long wallStart; long wallTime; long threadStart; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 92a8ce7b033a..6f909713e4c1 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2854,7 +2854,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // display lists to render, force an invalidate to allow the animation to // continue drawing another frame invalidate(true); - if (a instanceof AlphaAnimation) { + if (a.hasAlpha()) { // alpha animations should cause the child to recreate its display list child.invalidate(true); } diff --git a/core/java/android/view/animation/AlphaAnimation.java b/core/java/android/view/animation/AlphaAnimation.java index 651fe458ab4b..c4d9afcc15c6 100644 --- a/core/java/android/view/animation/AlphaAnimation.java +++ b/core/java/android/view/animation/AlphaAnimation.java @@ -78,4 +78,12 @@ public class AlphaAnimation extends Animation { public boolean willChangeBounds() { return false; } + + /** + * @hide + */ + @Override + public boolean hasAlpha() { + return true; + } } diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index 87c759c48853..b7dfabcbed0f 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -1001,6 +1001,15 @@ public abstract class Animation implements Cloneable { } /** + * Return true if this animation changes the view's alpha property. + * + * @hide + */ + public boolean hasAlpha() { + return false; + } + + /** * Utility class to parse a string description of a size. */ protected static class Description { diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java index 873ce53ff315..4f2542b730de 100644 --- a/core/java/android/view/animation/AnimationSet.java +++ b/core/java/android/view/animation/AnimationSet.java @@ -43,6 +43,8 @@ public class AnimationSet extends Animation { private static final int PROPERTY_CHANGE_BOUNDS_MASK = 0x80; private int mFlags = 0; + private boolean mDirty; + private boolean mHasAlpha; private ArrayList<Animation> mAnimations = new ArrayList<Animation>(); @@ -138,6 +140,28 @@ public class AnimationSet extends Animation { } /** + * @hide + */ + @Override + public boolean hasAlpha() { + if (mDirty) { + mDirty = mHasAlpha = false; + + final int count = mAnimations.size(); + final ArrayList<Animation> animations = mAnimations; + + for (int i = 0; i < count; i++) { + if (animations.get(i).hasAlpha()) { + mHasAlpha = true; + break; + } + } + } + + return mHasAlpha; + } + + /** * <p>Sets the duration of every child animation.</p> * * @param durationMillis the duration of the animation, in milliseconds, for @@ -175,6 +199,8 @@ public class AnimationSet extends Animation { mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration()); mDuration = mLastEnd - mStartOffset; } + + mDirty = true; } /** diff --git a/core/java/android/view/textservice/SpellCheckerInfo.aidl b/core/java/android/view/textservice/SpellCheckerInfo.aidl new file mode 100644 index 000000000000..eb5dfcc01c88 --- /dev/null +++ b/core/java/android/view/textservice/SpellCheckerInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2011 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.textservice; + +parcelable SpellCheckerInfo; diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java new file mode 100644 index 000000000000..d88a39fc050b --- /dev/null +++ b/core/java/android/view/textservice/SpellCheckerInfo.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2011 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.textservice; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class is used to specify meta information of an spell checker. + */ +public final class SpellCheckerInfo implements Parcelable { + private final ResolveInfo mService; + private final String mId; + + /** + * Constructor. + * @hide + */ + public SpellCheckerInfo(Context context, ResolveInfo service) { + mService = service; + ServiceInfo si = service.serviceInfo; + mId = new ComponentName(si.packageName, si.name).flattenToShortString(); + } + + /** + * Constructor. + * @hide + */ + public SpellCheckerInfo(Parcel source) { + mId = source.readString(); + mService = ResolveInfo.CREATOR.createFromParcel(source); + } + + /** + * Return a unique ID for this spell checker. The ID is generated from + * the package and class name implementing the method. + */ + public String getId() { + return mId; + } + + + /** + * Return the component of the service that implements. + */ + public ComponentName getComponent() { + return new ComponentName( + mService.serviceInfo.packageName, mService.serviceInfo.name); + } + + /** + * Return the .apk package that implements this input method. + */ + public String getPackageName() { + return mService.serviceInfo.packageName; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mId); + mService.writeToParcel(dest, flags); + } + + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<SpellCheckerInfo> CREATOR + = new Parcelable.Creator<SpellCheckerInfo>() { + @Override + public SpellCheckerInfo createFromParcel(Parcel source) { + return new SpellCheckerInfo(source); + } + + @Override + public SpellCheckerInfo[] newArray(int size) { + return new SpellCheckerInfo[size]; + } + }; + + /** + * Load the user-displayed label for this spell checker. + * + * @param pm Supply a PackageManager used to load the spell checker's resources. + */ + public CharSequence loadLabel(PackageManager pm) { + return mService.loadLabel(pm); + } + + /** + * Load the user-displayed icon for this spell checker. + * + * @param pm Supply a PackageManager used to load the spell checker's resources. + */ + public Drawable loadIcon(PackageManager pm) { + return mService.loadIcon(pm); + } + + /** + * Used to make this class parcelable. + */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/textservice/SuggestionsInfo.aidl b/core/java/android/view/textservice/SuggestionsInfo.aidl new file mode 100644 index 000000000000..66e20d24ce1c --- /dev/null +++ b/core/java/android/view/textservice/SuggestionsInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2011 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.textservice; + +parcelable SuggestionsInfo; diff --git a/core/java/android/view/textservice/SuggestionsInfo.java b/core/java/android/view/textservice/SuggestionsInfo.java new file mode 100644 index 000000000000..3332f1e262ee --- /dev/null +++ b/core/java/android/view/textservice/SuggestionsInfo.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2011 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.textservice; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class contains a metadata of suggestions from the text service + */ +public final class SuggestionsInfo implements Parcelable { + private static final String[] EMPTY = new String[0]; + + /** + * Flag of the attributes of the suggestions that can be obtained by + * {@link #getSuggestionsAttributes}: this tells that the requested word was found + * in the dictionary in the text service. + */ + public static final int RESULT_ATTR_IN_THE_DICTIONARY = 0x0001; + /** + * Flag of the attributes of the suggestions that can be obtained by + * {@link #getSuggestionsAttributes}: this tells that the text service thinks the requested + * word looks a typo. + */ + public static final int RESULT_ATTR_LOOKS_TYPO = 0x0002; + private final int mSuggestionsAttributes; + private final String[] mSuggestions; + private final boolean mSuggestionsAvailable; + private int mCookie; + private int mSequence; + + /** + * Constructor. + * @param suggestionsAttributes from the text service + * @param suggestions from the text service + */ + public SuggestionsInfo(int suggestionsAttributes, String[] suggestions) { + mSuggestionsAttributes = suggestionsAttributes; + if (suggestions == null) { + mSuggestions = EMPTY; + mSuggestionsAvailable = false; + } else { + mSuggestions = suggestions; + mSuggestionsAvailable = true; + } + mCookie = 0; + mSequence = 0; + } + + /** + * Constructor. + * @param suggestionsAttributes from the text service + * @param suggestions from the text service + * @param cookie the cookie of the input TextInfo + * @param sequence the cookie of the input TextInfo + */ + public SuggestionsInfo( + int suggestionsAttributes, String[] suggestions, int cookie, int sequence) { + if (suggestions == null) { + mSuggestions = EMPTY; + mSuggestionsAvailable = false; + } else { + mSuggestions = suggestions; + mSuggestionsAvailable = true; + } + mSuggestionsAttributes = suggestionsAttributes; + mCookie = cookie; + mSequence = sequence; + } + + public SuggestionsInfo(Parcel source) { + mSuggestionsAttributes = source.readInt(); + mSuggestions = source.readStringArray(); + mCookie = source.readInt(); + mSequence = source.readInt(); + mSuggestionsAvailable = source.readInt() == 1; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSuggestionsAttributes); + dest.writeStringArray(mSuggestions); + dest.writeInt(mCookie); + dest.writeInt(mSequence); + dest.writeInt(mSuggestionsAvailable ? 1 : 0); + } + + /** + * Set the cookie and the sequence of SuggestionsInfo which are set to TextInfo from a client + * application + * @param cookie the cookie of an input TextInfo + * @param sequence the cookie of an input TextInfo + */ + public void setCookieAndSequence(int cookie, int sequence) { + mCookie = cookie; + mSequence = sequence; + } + + /** + * @return the cookie which may be set by a client application + */ + public int getCookie() { + return mCookie; + } + + /** + * @return the sequence which may be set by a client application + */ + public int getSequence() { + return mSequence; + } + + /** + * @return the attributes of suggestions. This includes whether the spell checker has the word + * in its dictionary or not and whether the spell checker has confident suggestions for the + * word or not. + */ + public int getSuggestionsAttributes() { + return mSuggestionsAttributes; + } + + /** + * @return the count of the suggestions. If there's no suggestions at all, this method returns + * -1. Even if this method returns 0, it doesn't necessarily mean that there are no suggestions + * for the requested word. For instance, the caller could have been asked to limit the maximum + * number of suggestions returned. + */ + public int getSuggestionsCount() { + if (!mSuggestionsAvailable) { + return -1; + } + return mSuggestions.length; + } + + /** + * @param i the id of suggestions + * @return the suggestion at the specified id + */ + public String getSuggestionAt(int i) { + return mSuggestions[i]; + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<SuggestionsInfo> CREATOR + = new Parcelable.Creator<SuggestionsInfo>() { + @Override + public SuggestionsInfo createFromParcel(Parcel source) { + return new SuggestionsInfo(source); + } + + @Override + public SuggestionsInfo[] newArray(int size) { + return new SuggestionsInfo[size]; + } + }; + + /** + * Used to make this class parcelable. + */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/textservice/TextInfo.aidl b/core/java/android/view/textservice/TextInfo.aidl new file mode 100644 index 000000000000..d231d7638a75 --- /dev/null +++ b/core/java/android/view/textservice/TextInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2011 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.textservice; + +parcelable TextInfo; diff --git a/core/java/android/view/textservice/TextInfo.java b/core/java/android/view/textservice/TextInfo.java new file mode 100644 index 000000000000..b534eb0be607 --- /dev/null +++ b/core/java/android/view/textservice/TextInfo.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2011 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.textservice; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * This class contains a metadata of the input of TextService + */ +public final class TextInfo implements Parcelable { + private final String mText; + private final int mCookie; + private final int mSequence; + + /** + * Constructor. + * @param text the text which will be input to TextService + */ + public TextInfo(String text) { + this(text, 0, 0); + } + + /** + * Constructor. + * @param text the text which will be input to TextService + * @param cookie the cookie for this TextInfo + * @param sequence the sequence number for this TextInfo + */ + public TextInfo(String text, int cookie, int sequence) { + if (TextUtils.isEmpty(text)) { + throw new IllegalArgumentException(text); + } + mText = text; + mCookie = cookie; + mSequence = sequence; + } + + public TextInfo(Parcel source) { + mText = source.readString(); + mCookie = source.readInt(); + mSequence = source.readInt(); + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mText); + dest.writeInt(mCookie); + dest.writeInt(mSequence); + } + + /** + * @return the text which is an input of a text service + */ + public String getText() { + return mText; + } + + /** + * @return the cookie of TextInfo + */ + public int getCookie() { + return mCookie; + } + + /** + * @return the sequence of TextInfo + */ + public int getSequence() { + return mSequence; + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<TextInfo> CREATOR + = new Parcelable.Creator<TextInfo>() { + @Override + public TextInfo createFromParcel(Parcel source) { + return new TextInfo(source); + } + + @Override + public TextInfo[] newArray(int size) { + return new TextInfo[size]; + } + }; + + /** + * Used to make this class parcelable. + */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java new file mode 100644 index 000000000000..229b4145bdcd --- /dev/null +++ b/core/java/android/view/textservice/TextServicesManager.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2011 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.textservice; + +import com.android.internal.textservice.ITextServicesManager; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.view.textservice.SpellCheckerInfo; +import android.service.textservice.SpellCheckerSession; +import android.service.textservice.SpellCheckerSession.SpellCheckerSessionListener; + +import java.util.Locale; + +/** + * System API to the overall text services, which arbitrates interaction between applications + * and text services. You can retrieve an instance of this interface with + * {@link Context#getSystemService(String) Context.getSystemService()}. + * + * The user can change the current text services in Settings. And also applications can specify + * the target text services. + */ +public final class TextServicesManager { + private static final String TAG = TextServicesManager.class.getSimpleName(); + + private static TextServicesManager sInstance; + private static ITextServicesManager sService; + + private TextServicesManager() { + if (sService == null) { + IBinder b = ServiceManager.getService(Context.TEXT_SERVICES_MANAGER_SERVICE); + sService = ITextServicesManager.Stub.asInterface(b); + } + } + + /** + * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist. + * @hide + */ + public static TextServicesManager getInstance() { + synchronized (TextServicesManager.class) { + if (sInstance != null) { + return sInstance; + } + sInstance = new TextServicesManager(); + } + return sInstance; + } + + /** + * Get a spell checker session for the specified spell checker + * @param locale the locale for the spell checker + * @param listener a spell checker session lister for getting results from a spell checker. + * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled + * languages in settings will be returned. + * @return the spell checker session of the spell checker + */ + // TODO: Add a method to get enabled spell checkers. + // TODO: Handle referToSpellCheckerLanguageSettings + public SpellCheckerSession newSpellCheckerSession(Locale locale, + SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) { + if (locale == null || listener == null) { + throw new NullPointerException(); + } + final SpellCheckerInfo info; + try { + info = sService.getCurrentSpellChecker(locale.toString()); + } catch (RemoteException e) { + return null; + } + if (info == null) { + return null; + } + final SpellCheckerSession session = new SpellCheckerSession(info, sService, listener); + try { + sService.getSpellCheckerService( + info, locale.toString(), session.getTextServicesSessionListener(), + session.getSpellCheckerSessionListener()); + } catch (RemoteException e) { + return null; + } + return session; + } + + /** + * @hide + */ + public SpellCheckerInfo[] getEnabledSpellCheckers() { + try { + return sService.getEnabledSpellCheckers(); + } catch (RemoteException e) { + return null; + } + } + + /** + * @hide + */ + public SpellCheckerInfo getCurrentSpellChecker() { + try { + // Passing null as a locale for ICS + return sService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + return null; + } + } +} diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java index bb4d192516b9..620973e421d5 100644 --- a/core/java/android/webkit/JniUtil.java +++ b/core/java/android/webkit/JniUtil.java @@ -18,6 +18,7 @@ package android.webkit; import android.content.Context; import android.net.Uri; +import android.provider.Settings; import android.util.Log; import java.io.InputStream; @@ -38,7 +39,7 @@ class JniUtil { private static boolean initialized = false; - private static void checkIntialized() { + private static void checkInitialized() { if (!initialized) { throw new IllegalStateException("Call CookieSyncManager::createInstance() or create a webview before using this class"); } @@ -63,7 +64,7 @@ class JniUtil { * @return String The application's database directory */ private static synchronized String getDatabaseDirectory() { - checkIntialized(); + checkInitialized(); if (sDatabaseDirectory == null) sDatabaseDirectory = sContext.getDatabasePath("dummy").getParent(); @@ -76,7 +77,7 @@ class JniUtil { * @return String The application's cache directory */ private static synchronized String getCacheDirectory() { - checkIntialized(); + checkInitialized(); if (sCacheDirectory == null) sCacheDirectory = sContext.getCacheDir().getAbsolutePath(); @@ -166,5 +167,13 @@ class JniUtil { return sUseChromiumHttpStack; } + private static synchronized String getAutofillQueryUrl() { + checkInitialized(); + // If the device has not checked in it won't have pulled down the system setting for the + // Autofill Url. In that case we will not make autofill server requests. + return Settings.Secure.getString(sContext.getContentResolver(), + Settings.Secure.WEB_AUTOFILL_QUERY_URL); + } + private static native boolean nativeUseChromiumHttpStack(); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 73db03e44f56..e24ab58503ad 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -4701,6 +4701,7 @@ public class WebView extends AbsoluteLayout private Message mUpdateMessage; private boolean mAutoFillable; private boolean mAutoComplete; + private WebSettings mWebSettings; public RequestFormData(String name, String url, Message msg, boolean autoFillable, boolean autoComplete) { @@ -4709,6 +4710,7 @@ public class WebView extends AbsoluteLayout mUpdateMessage = msg; mAutoFillable = autoFillable; mAutoComplete = autoComplete; + mWebSettings = getSettings(); } public void run() { @@ -4718,8 +4720,7 @@ public class WebView extends AbsoluteLayout // Note that code inside the adapter click handler in WebTextView depends // on the AutoFill item being at the top of the drop down list. If you change // the order, make sure to do it there too! - WebSettings settings = getSettings(); - if (settings != null && settings.getAutoFillProfile() != null) { + if (mWebSettings != null && mWebSettings.getAutoFillProfile() != null) { pastEntries.add(getResources().getText( com.android.internal.R.string.autofill_this_form).toString() + " " + @@ -9119,20 +9120,12 @@ public class WebView extends AbsoluteLayout return nativeTileProfilingNumTilesInFrame(frame); } /** @hide only used by profiling tests */ - public int tileProfilingGetX(int frame, int tile) { - return nativeTileProfilingGetX(frame, tile); + public int tileProfilingGetInt(int frame, int tile, String key) { + return nativeTileProfilingGetInt(frame, tile, key); } /** @hide only used by profiling tests */ - public int tileProfilingGetY(int frame, int tile) { - return nativeTileProfilingGetY(frame, tile); - } - /** @hide only used by profiling tests */ - public boolean tileProfilingGetReady(int frame, int tile) { - return nativeTileProfilingGetReady(frame, tile); - } - /** @hide only used by profiling tests */ - public int tileProfilingGetLevel(int frame, int tile) { - return nativeTileProfilingGetLevel(frame, tile); + public float tileProfilingGetFloat(int frame, int tile, String key) { + return nativeTileProfilingGetFloat(frame, tile, key); } private native int nativeCacheHitFramePointer(); @@ -9262,10 +9255,8 @@ public class WebView extends AbsoluteLayout private native void nativeTileProfilingClear(); private native int nativeTileProfilingNumFrames(); private native int nativeTileProfilingNumTilesInFrame(int frame); - private native int nativeTileProfilingGetX(int frame, int tile); - private native int nativeTileProfilingGetY(int frame, int tile); - private native boolean nativeTileProfilingGetReady(int frame, int tile); - private native int nativeTileProfilingGetLevel(int frame, int tile); + private native int nativeTileProfilingGetInt(int frame, int tile, String key); + private native float nativeTileProfilingGetFloat(int frame, int tile, String key); // Never call this version except by updateCachedTextfield(String) - // we always want to pass in our generation number. private native void nativeUpdateCachedTextfield(String updatedText, diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index d7a2526003e7..8d8023bb0dd3 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -1069,6 +1069,15 @@ public final class WebViewCore { + " arg1=" + msg.arg1 + " arg2=" + msg.arg2 + " obj=" + msg.obj); } + if (mWebView == null + && msg.what != EventHub.RESUME_TIMERS + && msg.what != EventHub.PAUSE_TIMERS) { + if (DebugFlags.WEB_VIEW_CORE) { + Log.v(LOGTAG, "Rejecting message " + msg.what + + " because we are destroyed"); + } + return; + } switch (msg.what) { case WEBKIT_DRAW: webkitDraw(); @@ -1757,30 +1766,17 @@ public final class WebViewCore { } /** - * Removes pending messages and trigger a DESTROY message to send to - * WebCore. + * Sends a DESTROY message to WebCore. * Called from UI thread. */ void destroy() { - // We don't want anyone to post a message between removing pending - // messages and sending the destroy message. synchronized (mEventHub) { - // RESUME_TIMERS and PAUSE_TIMERS are per process base. They need to - // be preserved even the WebView is destroyed. - // Note: we should not have more than one RESUME_TIMERS/PAUSE_TIMERS - boolean hasResume = mEventHub.hasMessages(EventHub.RESUME_TIMERS); - boolean hasPause = mEventHub.hasMessages(EventHub.PAUSE_TIMERS); - mEventHub.removeMessages(); + // Do not call removeMessages as then we risk removing PAUSE_TIMERS + // or RESUME_TIMERS messages, which we must still handle as they + // are per process. DESTROY will instead trigger a white list in + // mEventHub, skipping any remaining messages in the queue mEventHub.sendMessageAtFrontOfQueue( Message.obtain(null, EventHub.DESTROY)); - if (hasPause) { - mEventHub.sendMessageAtFrontOfQueue( - Message.obtain(null, EventHub.PAUSE_TIMERS)); - } - if (hasResume) { - mEventHub.sendMessageAtFrontOfQueue( - Message.obtain(null, EventHub.RESUME_TIMERS)); - } mEventHub.blockMessages(); } } @@ -2113,13 +2109,17 @@ public final class WebViewCore { // called from JNI or WebView thread /* package */ void contentDraw() { - // don't update the Picture until we have an initial width and finish - // the first layout - if (mCurrentViewWidth == 0 || !mBrowserFrame.firstLayoutDone()) { - return; - } - // only fire an event if this is our first request synchronized (this) { + if (mWebView == null || mBrowserFrame == null) { + // We were destroyed + return; + } + // don't update the Picture until we have an initial width and finish + // the first layout + if (mCurrentViewWidth == 0 || !mBrowserFrame.firstLayoutDone()) { + return; + } + // only fire an event if this is our first request if (mDrawIsScheduled) return; mDrawIsScheduled = true; if (mDrawIsPaused) return; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 8f8c1d069b9d..b7c1687debef 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -4638,9 +4638,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te childrenTop += getVerticalFadingEdgeLength(); } } - // Don't ever focus a disabled item. - if (!mAdapter.isEnabled(i)) continue; - if (top >= childrenTop) { // Found a view whose top is fully visisble selectedPos = firstPosition + i; diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 49616cc1ef18..f3a6da7780bb 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -39,8 +39,9 @@ public class CheckedTextView extends TextView implements Checkable { private boolean mChecked; private int mCheckMarkResource; private Drawable mCheckMarkDrawable; - private int mBasePaddingRight; + private int mBasePadding; private int mCheckMarkWidth; + private boolean mNeedRequestlayout; private static final int[] CHECKED_STATE_SET = { R.attr.state_checked @@ -123,6 +124,7 @@ public class CheckedTextView extends TextView implements Checkable { mCheckMarkDrawable.setCallback(null); unscheduleDrawable(mCheckMarkDrawable); } + mNeedRequestlayout = (d != mCheckMarkDrawable); if (d != null) { d.setCallback(this); d.setVisible(getVisibility() == VISIBLE, false); @@ -130,19 +132,35 @@ public class CheckedTextView extends TextView implements Checkable { setMinHeight(d.getIntrinsicHeight()); mCheckMarkWidth = d.getIntrinsicWidth(); - mUserPaddingRight = mCheckMarkWidth + mBasePaddingRight; d.setState(getDrawableState()); } else { - mUserPaddingRight = mBasePaddingRight; + mCheckMarkWidth = 0; } mCheckMarkDrawable = d; - requestLayout(); + // Do padding resolution. This will call setPadding() and do a requestLayout() if needed. + resolvePadding(); + } + + /** + * @hide + */ + @Override + protected void resolvePadding() { + super.resolvePadding(); + int newPadding = (mCheckMarkDrawable != null) ? + mCheckMarkWidth + mBasePadding : mBasePadding; + mNeedRequestlayout |= (mPaddingRight != newPadding); + mPaddingRight = newPadding; + if (mNeedRequestlayout) { + requestLayout(); + mNeedRequestlayout = false; + } } @Override public void setPadding(int left, int top, int right, int bottom) { super.setPadding(left, top, right, bottom); - mBasePaddingRight = mUserPaddingRight; + mBasePadding = mPaddingRight; } @Override @@ -167,9 +185,9 @@ public class CheckedTextView extends TextView implements Checkable { int right = getWidth(); checkMarkDrawable.setBounds( - right - mUserPaddingRight, + right - mPaddingRight, y, - right - mUserPaddingRight + mCheckMarkWidth, + right - mPaddingRight + mCheckMarkWidth, y + height); checkMarkDrawable.draw(canvas); } diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 563fc26aa6e5..36927ca434ef 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -91,6 +91,7 @@ public class PopupWindow { private boolean mLayoutInScreen; private boolean mClipToScreen; private boolean mAllowScrollingAnchorParent = true; + private boolean mLayoutInsetDecor = false; private OnTouchListener mTouchInterceptor; @@ -658,6 +659,22 @@ public class PopupWindow { } /** + * Allows the popup window to force the flag + * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}, overriding default behavior. + * This will cause the popup to inset its content to account for system windows overlaying + * the screen, such as the status bar. + * + * <p>This will often be combined with {@link #setLayoutInScreenEnabled(boolean)}. + * + * @param enabled true if the popup's views should inset content to account for system windows, + * the way that decor views behave for full-screen windows. + * @hide + */ + public void setLayoutInsetDecor(boolean enabled) { + mLayoutInsetDecor = enabled; + } + + /** * Set the layout type for this window. Should be one of the TYPE constants defined in * {@link WindowManager.LayoutParams}. * @@ -942,6 +959,7 @@ public class PopupWindow { if (mContext != null) { p.packageName = mContext.getPackageName(); } + mPopupView.setFitsSystemWindows(mLayoutInsetDecor); mWindowManager.addView(mPopupView, p); } @@ -1012,6 +1030,9 @@ public class PopupWindow { if (mLayoutInScreen) { curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; } + if (mLayoutInsetDecor) { + curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; + } return curFlags; } diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index b2d1a1e53573..55b73dffb4a6 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -45,6 +45,7 @@ import android.text.style.ImageSpan; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; +import android.view.CollapsibleActionView; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -58,18 +59,30 @@ import com.android.internal.R; import java.util.WeakHashMap; /** - * A widget that provides a user interface for the user to enter a search query and submit a - * request to a search provider. Shows a list of query suggestions or results, if - * available, and allows the user to pick a suggestion or result to launch into. + * A widget that provides a user interface for the user to enter a search query and submit a request + * to a search provider. Shows a list of query suggestions or results, if available, and allows the + * user to pick a suggestion or result to launch into. * - * <p>For more information, see the <a href="{@docRoot}guide/topics/search/index.html">Search</a> - * documentation.<p> + * <p> + * When the SearchView is used in an ActionBar as an action view for a collapsible menu item, it + * needs to be set to iconified by default using {@link #setIconifiedByDefault(boolean) + * setIconifiedByDefault(true)}. This is the default, so nothing needs to be done. + * </p> + * <p> + * If you want the search field to always be visible, then call setIconifiedByDefault(false). + * </p> * + * <p> + * For more information, see the <a href="{@docRoot}guide/topics/search/index.html">Search</a> + * documentation. + * <p> + * + * @see android.view.MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW * @attr ref android.R.styleable#SearchView_iconifiedByDefault * @attr ref android.R.styleable#SearchView_maxWidth * @attr ref android.R.styleable#SearchView_queryHint */ -public class SearchView extends LinearLayout { +public class SearchView extends LinearLayout implements CollapsibleActionView { private static final boolean DBG = false; private static final String LOG_TAG = "SearchView"; @@ -100,6 +113,7 @@ public class SearchView extends LinearLayout { private int mMaxWidth; private boolean mVoiceButtonEnabled; private CharSequence mUserQuery; + private boolean mExpandedInActionView; private SearchableInfo mSearchable; private Bundle mAppSearchData; @@ -623,7 +637,7 @@ public class SearchView extends LinearLayout { final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText()); // Should we show the close button? It is not shown if there's no focus, // field is not iconified by default and there is no text in it. - final boolean showClose = hasText || mIconifiedByDefault; + final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView); mCloseButton.setVisibility(showClose ? VISIBLE : INVISIBLE); mCloseButton.getDrawable().setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET); } @@ -1022,6 +1036,25 @@ public class SearchView extends LinearLayout { super.onAttachedToWindow(); } + /** + * {@inheritDoc} + */ + @Override + public void onActionViewCollapsed() { + mQueryTextView.setText(""); + setIconified(true); + mExpandedInActionView = false; + } + + /** + * {@inheritDoc} + */ + @Override + public void onActionViewExpanded() { + mExpandedInActionView = true; + setIconified(false); + } + private void adjustDropDownSizeAndPosition() { if (mDropDownAnchor.getWidth() > 1) { Resources res = getContext().getResources(); diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index b7565f3129d5..0c80a1170b13 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -34,6 +34,7 @@ import android.view.Gravity; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; import com.android.internal.R; @@ -360,6 +361,13 @@ public class Switch extends CompoundButton { } } + @Override + public void onPopulateAccessibilityEvent(AccessibilityEvent event) { + super.onPopulateAccessibilityEvent(event); + Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout; + event.getText().add(switchText.getText()); + } + private Layout makeLayout(CharSequence text) { return new StaticLayout(text, mTextPaint, (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint)), diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 108ac335fe7f..a7324b085f9b 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -329,7 +329,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mTextEditPasteWindowLayout, mTextEditSidePasteWindowLayout; private int mTextEditNoPasteWindowLayout, mTextEditSideNoPasteWindowLayout; - private int mTextEditSuggestionsBottomWindowLayout, mTextEditSuggestionsTopWindowLayout; + private int mTextEditSuggestionsWindowLayout; private int mTextEditSuggestionItemLayout; private SuggestionsPopupWindow mSuggestionsPopupWindow; private SuggestionRangeSpan mSuggestionRangeSpan; @@ -830,12 +830,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mTextEditSideNoPasteWindowLayout = a.getResourceId(attr, 0); break; - case com.android.internal.R.styleable.TextView_textEditSuggestionsBottomWindowLayout: - mTextEditSuggestionsBottomWindowLayout = a.getResourceId(attr, 0); - break; - - case com.android.internal.R.styleable.TextView_textEditSuggestionsTopWindowLayout: - mTextEditSuggestionsTopWindowLayout = a.getResourceId(attr, 0); + case com.android.internal.R.styleable.TextView_textEditSuggestionsWindowLayout: + mTextEditSuggestionsWindowLayout = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: @@ -8785,9 +8781,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int MAX_NUMBER_SUGGESTIONS = 5; private static final int NO_SUGGESTIONS = -1; private final PopupWindow mContainer; - private final ViewGroup[] mSuggestionViews = new ViewGroup[2]; - private final int[] mSuggestionViewLayouts = new int[] { - mTextEditSuggestionsBottomWindowLayout, mTextEditSuggestionsTopWindowLayout}; + private ViewGroup mSuggestionViewGroup; private WordIterator mSuggestionWordIterator; private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan[0]; @@ -8809,12 +8803,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int suggestionIndex; // the index of the suggestion inside suggestionSpan } - private ViewGroup getViewGroup(boolean under) { - final int viewIndex = under ? 0 : 1; - ViewGroup viewGroup = mSuggestionViews[viewIndex]; - - if (viewGroup == null) { - final int layout = mSuggestionViewLayouts[viewIndex]; + private void initSuggestionViewGroup() { + if (mSuggestionViewGroup == null) { LayoutInflater inflater = (LayoutInflater) TextView.this.mContext. getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -8823,19 +8813,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener "Unable to create TextEdit suggestion window inflater"); } - View view = inflater.inflate(layout, null); + View view = inflater.inflate(mTextEditSuggestionsWindowLayout, null); if (! (view instanceof ViewGroup)) { throw new IllegalArgumentException( "Inflated TextEdit suggestion window is not a ViewGroup: " + view); } - viewGroup = (ViewGroup) view; + mSuggestionViewGroup = (ViewGroup) view; // Inflate the suggestion items once and for all. for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) { - View childView = inflater.inflate(mTextEditSuggestionItemLayout, viewGroup, - false); + View childView = inflater.inflate(mTextEditSuggestionItemLayout, + mSuggestionViewGroup, false); if (! (childView instanceof TextView)) { throw new IllegalArgumentException( @@ -8843,14 +8833,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } childView.setTag(new SuggestionInfo()); - viewGroup.addView(childView); + mSuggestionViewGroup.addView(childView); childView.setOnClickListener(this); } - mSuggestionViews[viewIndex] = viewGroup; + mContainer.setContentView(mSuggestionViewGroup); } - - return viewGroup; } public void show() { @@ -8861,8 +8849,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class); final int nbSpans = suggestionSpans.length; - ViewGroup viewGroup = getViewGroup(true); - mContainer.setContentView(viewGroup); + initSuggestionViewGroup(); int totalNbSuggestions = 0; int spanUnionStart = mText.length(); @@ -8878,7 +8865,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener String[] suggestions = suggestionSpan.getSuggestions(); int nbSuggestions = suggestions.length; for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) { - TextView textView = (TextView) viewGroup.getChildAt(totalNbSuggestions); + TextView textView = (TextView) mSuggestionViewGroup.getChildAt( + totalNbSuggestions); textView.setText(suggestions[suggestionIndex]); SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag(); suggestionInfo.spanStart = spanStart; @@ -8897,7 +8885,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (totalNbSuggestions == 0) { // TODO Replace by final text, use a dedicated layout, add a fade out timer... - TextView textView = (TextView) viewGroup.getChildAt(0); + TextView textView = (TextView) mSuggestionViewGroup.getChildAt(0); textView.setText("No suggestions available"); SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag(); suggestionInfo.spanStart = NO_SUGGESTIONS; @@ -8908,17 +8896,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); for (int i = 0; i < totalNbSuggestions; i++) { - final TextView textView = (TextView) viewGroup.getChildAt(i); + final TextView textView = (TextView) mSuggestionViewGroup.getChildAt(i); highlightTextDifferences(textView, spanUnionStart, spanUnionEnd); } } - for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) { - viewGroup.getChildAt(i).setVisibility(i < totalNbSuggestions ? VISIBLE : GONE); + for (int i = 0; i < totalNbSuggestions; i++) { + mSuggestionViewGroup.getChildAt(i).setVisibility(VISIBLE); + } + for (int i = totalNbSuggestions; i < MAX_NUMBER_SUGGESTIONS; i++) { + mSuggestionViewGroup.getChildAt(i).setVisibility(GONE); } - final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - viewGroup.measure(size, size); + final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); + final int screenWidth = displayMetrics.widthPixels; + final int screenHeight = displayMetrics.heightPixels; + mSuggestionViewGroup.measure( + View.MeasureSpec.makeMeasureSpec(screenWidth, View.MeasureSpec.AT_MOST), + View.MeasureSpec.makeMeasureSpec(screenHeight, View.MeasureSpec.AT_MOST)); positionAtCursor(); } @@ -9171,23 +9166,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Vertical clipping if (coords[1] + height > screenHeight) { - // Try to position above current line instead - // TODO use top layout instead, reverse suggestion order, - // try full screen vertical down if it still does not fit. TBD with designers. - - // Update dimensions from new view - contentView = mContainer.getContentView(); - width = contentView.getMeasuredWidth(); - height = contentView.getMeasuredHeight(); - - final int lineTop = mLayout.getLineTop(line); - final int lineHeight = lineBottom - lineTop; - coords[1] -= height + lineHeight; + coords[1] = screenHeight - height; } // Horizontal clipping - coords[0] = Math.max(0, coords[0]); coords[0] = Math.min(displayMetrics.widthPixels - width, coords[0]); + coords[0] = Math.max(0, coords[0]); mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY, coords[0], coords[1]); } diff --git a/core/java/com/android/internal/textservice/ISpellCheckerService.aidl b/core/java/com/android/internal/textservice/ISpellCheckerService.aidl new file mode 100644 index 000000000000..ff0049276bce --- /dev/null +++ b/core/java/com/android/internal/textservice/ISpellCheckerService.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011 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.textservice; + +import com.android.internal.textservice.ISpellCheckerSession; +import com.android.internal.textservice.ISpellCheckerSessionListener; + +/** + * Public interface to the global spell checker. + * @hide + */ +interface ISpellCheckerService { + ISpellCheckerSession getISpellCheckerSession( + String locale, ISpellCheckerSessionListener listener); +} diff --git a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl new file mode 100644 index 000000000000..79e43510c00a --- /dev/null +++ b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011 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.textservice; + +import android.view.textservice.TextInfo; + +/** + * @hide + */ +oneway interface ISpellCheckerSession { + void getSuggestionsMultiple( + in TextInfo[] textInfos, int suggestionsLimit, boolean multipleWords); + void cancel(); +} diff --git a/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl b/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl new file mode 100644 index 000000000000..796b06eb06d6 --- /dev/null +++ b/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2011 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.textservice; + +import android.view.textservice.SuggestionsInfo; + +/** + * @hide + */ +oneway interface ISpellCheckerSessionListener { + void onGetSuggestions(in SuggestionsInfo[] results); +} diff --git a/core/java/com/android/internal/textservice/ITextServicesManager.aidl b/core/java/com/android/internal/textservice/ITextServicesManager.aidl new file mode 100644 index 000000000000..2a045e3ad754 --- /dev/null +++ b/core/java/com/android/internal/textservice/ITextServicesManager.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2011 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.textservice; + +import com.android.internal.textservice.ISpellCheckerSessionListener; +import com.android.internal.textservice.ITextServicesSessionListener; + +import android.content.ComponentName; +import android.view.textservice.SpellCheckerInfo; + +/** + * Interface to the text service manager. + * @hide + */ +interface ITextServicesManager { + SpellCheckerInfo getCurrentSpellChecker(String locale); + oneway void getSpellCheckerService(in SpellCheckerInfo info, in String locale, + in ITextServicesSessionListener tsListener, + in ISpellCheckerSessionListener scListener); + oneway void finishSpellCheckerService(in ISpellCheckerSessionListener listener); + SpellCheckerInfo[] getEnabledSpellCheckers(); +} diff --git a/core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl b/core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl new file mode 100644 index 000000000000..ecb6cd0f85d1 --- /dev/null +++ b/core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011 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.textservice; + +import com.android.internal.textservice.ISpellCheckerSession; + +import android.view.textservice.SpellCheckerInfo; + +/** + * Interface to the text service session. + * @hide + */ +interface ITextServicesSessionListener { + oneway void onServiceConnected(in ISpellCheckerSession spellCheckerSession); +} diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index 69b80d9a91e5..9ecd29ff81b9 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -41,6 +41,9 @@ public class Protocol { /** Non system protocols */ public static final int BASE_WIFI = 0x00020000; public static final int BASE_WIFI_WATCHDOG = 0x00021000; + public static final int BASE_WIFI_P2P_MANAGER = 0x00022000; + public static final int BASE_WIFI_P2P_SERVICE = 0x00023000; + public static final int BASE_WIFI_MONITOR = 0x00024000; public static final int BASE_DHCP = 0x00030000; public static final int BASE_DATA_CONNECTION = 0x00040000; public static final int BASE_DATA_CONNECTION_AC = 0x00041000; diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 164d5811c31e..159b3da440bd 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -50,6 +50,8 @@ public class MenuBuilder implements Menu { private static final String LOGTAG = "MenuBuilder"; private static final String PRESENTER_KEY = "android:menu:presenters"; + private static final String ACTION_VIEW_STATES_KEY = "android:menu:actionviewstates"; + private static final String EXPANDED_ACTION_VIEW_ID = "android:menu:expandedactionview"; private static final int[] sCategoryToOrder = new int[] { 1, /* No category */ @@ -308,6 +310,67 @@ public class MenuBuilder implements Menu { dispatchRestoreInstanceState(state); } + public void saveActionViewStates(Bundle outStates) { + SparseArray<Parcelable> viewStates = null; + + final int itemCount = size(); + for (int i = 0; i < itemCount; i++) { + final MenuItem item = getItem(i); + final View v = item.getActionView(); + if (v != null && v.getId() != View.NO_ID) { + if (viewStates == null) { + viewStates = new SparseArray<Parcelable>(); + } + v.saveHierarchyState(viewStates); + if (item.isActionViewExpanded()) { + outStates.putInt(EXPANDED_ACTION_VIEW_ID, item.getItemId()); + } + } + if (item.hasSubMenu()) { + final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu(); + subMenu.saveActionViewStates(outStates); + } + } + + if (viewStates != null) { + outStates.putSparseParcelableArray(getActionViewStatesKey(), viewStates); + } + } + + public void restoreActionViewStates(Bundle states) { + if (states == null) { + return; + } + + SparseArray<Parcelable> viewStates = states.getSparseParcelableArray( + getActionViewStatesKey()); + + final int itemCount = size(); + for (int i = 0; i < itemCount; i++) { + final MenuItem item = getItem(i); + final View v = item.getActionView(); + if (v != null && v.getId() != View.NO_ID) { + v.restoreHierarchyState(viewStates); + } + if (item.hasSubMenu()) { + final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu(); + subMenu.restoreActionViewStates(states); + } + } + + final int expandedId = states.getInt(EXPANDED_ACTION_VIEW_ID); + if (expandedId > 0) { + MenuItem itemToExpand = findItem(expandedId); + if (itemToExpand != null) { + itemToExpand.expandActionView(); + } + } + } + + protected String getActionViewStatesKey() { + return ACTION_VIEW_STATES_KEY; + } + public void setCallback(Callback cb) { mCallback = cb; } diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 541d10120dd6..b0a002d2ce8d 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -553,6 +553,9 @@ public final class MenuItemImpl implements MenuItem { public MenuItem setActionView(View view) { mActionView = view; mActionProvider = null; + if (view != null && view.getId() == View.NO_ID && mId > 0) { + view.setId(mId); + } mMenu.onItemActionRequestChanged(this); return this; } diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java index fb1cd5e81ef7..92acf8cda8f5 100644 --- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java +++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java @@ -121,4 +121,13 @@ public class SubMenuBuilder extends MenuBuilder implements SubMenu { public boolean collapseItemActionView(MenuItemImpl item) { return mParentMenu.collapseItemActionView(item); } + + @Override + public String getActionViewStatesKey() { + final int itemId = mItem != null ? mItem.getItemId() : 0; + if (itemId == 0) { + return null; + } + return super.getActionViewStatesKey() + ":" + itemId; + } } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index e03858b4af13..09262e01284a 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -42,6 +42,7 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; +import android.view.CollapsibleActionView; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -285,8 +286,11 @@ public class ActionBarView extends AbsActionBarView { public void setSplitActionBar(boolean splitActionBar) { if (mSplitActionBar != splitActionBar) { if (mMenuView != null) { + final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); + if (oldParent != null) { + oldParent.removeView(mMenuView); + } if (splitActionBar) { - removeView(mMenuView); if (mSplitView != null) { mSplitView.addView(mMenuView); } @@ -332,7 +336,10 @@ public class ActionBarView extends AbsActionBarView { MenuBuilder builder = (MenuBuilder) menu; mOptionsMenu = builder; if (mMenuView != null) { - removeView(mMenuView); + final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); + if (oldParent != null) { + oldParent.removeView(mMenuView); + } } if (mActionMenuPresenter == null) { mActionMenuPresenter = new ActionMenuPresenter(); @@ -351,6 +358,10 @@ public class ActionBarView extends AbsActionBarView { builder.addMenuPresenter(mActionMenuPresenter); builder.addMenuPresenter(mExpandedMenuPresenter); menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); + final ViewGroup oldParent = (ViewGroup) menuView.getParent(); + if (oldParent != null && oldParent != this) { + oldParent.removeView(menuView); + } addView(menuView, layoutParams); } else { mActionMenuPresenter.setExpandedActionViewsExclusive(false); @@ -365,6 +376,10 @@ public class ActionBarView extends AbsActionBarView { builder.addMenuPresenter(mExpandedMenuPresenter); menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); if (mSplitView != null) { + final ViewGroup oldParent = (ViewGroup) menuView.getParent(); + if (oldParent != null && oldParent != mSplitView) { + oldParent.removeView(menuView); + } mSplitView.addView(menuView, layoutParams); } else { // We'll add this later if we missed it this time. @@ -1304,6 +1319,10 @@ public class ActionBarView extends AbsActionBarView { if (mCustomNavView != null) mCustomNavView.setVisibility(GONE); requestLayout(); item.setActionViewExpanded(true); + + if (mExpandedActionView instanceof CollapsibleActionView) { + ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded(); + } return true; } @@ -1330,11 +1349,16 @@ public class ActionBarView extends AbsActionBarView { if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { mCustomNavView.setVisibility(VISIBLE); } + View collapsedView = mExpandedActionView; mExpandedActionView = null; mExpandedHomeLayout.setIcon(null); mCurrentExpandedItem = null; requestLayout(); item.setActionViewExpanded(false); + + if (collapsedView instanceof CollapsibleActionView) { + ((CollapsibleActionView) collapsedView).onActionViewCollapsed(); + } return true; } diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp index e930c5c37638..3cbd9124d8cd 100644 --- a/core/jni/android_net_wifi_Wifi.cpp +++ b/core/jni/android_net_wifi_Wifi.cpp @@ -28,6 +28,9 @@ #define WIFI_PKG_NAME "android/net/wifi/WifiNative" #define BUF_SIZE 256 +//TODO: This file can be refactored to push a lot of the functionality to java +//with just a few JNI calls - doBoolean/doInt/doString + namespace android { static jboolean sScanModeActive = false; @@ -315,26 +318,56 @@ static jboolean android_net_wifi_stopDriverCommand(JNIEnv* env, jobject) return doBooleanCommand("OK", "DRIVER STOP"); } -static jboolean android_net_wifi_startPacketFiltering(JNIEnv* env, jobject) +/* + Multicast filtering rules work as follows: + + The driver can filter multicast (v4 and/or v6) and broadcast packets when in + a power optimized mode (typically when screen goes off). + + In order to prevent the driver from filtering the multicast/broadcast packets, we have to + add a DRIVER RXFILTER-ADD rule followed by DRIVER RXFILTER-START to make the rule effective + + DRIVER RXFILTER-ADD Num + where Num = 0 - Unicast, 1 - Broadcast, 2 - Mutil4 or 3 - Multi6 + + and DRIVER RXFILTER-START + + In order to stop the usage of these rules, we do + + DRIVER RXFILTER-STOP + DRIVER RXFILTER-REMOVE Num + where Num is as described for RXFILTER-ADD + + The SETSUSPENDOPT driver command overrides the filtering rules +*/ + +static jboolean android_net_wifi_startMultiV4Filtering(JNIEnv* env, jobject) { - return doBooleanCommand("OK", "DRIVER RXFILTER-ADD 0") - && doBooleanCommand("OK", "DRIVER RXFILTER-ADD 1") - && doBooleanCommand("OK", "DRIVER RXFILTER-ADD 3") + return doBooleanCommand("OK", "DRIVER RXFILTER-STOP") + && doBooleanCommand("OK", "DRIVER RXFILTER-REMOVE 2") && doBooleanCommand("OK", "DRIVER RXFILTER-START"); } -static jboolean android_net_wifi_stopPacketFiltering(JNIEnv* env, jobject) +static jboolean android_net_wifi_stopMultiV4Filtering(JNIEnv* env, jobject) { - jboolean result = doBooleanCommand("OK", "DRIVER RXFILTER-STOP"); - if (result) { - (void)doBooleanCommand("OK", "DRIVER RXFILTER-REMOVE 3"); - (void)doBooleanCommand("OK", "DRIVER RXFILTER-REMOVE 1"); - (void)doBooleanCommand("OK", "DRIVER RXFILTER-REMOVE 0"); - } + return doBooleanCommand("OK", "DRIVER RXFILTER-ADD 2") + && doBooleanCommand("OK", "DRIVER RXFILTER-START"); +} - return result; +static jboolean android_net_wifi_startMultiV6Filtering(JNIEnv* env, jobject) +{ + return doBooleanCommand("OK", "DRIVER RXFILTER-STOP") + && doBooleanCommand("OK", "DRIVER RXFILTER-REMOVE 3") + && doBooleanCommand("OK", "DRIVER RXFILTER-START"); +} + +static jboolean android_net_wifi_stopMultiV6Filtering(JNIEnv* env, jobject) +{ + return doBooleanCommand("OK", "DRIVER RXFILTER-ADD 3") + && doBooleanCommand("OK", "DRIVER RXFILTER-START"); } + static jint android_net_wifi_getRssiHelper(const char *cmd) { char reply[BUF_SIZE]; @@ -507,6 +540,35 @@ static void android_net_wifi_setScanIntervalCommand(JNIEnv* env, jobject, jint s } +static jboolean android_net_wifi_doBooleanCommand(JNIEnv* env, jobject, jstring javaCommand) +{ + ScopedUtfChars command(env, javaCommand); + if (command.c_str() == NULL) { + return JNI_FALSE; + } + return doBooleanCommand("OK", "%s", command.c_str()); +} + +static jint android_net_wifi_doIntCommand(JNIEnv* env, jobject, jstring javaCommand) +{ + ScopedUtfChars command(env, javaCommand); + if (command.c_str() == NULL) { + return -1; + } + return doIntCommand("%s", command.c_str()); +} + +static jstring android_net_wifi_doStringCommand(JNIEnv* env, jobject, jstring javaCommand) +{ + ScopedUtfChars command(env, javaCommand); + if (command.c_str() == NULL) { + return NULL; + } + return doStringCommand(env, "%s", command.c_str()); +} + + + // ---------------------------------------------------------------------------- /* @@ -545,8 +607,10 @@ static JNINativeMethod gWifiMethods[] = { { "setScanModeCommand", "(Z)Z", (void*) android_net_wifi_setScanModeCommand }, { "startDriverCommand", "()Z", (void*) android_net_wifi_startDriverCommand }, { "stopDriverCommand", "()Z", (void*) android_net_wifi_stopDriverCommand }, - { "startPacketFiltering", "()Z", (void*) android_net_wifi_startPacketFiltering }, - { "stopPacketFiltering", "()Z", (void*) android_net_wifi_stopPacketFiltering }, + { "startFilteringMulticastV4Packets", "()Z", (void*) android_net_wifi_startMultiV4Filtering}, + { "stopFilteringMulticastV4Packets", "()Z", (void*) android_net_wifi_stopMultiV4Filtering}, + { "startFilteringMulticastV6Packets", "()Z", (void*) android_net_wifi_startMultiV6Filtering}, + { "stopFilteringMulticastV6Packets", "()Z", (void*) android_net_wifi_stopMultiV6Filtering}, { "setPowerModeCommand", "(I)Z", (void*) android_net_wifi_setPowerModeCommand }, { "getPowerModeCommand", "()I", (void*) android_net_wifi_getPowerModeCommand }, { "setBandCommand", "(I)Z", (void*) android_net_wifi_setBandCommand}, @@ -576,6 +640,9 @@ static JNINativeMethod gWifiMethods[] = { (void*) android_net_wifi_setCountryCodeCommand}, { "enableBackgroundScanCommand", "(Z)V", (void*) android_net_wifi_enableBackgroundScanCommand}, { "setScanIntervalCommand", "(I)V", (void*) android_net_wifi_setScanIntervalCommand}, + { "doBooleanCommand", "(Ljava/lang/String;)Z", (void*) android_net_wifi_doBooleanCommand}, + { "doIntCommand", "(Ljava/lang/String;)I", (void*) android_net_wifi_doIntCommand}, + { "doStringCommand", "(Ljava/lang/String;)Ljava/lang/String;", (void*) android_net_wifi_doStringCommand}, }; int register_android_net_wifi_WifiManager(JNIEnv* env) diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index b0c2f2c2da07..b06de9d1b91e 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -576,18 +576,18 @@ static void android_view_GLES20Canvas_drawTextRun(JNIEnv* env, jobject clazz, // ---------------------------------------------------------------------------- static DisplayList* android_view_GLES20Canvas_getDisplayList(JNIEnv* env, - jobject clazz, DisplayListRenderer* renderer) { - return renderer->getDisplayList(); + jobject clazz, DisplayListRenderer* renderer, DisplayList* displayList) { + return renderer->getDisplayList(displayList); +} + +static OpenGLRenderer* android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env, + jobject clazz) { + return new DisplayListRenderer; } -static OpenGLRenderer* android_view_GLES20Canvas_getDisplayListRenderer(JNIEnv* env, +static void android_view_GLES20Canvas_resetDisplayListRenderer(JNIEnv* env, jobject clazz, DisplayListRenderer* renderer) { - if (renderer == NULL) { - renderer = new DisplayListRenderer; - } else { - renderer->reset(); - } - return renderer; + renderer->reset(); } static void android_view_GLES20Canvas_destroyDisplayList(JNIEnv* env, @@ -812,9 +812,10 @@ static JNINativeMethod gMethods[] = { { "nGetClipBounds", "(ILandroid/graphics/Rect;)Z", (void*) android_view_GLES20Canvas_getClipBounds }, - { "nGetDisplayList", "(I)I", (void*) android_view_GLES20Canvas_getDisplayList }, + { "nGetDisplayList", "(II)I", (void*) android_view_GLES20Canvas_getDisplayList }, { "nDestroyDisplayList", "(I)V", (void*) android_view_GLES20Canvas_destroyDisplayList }, - { "nGetDisplayListRenderer", "(I)I", (void*) android_view_GLES20Canvas_getDisplayListRenderer }, + { "nCreateDisplayListRenderer", "()I", (void*) android_view_GLES20Canvas_createDisplayListRenderer }, + { "nResetDisplayListRenderer", "(I)V", (void*) android_view_GLES20Canvas_resetDisplayListRenderer }, { "nDrawDisplayList", "(IIIILandroid/graphics/Rect;)Z", (void*) android_view_GLES20Canvas_drawDisplayList }, { "nOutputDisplayList", "(II)V", (void*) android_view_GLES20Canvas_outputDisplayList }, diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 0dc92934fdd9..4c1ca313feff 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -22,6 +22,7 @@ #include "android/graphics/GraphicsJNI.h" #include <binder/IMemory.h> +#include <gui/SurfaceTexture.h> #include <surfaceflinger/SurfaceComposerClient.h> #include <surfaceflinger/Surface.h> #include <ui/Region.h> @@ -38,6 +39,7 @@ #include "JNIHelp.h" #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_view_Surface.h> +#include <android_runtime/android_graphics_SurfaceTexture.h> #include <utils/misc.h> @@ -244,6 +246,19 @@ static void Surface_init( setSurfaceControl(env, clazz, surface); } +static void Surface_initFromSurfaceTexture( + JNIEnv* env, jobject clazz, jobject jst) +{ + sp<ISurfaceTexture> st(SurfaceTexture_getSurfaceTexture(env, jst)); + sp<Surface> surface(new Surface(st)); + if (surface == NULL) { + jniThrowException(env, OutOfResourcesException, NULL); + return; + } + setSurfaceControl(env, clazz, NULL); + setSurface(env, clazz, surface); +} + static void Surface_initParcel(JNIEnv* env, jobject clazz, jobject argParcel) { Parcel* parcel = (Parcel*)env->GetIntField(argParcel, no.native_parcel); @@ -761,10 +776,26 @@ static void Surface_writeToParcel( return; } + // The Java instance may have a SurfaceControl (in the case of the + // WindowManager or a system app). In that case, we defer to the + // SurfaceControl to send its ISurface. Otherwise, if the Surface is + // available we let it parcel itself. Finally, if the Surface is also + // NULL we fall back to using the SurfaceControl path which sends an + // empty surface; this matches legacy behavior. const sp<SurfaceControl>& control(getSurfaceControl(env, clazz)); - SurfaceControl::writeSurfaceToParcel(control, parcel); + if (control != NULL) { + SurfaceControl::writeSurfaceToParcel(control, parcel); + } else { + sp<Surface> surface(Surface_getSurface(env, clazz)); + if (surface != NULL) { + Surface::writeToParcel(surface, parcel); + } else { + SurfaceControl::writeSurfaceToParcel(NULL, parcel); + } + } if (flags & PARCELABLE_WRITE_RETURN_VALUE) { - setSurfaceControl(env, clazz, 0); + setSurfaceControl(env, clazz, NULL); + setSurface(env, clazz, NULL); } } @@ -784,6 +815,7 @@ static JNINativeMethod gSurfaceMethods[] = { {"nativeClassInit", "()V", (void*)nativeClassInit }, {"init", "(Landroid/view/SurfaceSession;ILjava/lang/String;IIIII)V", (void*)Surface_init }, {"init", "(Landroid/os/Parcel;)V", (void*)Surface_initParcel }, + {"initFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)V", (void*)Surface_initFromSurfaceTexture }, {"getIdentity", "()I", (void*)Surface_getIdentity }, {"destroy", "()V", (void*)Surface_destroy }, {"release", "()V", (void*)Surface_release }, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 103a32691735..91003d181b9c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1120,6 +1120,13 @@ android:description="@string/permdesc_bindInputMethod" android:protectionLevel="signature" /> + <!-- Must be required by a TextService (e.g. SpellCheckerService) + to ensure that only the system can bind to it. --> + <permission android:name="android.permission.BIND_TEXT_SERVICE" + android:label="@string/permlab_bindTextService" + android:description="@string/permdesc_bindTextService" + android:protectionLevel="signature" /> + <!-- Must be required by a {@link android.service.wallpaper.WallpaperService}, to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_WALLPAPER" @@ -1197,7 +1204,7 @@ <permission android:name="android.permission.READ_FRAME_BUFFER" android:label="@string/permlab_readFrameBuffer" android:description="@string/permdesc_readFrameBuffer" - android:protectionLevel="signature" /> + android:protectionLevel="signatureOrSystem" /> <!-- Required to be able to disable the device (very dangerous!). --> <permission android:name="android.permission.BRICK" diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml index 30518e02784b..61aa72a3b307 100644 --- a/core/res/res/anim/screen_rotate_minus_90_enter.xml +++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml @@ -19,11 +19,6 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> - <scale android:fromXScale="100%p" android:toXScale="100%" - android:fromYScale="100%p" android:toYScale="100%" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:duration="@android:integer/config_mediumAnimTime" /> <rotate android:fromDegrees="-90" android:toDegrees="0" android:pivotX="50%" android:pivotY="50%" android:interpolator="@interpolator/decelerate_quint" diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml index 20943c853578..53b0ccd43f05 100644 --- a/core/res/res/anim/screen_rotate_plus_90_enter.xml +++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml @@ -19,11 +19,6 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> - <scale android:fromXScale="100%p" android:toXScale="100%" - android:fromYScale="100%p" android:toYScale="100%" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:duration="@android:integer/config_mediumAnimTime" /> <rotate android:fromDegrees="90" android:toDegrees="0" android:pivotX="50%" android:pivotY="50%" android:interpolator="@interpolator/decelerate_quint" diff --git a/core/res/res/drawable-hdpi/text_edit_suggestions_top_window.9.png b/core/res/res/drawable-hdpi/text_edit_suggestions_top_window.9.png Binary files differdeleted file mode 100644 index ff6b34a7ace4..000000000000 --- a/core/res/res/drawable-hdpi/text_edit_suggestions_top_window.9.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/text_edit_suggestions_bottom_window.9.png b/core/res/res/drawable-hdpi/text_edit_suggestions_window.9.png Binary files differindex c97514f3ca99..c97514f3ca99 100644 --- a/core/res/res/drawable-hdpi/text_edit_suggestions_bottom_window.9.png +++ b/core/res/res/drawable-hdpi/text_edit_suggestions_window.9.png diff --git a/core/res/res/drawable-mdpi/text_edit_suggestions_top_window.9.png b/core/res/res/drawable-mdpi/text_edit_suggestions_top_window.9.png Binary files differdeleted file mode 100644 index 41886eb378a5..000000000000 --- a/core/res/res/drawable-mdpi/text_edit_suggestions_top_window.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/text_edit_suggestions_bottom_window.9.png b/core/res/res/drawable-mdpi/text_edit_suggestions_window.9.png Binary files differindex 88be6e1f96f4..88be6e1f96f4 100644 --- a/core/res/res/drawable-mdpi/text_edit_suggestions_bottom_window.9.png +++ b/core/res/res/drawable-mdpi/text_edit_suggestions_window.9.png diff --git a/core/res/res/layout/text_edit_suggestion_item.xml b/core/res/res/layout/text_edit_suggestion_item.xml index ef537d9efa93..082c5ec031c0 100644 --- a/core/res/res/layout/text_edit_suggestion_item.xml +++ b/core/res/res/layout/text_edit_suggestion_item.xml @@ -22,6 +22,8 @@ android:paddingTop="8dip" android:paddingBottom="8dip" android:layout_gravity="left|center_vertical" + android:singleLine="true" + android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@android:color/dim_foreground_light" /> diff --git a/core/res/res/layout/text_edit_suggestions_top_window.xml b/core/res/res/layout/text_edit_suggestions_window.xml index 67faa37c32d5..824025e606f4 100644 --- a/core/res/res/layout/text_edit_suggestions_top_window.xml +++ b/core/res/res/layout/text_edit_suggestions_window.xml @@ -18,6 +18,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - android:background="@android:drawable/text_edit_suggestions_top_window"> + android:background="@android:drawable/text_edit_suggestions_window"> </LinearLayout> diff --git a/core/res/res/layout/text_edit_suggestions_bottom_window.xml b/core/res/res/layout/wifi_p2p_go_negotiation_request_alert.xml index 588bfbd9a321..41ca6571cbc0 100644 --- a/core/res/res/layout/text_edit_suggestions_bottom_window.xml +++ b/core/res/res/layout/wifi_p2p_go_negotiation_request_alert.xml @@ -4,9 +4,9 @@ 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. @@ -16,8 +16,11 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - android:background="@android:drawable/text_edit_suggestions_bottom_window"> + android:layout_height="wrap_content"> + <EditText android:id="@+id/wifi_p2p_wps_pin" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inputType="textPassword" + /> </LinearLayout> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e307c1799612..b5fc55eb7ee0 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -713,10 +713,7 @@ <!-- Layout of a the view that is used to create the text suggestions popup window in an EditText. This window will be displayed below the text line. --> - <attr name="textEditSuggestionsBottomWindowLayout" format="reference" /> - <!-- Same as textEditSuggestionsBottomWindowLayout, but used when the popup is displayed - above the current line of text instead of below. --> - <attr name="textEditSuggestionsTopWindowLayout" format="reference" /> + <attr name="textEditSuggestionsWindowLayout" format="reference" /> <!-- Layout of the TextView item that will populate the suggestion popup window. --> <attr name="textEditSuggestionItemLayout" format="reference" /> @@ -3084,10 +3081,7 @@ <!-- Layout of a the view that is used to create the text suggestions popup window in an EditText. This window will be displayed below the text line. --> - <attr name="textEditSuggestionsBottomWindowLayout" /> - <!-- Same as textEditSuggestionsBottomWindowLayout, but used when the popup is displayed - above the current line of text instead of below. --> - <attr name="textEditSuggestionsTopWindowLayout" /> + <attr name="textEditSuggestionsWindowLayout" /> <!-- Layout of the TextView item that will populate the suggestion popup window. --> <attr name="textEditSuggestionItemLayout" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 215700c2314f..65dce4977eeb 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -182,6 +182,9 @@ <!-- Boolean indicating whether the wifi chipset has dual frequency band support --> <bool translatable="false" name="config_wifi_dual_band_support">false</bool> + <!-- Boolean indicating whether the wifi chipset has p2p support --> + <bool translatable="false" name="config_wifi_p2p_support">false</bool> + <!-- Boolean indicating whether the wifi chipset supports background scanning mechanism. This mechanism allows the host to remain in suspend state and the dongle to actively scan and wake the host when a configured SSID is detected by the dongle. This chipset @@ -466,6 +469,10 @@ speech --> <bool name="config_bluetooth_wide_band_speech">true</bool> + <!-- Boolean indicating if current platform supports quick switch-on/off of + Bluetooth Module --> + <bool name="config_bluetooth_adapter_quick_switch">true</bool> + <!-- The default data-use polling period. --> <integer name="config_datause_polling_period_sec">600</integer> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 75f0c4e78f1d..b2b7025b398d 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1715,8 +1715,7 @@ <public type="attr" name="switchPreferenceStyle" /> <public type="attr" name="textSuggestionsWindowStyle" /> - <public type="attr" name="textEditSuggestionsBottomWindowLayout" /> - <public type="attr" name="textEditSuggestionsTopWindowLayout" /> + <public type="attr" name="textEditSuggestionsWindowLayout" /> <public type="attr" name="textEditSuggestionItemLayout" /> <public type="attr" name="suggestionsEnabled" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 509ee6996246..e1a31f4d3e46 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -705,6 +705,12 @@ interface of an input method. Should never be needed for normal applications.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bindTextService">bind to a text service</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bindTextService">Allows the holder to bind to the top-level + interface of a text service(e.g. SpellCheckerService). Should never be needed for normal applications.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_bindWallpaper">bind to a wallpaper</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_bindWallpaper">Allows the holder to bind to the top-level @@ -2577,6 +2583,14 @@ <!-- Do not translate. Default access point SSID used for tethering --> <string name="wifi_tether_configure_ssid_default" translatable="false">AndroidAP</string> + <!-- Wi-Fi p2p dialog title--> + <string name="wifi_p2p_dialog_title">Wi-Fi Direct</string> + <string name="wifi_p2p_turnon_message">Start Wi-Fi Direct operation. This will turn off Wi-Fi client/hotspot operation.</string> + <string name="wifi_p2p_failed_message">Failed to start Wi-Fi Direct</string> + <string name="wifi_p2p_pbc_go_negotiation_request_message">Wi-Fi Direct connection setup request from <xliff:g id="p2p_device_address">%1$s</xliff:g>. Click OK to accept. </string> + <string name="wifi_p2p_pin_go_negotiation_request_message">Wi-Fi Direct connection setup request from <xliff:g id="p2p_device_address">%1$s</xliff:g>. Enter pin to proceed. </string> + <string name="wifi_p2p_pin_display_message">WPS pin <xliff:g id="p2p_wps_pin">%1$s</xliff:g> needs to be entered on the peer device <xliff:g id="p2p_client_address">%2$s</xliff:g> for connection setup to proceed </string> + <!-- Name of the dialog that lets the user choose an accented character to insert --> <string name="select_character">Insert character</string> @@ -2675,12 +2689,14 @@ <!-- USB_STORAGE_ERROR dialog ok button--> <string name="dlg_ok">OK</string> - <!-- USB_PREFERENCES: Notification for wehen the user connects the phone to a computer via USB in MTP mode. This is the title --> + <!-- USB_PREFERENCES: Notification for when the user connects the phone to a computer via USB in MTP mode. This is the title --> <string name="usb_mtp_notification_title">Connected as a media device</string> - <!-- USB_PREFERENCES: Notification for wehen the user connects the phone to a computer via USB in PTP mode. This is the title --> + <!-- USB_PREFERENCES: Notification for when the user connects the phone to a computer via USB in PTP mode. This is the title --> <string name="usb_ptp_notification_title">Connected as a camera</string> - <!-- USB_PREFERENCES: Notification for wehen the user connects the phone to a computer via USB in mass storage mode (for installer CD image). This is the title --> + <!-- USB_PREFERENCES: Notification for when the user connects the phone to a computer via USB in mass storage mode (for installer CD image). This is the title --> <string name="usb_cd_installer_notification_title">Connected as an installer</string> + <!-- USB_PREFERENCES: Notification for when a USB accessory is attached. This is the title --> + <string name="usb_accessory_notification_title">Connected to a USB accessory</string> <!-- See USB_PREFERENCES. This is the message. --> <string name="usb_notification_message">Touch for other USB options</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index d647467a59fc..9b6c4424e7f0 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -423,8 +423,7 @@ <item name="android:textEditNoPasteWindowLayout">?android:attr/textEditNoPasteWindowLayout</item> <item name="android:textEditSidePasteWindowLayout">?android:attr/textEditSidePasteWindowLayout</item> <item name="android:textEditSideNoPasteWindowLayout">?android:attr/textEditSideNoPasteWindowLayout</item> - <item name="android:textEditSuggestionsBottomWindowLayout">?android:attr/textEditSuggestionsBottomWindowLayout</item> - <item name="android:textEditSuggestionsTopWindowLayout">?android:attr/textEditSuggestionsTopWindowLayout</item> + <item name="android:textEditSuggestionsWindowLayout">?android:attr/textEditSuggestionsWindowLayout</item> <item name="android:textEditSuggestionItemLayout">?android:attr/textEditSuggestionItemLayout</item> <item name="android:textCursorDrawable">?android:attr/textCursorDrawable</item> </style> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 90f3602073c0..93ccfe30412a 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -190,8 +190,7 @@ <item name="textEditSidePasteWindowLayout">@android:layout/text_edit_side_paste_window</item> <item name="textEditSideNoPasteWindowLayout">@android:layout/text_edit_side_no_paste_window</item> <item name="textSuggestionsWindowStyle">@android:style/Widget.TextSuggestions</item> - <item name="textEditSuggestionsBottomWindowLayout">@android:layout/text_edit_suggestions_bottom_window</item> - <item name="textEditSuggestionsTopWindowLayout">@android:layout/text_edit_suggestions_top_window</item> + <item name="textEditSuggestionsWindowLayout">@android:layout/text_edit_suggestions_window</item> <item name="textEditSuggestionItemLayout">@android:layout/text_edit_suggestion_item</item> <item name="textCursorDrawable">@null</item> diff --git a/docs/html/guide/developing/tools/adb.jd b/docs/html/guide/developing/tools/adb.jd index 78d12effd8b7..d32cf6684eda 100644 --- a/docs/html/guide/developing/tools/adb.jd +++ b/docs/html/guide/developing/tools/adb.jd @@ -280,7 +280,7 @@ instance, see <a href="{@docRoot}guide/developing/building/index.html">Building <td>Run PPP over USB. <ul> <li><code><tty></code> — the tty for PPP stream. For example <code>dev:/dev/omap_csmi_ttyl</code>. </li> -<li><code>[parm]... </code> &mdash zero or more PPP/PPPD options, such as <code>defaultroute</code>, <code>local</code>, <code>notty</code>, etc.</li></ul> +<li><code>[parm]... </code> — zero or more PPP/PPPD options, such as <code>defaultroute</code>, <code>local</code>, <code>notty</code>, etc.</li></ul> <p>Note that you should not automatically start a PPP connection. </p></td> <td></td> diff --git a/docs/html/guide/topics/intents/intents-filters.jd b/docs/html/guide/topics/intents/intents-filters.jd index 59052143523e..3f9455391a86 100644 --- a/docs/html/guide/topics/intents/intents-filters.jd +++ b/docs/html/guide/topics/intents/intents-filters.jd @@ -927,7 +927,7 @@ as described by its two intent filters: <p> The first, primary, purpose of this activity is to enable the user to -interact with a single note &mdash to either {@code VIEW} the note or +interact with a single note — to either {@code VIEW} the note or {@code EDIT} it. (The {@code EDIT_NOTE} category is a synonym for {@code EDIT}.) The intent would contain the URI for data matching the MIME type <code>vnd.android.cursor.item/vnd.google.note</code> — diff --git a/docs/html/guide/topics/manifest/activity-element.jd b/docs/html/guide/topics/manifest/activity-element.jd index 34862121ae82..743832c6e763 100644 --- a/docs/html/guide/topics/manifest/activity-element.jd +++ b/docs/html/guide/topics/manifest/activity-element.jd @@ -710,7 +710,7 @@ the soft keyboard.</li> The setting must be one of the values listed in the following table, or a combination of one "{@code state...}" value plus one "{@code adjust...}" value. Setting multiple values in either group — multiple -"{@code state...}" values, for example &mdash has undefined results. +"{@code state...}" values, for example — has undefined results. Individual values are separated by a vertical bar ({@code |}). For example: </p> @@ -801,4 +801,4 @@ Level 3.</dd> <dt>see also:</dt> <dd><code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> <br/><code><a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"><activity-alias></a></code></dd> -</dl>
\ No newline at end of file +</dl> diff --git a/docs/html/guide/topics/providers/content-providers.jd b/docs/html/guide/topics/providers/content-providers.jd index 2a84c263f652..513886afbe23 100644 --- a/docs/html/guide/topics/providers/content-providers.jd +++ b/docs/html/guide/topics/providers/content-providers.jd @@ -277,7 +277,7 @@ are returned. All the content providers that come with the platform define constants for their columns. For example, the {@link android.provider.Contacts.Phones android.provider.Contacts.Phones} class defines constants for the names of the columns in the phone table illustrated -earlier &mdash {@code _ID}, {@code NUMBER}, {@code NUMBER_KEY}, {@code NAME}, +earlier — {@code _ID}, {@code NUMBER}, {@code NUMBER_KEY}, {@code NAME}, and so on.</li> <li><p>A filter detailing which rows to return, formatted as an SQL {@code WHERE} diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp index 4a85faf7dd2e..3476bd5aa646 100644 --- a/graphics/jni/android_renderscript_RenderScript.cpp +++ b/graphics/jni/android_renderscript_RenderScript.cpp @@ -504,6 +504,7 @@ nAllocationCopyToBitmap(JNIEnv *_env, jobject _this, RsContext con, jint alloc, void* ptr = bitmap.getPixels(); rsAllocationCopyToBitmap(con, (RsAllocation)alloc, ptr, bitmap.getSize()); bitmap.unlockPixels(); + bitmap.notifyPixelsChanged(); } static void ReleaseBitmapCallback(void *bmp) diff --git a/include/gui/ISurfaceTexture.h b/include/gui/ISurfaceTexture.h index bc630ae4ee57..1eda64669599 100644 --- a/include/gui/ISurfaceTexture.h +++ b/include/gui/ISurfaceTexture.h @@ -51,7 +51,7 @@ protected: // the given slot index, and the client is expected to mirror the // slot->buffer mapping so that it's not necessary to transfer a // GraphicBuffer for every dequeue operation. - virtual sp<GraphicBuffer> requestBuffer(int slot) = 0; + virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) = 0; // setBufferCount sets the number of buffer slots available. Calling this // will also cause all buffer slots to be emptied. The caller should empty @@ -94,12 +94,6 @@ protected: virtual status_t setTransform(uint32_t transform) = 0; virtual status_t setScalingMode(int mode) = 0; - // getAllocator retrieves the binder object that must be referenced as long - // as the GraphicBuffers dequeued from this ISurfaceTexture are referenced. - // Holding this binder reference prevents SurfaceFlinger from freeing the - // buffers before the client is done with them. - virtual sp<IBinder> getAllocator() = 0; - // query retrieves some information for this surface // 'what' tokens allowed are that of android_natives.h virtual int query(int what, int* value) = 0; diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h index 945f4bcd6890..2a8e7255602b 100644 --- a/include/gui/SurfaceTexture.h +++ b/include/gui/SurfaceTexture.h @@ -69,7 +69,7 @@ public: // SurfaceTexture object (i.e. they are not owned by the client). virtual status_t setBufferCount(int bufferCount); - virtual sp<GraphicBuffer> requestBuffer(int buf); + virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf); // dequeueBuffer gets the next buffer slot index for the client to use. If a // buffer slot is available then that slot index is written to the location @@ -190,6 +190,17 @@ public: // getCurrentScalingMode returns the scaling mode of the current buffer uint32_t getCurrentScalingMode() const; + // abandon frees all the buffers and puts the SurfaceTexture into the + // 'abandoned' state. Once put in this state the SurfaceTexture can never + // leave it. When in the 'abandoned' state, all methods of the + // ISurfaceTexture interface will fail with the NO_INIT error. + // + // Note that while calling this method causes all the buffers to be freed + // from the perspective of the the SurfaceTexture, if there are additional + // references on the buffers (e.g. if a buffer is referenced by a client or + // by OpenGL ES as a texture) then those buffer will remain allocated. + void abandon(); + // dump our state in a String void dump(String8& result) const; void dump(String8& result, const char* prefix, char* buffer, size_t SIZE) const; @@ -200,7 +211,6 @@ protected: // all slots. void freeAllBuffers(); static bool isExternalFormat(uint32_t format); - static GLenum getTextureTarget(uint32_t format); private: @@ -337,14 +347,9 @@ private: // reset mCurrentTexture to INVALID_BUFFER_SLOT. int mCurrentTexture; - // mCurrentTextureTarget is the GLES texture target to be used with the - // current texture. - GLenum mCurrentTextureTarget; - // mCurrentTextureBuf is the graphic buffer of the current texture. It's // possible that this buffer is not associated with any buffer slot, so we - // must track it separately in order to properly use - // IGraphicBufferAlloc::freeAllGraphicBuffersExcept. + // must track it separately in order to support the getCurrentBuffer method. sp<GraphicBuffer> mCurrentTextureBuf; // mCurrentCrop is the crop rectangle that applies to the current texture. @@ -412,6 +417,13 @@ private: typedef Vector<int> Fifo; Fifo mQueue; + // mAbandoned indicates that the SurfaceTexture will no longer be used to + // consume images buffers pushed to it using the ISurfaceTexture interface. + // It is initialized to false, and set to true in the abandon method. A + // SurfaceTexture that has been abandoned will return the NO_INIT error from + // all ISurfaceTexture methods capable of returning an error. + bool mAbandoned; + // mMutex is the mutex used to prevent concurrent access to the member // variables of SurfaceTexture objects. It must be locked whenever the // member variables are accessed. diff --git a/include/gui/SurfaceTextureClient.h b/include/gui/SurfaceTextureClient.h index 829d8abf7057..56f029f0c254 100644 --- a/include/gui/SurfaceTextureClient.h +++ b/include/gui/SurfaceTextureClient.h @@ -106,10 +106,6 @@ private: // interactions with the server using this interface. sp<ISurfaceTexture> mSurfaceTexture; - // mAllocator is the binder object that is referenced to prevent the - // dequeued buffers from being freed prematurely. - sp<IBinder> mAllocator; - // mSlots stores the buffers that have been allocated for each buffer slot. // It is initialized to null pointers, and gets filled in with the result of // ISurfaceTexture::requestBuffer when the client dequeues a buffer from a diff --git a/include/media/IMediaRecorder.h b/include/media/IMediaRecorder.h index a73267d92587..007aea6c0362 100644 --- a/include/media/IMediaRecorder.h +++ b/include/media/IMediaRecorder.h @@ -26,6 +26,7 @@ class Surface; class ICamera; class ICameraRecordingProxy; class IMediaRecorderClient; +class ISurfaceTexture; class IMediaRecorder: public IInterface { @@ -55,6 +56,7 @@ public: virtual status_t init() = 0; virtual status_t close() = 0; virtual status_t release() = 0; + virtual sp<ISurfaceTexture> querySurfaceMediaSource() = 0; }; // ---------------------------------------------------------------------------- diff --git a/include/media/MediaRecorderBase.h b/include/media/MediaRecorderBase.h index 1c08969a0d5c..ef799f5cff26 100644 --- a/include/media/MediaRecorderBase.h +++ b/include/media/MediaRecorderBase.h @@ -26,6 +26,7 @@ namespace android { class ICameraRecordingProxy; class Surface; +class ISurfaceTexture; struct MediaRecorderBase { MediaRecorderBase() {} @@ -54,6 +55,7 @@ struct MediaRecorderBase { virtual status_t reset() = 0; virtual status_t getMaxAmplitude(int *max) = 0; virtual status_t dump(int fd, const Vector<String16>& args) const = 0; + virtual sp<ISurfaceTexture> querySurfaceMediaSource() const = 0; private: MediaRecorderBase(const MediaRecorderBase &); diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h index af12d3c59c1f..72d3736296d1 100644 --- a/include/media/mediarecorder.h +++ b/include/media/mediarecorder.h @@ -31,12 +31,15 @@ class Surface; class IMediaRecorder; class ICamera; class ICameraRecordingProxy; +class ISurfaceTexture; +class SurfaceTextureClient; typedef void (*media_completion_f)(status_t status, void *cookie); enum video_source { VIDEO_SOURCE_DEFAULT = 0, VIDEO_SOURCE_CAMERA = 1, + VIDEO_SOURCE_GRALLOC_BUFFER = 2, VIDEO_SOURCE_LIST_END // must be last - used to validate audio source type }; @@ -226,6 +229,7 @@ public: status_t close(); status_t release(); void notify(int msg, int ext1, int ext2); + sp<ISurfaceTexture> querySurfaceMediaSourceFromMediaServer(); private: void doCleanUp(); @@ -233,6 +237,12 @@ private: sp<IMediaRecorder> mMediaRecorder; sp<MediaRecorderListener> mListener; + + // Reference toISurfaceTexture + // for encoding GL Frames. That is useful only when the + // video source is set to VIDEO_SOURCE_GRALLOC_BUFFER + sp<ISurfaceTexture> mSurfaceMediaSource; + media_recorder_states mCurrentState; bool mIsAudioSourceSet; bool mIsVideoSourceSet; diff --git a/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h index 48d1464336a1..713af92860ef 100644 --- a/include/media/stagefright/DataSource.h +++ b/include/media/stagefright/DataSource.h @@ -20,6 +20,7 @@ #include <sys/types.h> +#include <media/stagefright/MediaErrors.h> #include <utils/Errors.h> #include <utils/KeyedVector.h> #include <utils/List.h> @@ -61,6 +62,10 @@ public: return 0; } + virtual status_t reconnectAtOffset(off64_t offset) { + return ERROR_UNSUPPORTED; + } + //////////////////////////////////////////////////////////////////////////// bool sniff(String8 *mimeType, float *confidence, sp<AMessage> *meta); diff --git a/include/media/stagefright/HardwareAPI.h b/include/media/stagefright/HardwareAPI.h index 946a0aaa8f6c..32eed3f79c6c 100644 --- a/include/media/stagefright/HardwareAPI.h +++ b/include/media/stagefright/HardwareAPI.h @@ -99,6 +99,13 @@ struct GetAndroidNativeBufferUsageParams { OMX_U32 nUsage; // OUT }; +// An enum OMX_COLOR_FormatAndroidOpaque to indicate an opaque colorformat +// is declared in media/stagefright/openmax/OMX_IVCommon.h +// This will inform the encoder that the actual +// colorformat will be relayed by the GRalloc Buffers. +// OMX_COLOR_FormatAndroidOpaque = 0x7F000001, + + } // namespace android extern android::OMXPluginBase *createOMXPlugin(); diff --git a/include/media/stagefright/MediaSource.h b/include/media/stagefright/MediaSource.h index 37dbcd8eae84..3818e63ff4e9 100644 --- a/include/media/stagefright/MediaSource.h +++ b/include/media/stagefright/MediaSource.h @@ -29,7 +29,7 @@ namespace android { class MediaBuffer; class MetaData; -struct MediaSource : public RefBase { +struct MediaSource : public virtual RefBase { MediaSource(); // To be called before any other methods on this object, except diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h index 20fcde52d828..2932744f0e6b 100644 --- a/include/media/stagefright/OMXCodec.h +++ b/include/media/stagefright/OMXCodec.h @@ -329,6 +329,7 @@ private: void restorePatchedDataPointer(BufferInfo *info); status_t applyRotation(); + status_t waitForBufferFilled_l(); int64_t retrieveDecodingTimeUs(bool isCodecSpecific); @@ -348,6 +349,8 @@ struct CodecCapabilities { // that encode content of the given type. // profile and level indications only make sense for h.263, mpeg4 and avc // video. +// If hwCodecOnly==true, only returns hardware-based components, software and +// hardware otherwise. // The profile/level values correspond to // OMX_VIDEO_H263PROFILETYPE, OMX_VIDEO_MPEG4PROFILETYPE, // OMX_VIDEO_AVCPROFILETYPE, OMX_VIDEO_H263LEVELTYPE, OMX_VIDEO_MPEG4LEVELTYPE @@ -358,6 +361,11 @@ status_t QueryCodecs( const char *mimeType, bool queryDecoders, bool hwCodecOnly, Vector<CodecCapabilities> *results); +status_t QueryCodecs( + const sp<IOMX> &omx, + const char *mimeType, bool queryDecoders, + Vector<CodecCapabilities> *results); + } // namespace android #endif // OMX_CODEC_H_ diff --git a/include/media/stagefright/SurfaceMediaSource.h b/include/media/stagefright/SurfaceMediaSource.h new file mode 100644 index 000000000000..56bd9c315005 --- /dev/null +++ b/include/media/stagefright/SurfaceMediaSource.h @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_GUI_SURFACEMEDIASOURCE_H +#define ANDROID_GUI_SURFACEMEDIASOURCE_H + +#include <gui/ISurfaceTexture.h> + +#include <utils/threads.h> +#include <utils/Vector.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MediaBuffer.h> + +namespace android { +// ---------------------------------------------------------------------------- + +class IGraphicBufferAlloc; +class String8; +class GraphicBuffer; + +class SurfaceMediaSource : public BnSurfaceTexture, public MediaSource, + public MediaBufferObserver { +public: + enum { MIN_UNDEQUEUED_BUFFERS = 3 }; + enum { + MIN_ASYNC_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS + 1, + MIN_SYNC_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS + }; + enum { NUM_BUFFER_SLOTS = 32 }; + enum { NO_CONNECTED_API = 0 }; + + struct FrameAvailableListener : public virtual RefBase { + // onFrameAvailable() is called from queueBuffer() is the FIFO is + // empty. You can use SurfaceMediaSource::getQueuedCount() to + // figure out if there are more frames waiting. + // This is called without any lock held can be called concurrently by + // multiple threads. + virtual void onFrameAvailable() = 0; + }; + + SurfaceMediaSource(uint32_t bufW, uint32_t bufH); + + virtual ~SurfaceMediaSource(); + + + // For the MediaSource interface for use by StageFrightRecorder: + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + virtual sp<MetaData> getFormat(); + + // Get / Set the frame rate used for encoding. Default fps = 30 + status_t setFrameRate(int32_t fps) ; + int32_t getFrameRate( ) const; + + // The call for the StageFrightRecorder to tell us that + // it is done using the MediaBuffer data so that its state + // can be set to FREE for dequeuing + virtual void signalBufferReturned(MediaBuffer* buffer); + // end of MediaSource interface + + uint32_t getBufferCount( ) const { return mBufferCount;} + + + // setBufferCount updates the number of available buffer slots. After + // calling this all buffer slots are both unallocated and owned by the + // SurfaceMediaSource object (i.e. they are not owned by the client). + virtual status_t setBufferCount(int bufferCount); + + virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf); + + // dequeueBuffer gets the next buffer slot index for the client to use. If a + // buffer slot is available then that slot index is written to the location + // pointed to by the buf argument and a status of OK is returned. If no + // slot is available then a status of -EBUSY is returned and buf is + // unmodified. + virtual status_t dequeueBuffer(int *buf, uint32_t w, uint32_t h, + uint32_t format, uint32_t usage); + + // queueBuffer returns a filled buffer to the SurfaceMediaSource. In addition, a + // timestamp must be provided for the buffer. The timestamp is in + // nanoseconds, and must be monotonically increasing. Its other semantics + // (zero point, etc) are client-dependent and should be documented by the + // client. + virtual status_t queueBuffer(int buf, int64_t timestamp, + uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform); + virtual void cancelBuffer(int buf); + + // onFrameReceivedLocked informs the buffer consumers (StageFrightRecorder) + // or listeners that a frame has been received + // The buffer is not made available for dequeueing immediately. We need to + // wait to hear from StageFrightRecorder to set the buffer FREE + // Make sure this is called when the mutex is locked + virtual status_t onFrameReceivedLocked(); + + virtual status_t setScalingMode(int mode) { } // no op for encoding + virtual int query(int what, int* value); + + // Just confirming to the ISurfaceTexture interface as of now + virtual status_t setCrop(const Rect& reg) { return OK; } + virtual status_t setTransform(uint32_t transform) {return OK;} + + // setSynchronousMode set whether dequeueBuffer is synchronous or + // asynchronous. In synchronous mode, dequeueBuffer blocks until + // a buffer is available, the currently bound buffer can be dequeued and + // queued buffers will be retired in order. + // The default mode is synchronous. + // TODO: Clarify the minute differences bet sycn /async + // modes (S.Encoder vis-a-vis SurfaceTexture) + virtual status_t setSynchronousMode(bool enabled); + + // connect attempts to connect a client API to the SurfaceMediaSource. This + // must be called before any other ISurfaceTexture methods are called except + // for getAllocator. + // + // This method will fail if the connect was previously called on the + // SurfaceMediaSource and no corresponding disconnect call was made. + virtual status_t connect(int api); + + // disconnect attempts to disconnect a client API from the SurfaceMediaSource. + // Calling this method will cause any subsequent calls to other + // ISurfaceTexture methods to fail except for getAllocator and connect. + // Successfully calling connect after this will allow the other methods to + // succeed again. + // + // This method will fail if the the SurfaceMediaSource is not currently + // connected to the specified client API. + virtual status_t disconnect(int api); + + // getqueuedCount returns the number of queued frames waiting in the + // FIFO. In asynchronous mode, this always returns 0 or 1 since + // frames are not accumulating in the FIFO. + size_t getQueuedCount() const; + + // setBufferCountServer set the buffer count. If the client has requested + // a buffer count using setBufferCount, the server-buffer count will + // take effect once the client sets the count back to zero. + status_t setBufferCountServer(int bufferCount); + + // getTimestamp retrieves the timestamp associated with the image + // set by the most recent call to updateFrameInfoLocked(). + // + // The timestamp is in nanoseconds, and is monotonically increasing. Its + // other semantics (zero point, etc) are source-dependent and should be + // documented by the source. + int64_t getTimestamp(); + + // setFrameAvailableListener sets the listener object that will be notified + // when a new frame becomes available. + void setFrameAvailableListener(const sp<FrameAvailableListener>& listener); + + // getCurrentBuffer returns the buffer associated with the current image. + sp<GraphicBuffer> getCurrentBuffer() const; + + // dump our state in a String + void dump(String8& result) const; + void dump(String8& result, const char* prefix, char* buffer, + size_t SIZE) const; + + // isMetaDataStoredInVideoBuffers tells the encoder whether we will + // pass metadata through the buffers. Currently, it is force set to true + bool isMetaDataStoredInVideoBuffers() const; + +protected: + + // freeAllBuffers frees the resources (both GraphicBuffer and EGLImage) for + // all slots. + void freeAllBuffers(); + static bool isExternalFormat(uint32_t format); + +private: + + status_t setBufferCountServerLocked(int bufferCount); + + enum { INVALID_BUFFER_SLOT = -1 }; + + struct BufferSlot { + + BufferSlot() + : mBufferState(BufferSlot::FREE), + mRequestBufferCalled(false), + mTimestamp(0) { + } + + // mGraphicBuffer points to the buffer allocated for this slot or is + // NULL if no buffer has been allocated. + sp<GraphicBuffer> mGraphicBuffer; + + // BufferState represents the different states in which a buffer slot + // can be. + enum BufferState { + // FREE indicates that the buffer is not currently being used and + // will not be used in the future until it gets dequeued and + // subseqently queued by the client. + FREE = 0, + + // DEQUEUED indicates that the buffer has been dequeued by the + // client, but has not yet been queued or canceled. The buffer is + // considered 'owned' by the client, and the server should not use + // it for anything. + // + // Note that when in synchronous-mode (mSynchronousMode == true), + // the buffer that's currently attached to the texture may be + // dequeued by the client. That means that the current buffer can + // be in either the DEQUEUED or QUEUED state. In asynchronous mode, + // however, the current buffer is always in the QUEUED state. + DEQUEUED = 1, + + // QUEUED indicates that the buffer has been queued by the client, + // and has not since been made available for the client to dequeue. + // Attaching the buffer to the texture does NOT transition the + // buffer away from the QUEUED state. However, in Synchronous mode + // the current buffer may be dequeued by the client under some + // circumstances. See the note about the current buffer in the + // documentation for DEQUEUED. + QUEUED = 2, + }; + + // mBufferState is the current state of this buffer slot. + BufferState mBufferState; + + // mRequestBufferCalled is used for validating that the client did + // call requestBuffer() when told to do so. Technically this is not + // needed but useful for debugging and catching client bugs. + bool mRequestBufferCalled; + + // mTimestamp is the current timestamp for this buffer slot. This gets + // to set by queueBuffer each time this slot is queued. + int64_t mTimestamp; + }; + + // mSlots is the array of buffer slots that must be mirrored on the client + // side. This allows buffer ownership to be transferred between the client + // and server without sending a GraphicBuffer over binder. The entire array + // is initialized to NULL at construction time, and buffers are allocated + // for a slot when requestBuffer is called with that slot's index. + BufferSlot mSlots[NUM_BUFFER_SLOTS]; + + // mDefaultWidth holds the default width of allocated buffers. It is used + // in requestBuffers() if a width and height of zero is specified. + uint32_t mDefaultWidth; + + // mDefaultHeight holds the default height of allocated buffers. It is used + // in requestBuffers() if a width and height of zero is specified. + uint32_t mDefaultHeight; + + // mPixelFormat holds the pixel format of allocated buffers. It is used + // in requestBuffers() if a format of zero is specified. + uint32_t mPixelFormat; + + // mBufferCount is the number of buffer slots that the client and server + // must maintain. It defaults to MIN_ASYNC_BUFFER_SLOTS and can be changed + // by calling setBufferCount or setBufferCountServer + int mBufferCount; + + // mClientBufferCount is the number of buffer slots requested by the + // client. The default is zero, which means the client doesn't care how + // many buffers there are + int mClientBufferCount; + + // mServerBufferCount buffer count requested by the server-side + int mServerBufferCount; + + // mCurrentSlot is the buffer slot index of the buffer that is currently + // being used by buffer consumer + // (e.g. StageFrightRecorder in the case of SurfaceMediaSource or GLTexture + // in the case of SurfaceTexture). + // It is initialized to INVALID_BUFFER_SLOT, + // indicating that no buffer slot is currently bound to the texture. Note, + // however, that a value of INVALID_BUFFER_SLOT does not necessarily mean + // that no buffer is bound to the texture. A call to setBufferCount will + // reset mCurrentTexture to INVALID_BUFFER_SLOT. + int mCurrentSlot; + + + // mCurrentBuf is the graphic buffer of the current slot to be used by + // buffer consumer. It's possible that this buffer is not associated + // with any buffer slot, so we must track it separately in order to + // properly use IGraphicBufferAlloc::freeAllGraphicBuffersExcept. + sp<GraphicBuffer> mCurrentBuf; + + + // mCurrentTimestamp is the timestamp for the current texture. It + // gets set to mLastQueuedTimestamp each time updateTexImage is called. + int64_t mCurrentTimestamp; + + // mGraphicBufferAlloc is the connection to SurfaceFlinger that is used to + // allocate new GraphicBuffer objects. + sp<IGraphicBufferAlloc> mGraphicBufferAlloc; + + // mFrameAvailableListener is the listener object that will be called when a + // new frame becomes available. If it is not NULL it will be called from + // queueBuffer. + sp<FrameAvailableListener> mFrameAvailableListener; + + // mSynchronousMode whether we're in synchronous mode or not + bool mSynchronousMode; + + // mConnectedApi indicates the API that is currently connected to this + // SurfaceTexture. It defaults to NO_CONNECTED_API (= 0), and gets updated + // by the connect and disconnect methods. + int mConnectedApi; + + // mDequeueCondition condition used for dequeueBuffer in synchronous mode + mutable Condition mDequeueCondition; + + + // mQueue is a FIFO of queued buffers used in synchronous mode + typedef Vector<int> Fifo; + Fifo mQueue; + + // mMutex is the mutex used to prevent concurrent access to the member + // variables of SurfaceMediaSource objects. It must be locked whenever the + // member variables are accessed. + mutable Mutex mMutex; + + ////////////////////////// For MediaSource + // Set to a default of 30 fps if not specified by the client side + int32_t mFrameRate; + + // mStarted is a flag to check if the recording has started + bool mStarted; + + // mFrameAvailableCondition condition used to indicate whether there + // is a frame available for dequeuing + Condition mFrameAvailableCondition; + Condition mFrameCompleteCondition; + + // Avoid copying and equating and default constructor + DISALLOW_IMPLICIT_CONSTRUCTORS(SurfaceMediaSource); +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_GUI_SURFACEMEDIASOURCE_H diff --git a/include/media/stagefright/openmax/OMX_IVCommon.h b/include/media/stagefright/openmax/OMX_IVCommon.h index 7ed072b73f96..97170d7e7dbe 100644 --- a/include/media/stagefright/openmax/OMX_IVCommon.h +++ b/include/media/stagefright/openmax/OMX_IVCommon.h @@ -16,29 +16,29 @@ * ------------------------------------------------------------------- */ /** - * Copyright (c) 2008 The Khronos Group Inc. - * + * Copyright (c) 2008 The Khronos Group Inc. + * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject - * to the following conditions: + * to the following conditions: * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * + * in all copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ -/** +/** * @file OMX_IVCommon.h - OpenMax IL version 1.1.2 * The structures needed by Video and Image components to exchange * parameters and configuration data with the components. @@ -53,7 +53,7 @@ extern "C" { /** * Each OMX header must include all required header files to allow the header * to compile without errors. The includes below are required for this header - * file to compile successfully + * file to compile successfully */ #include <OMX_Core.h> @@ -64,8 +64,8 @@ extern "C" { */ -/** - * Enumeration defining possible uncompressed image/video formats. +/** + * Enumeration defining possible uncompressed image/video formats. * * ENUMS: * Unused : Placeholder value when format is N/A @@ -113,7 +113,7 @@ typedef enum OMX_COLOR_FORMATTYPE { OMX_COLOR_Format16bitBGR565, OMX_COLOR_Format18bitRGB666, OMX_COLOR_Format18bitARGB1665, - OMX_COLOR_Format19bitARGB1666, + OMX_COLOR_Format19bitARGB1666, OMX_COLOR_Format24bitRGB888, OMX_COLOR_Format24bitBGR888, OMX_COLOR_Format24bitARGB1887, @@ -136,55 +136,62 @@ typedef enum OMX_COLOR_FORMATTYPE { OMX_COLOR_FormatRawBayer8bit, OMX_COLOR_FormatRawBayer10bit, OMX_COLOR_FormatRawBayer8bitcompressed, - OMX_COLOR_FormatL2, - OMX_COLOR_FormatL4, - OMX_COLOR_FormatL8, - OMX_COLOR_FormatL16, - OMX_COLOR_FormatL24, + OMX_COLOR_FormatL2, + OMX_COLOR_FormatL4, + OMX_COLOR_FormatL8, + OMX_COLOR_FormatL16, + OMX_COLOR_FormatL24, OMX_COLOR_FormatL32, OMX_COLOR_FormatYUV420PackedSemiPlanar, OMX_COLOR_FormatYUV422PackedSemiPlanar, OMX_COLOR_Format18BitBGR666, OMX_COLOR_Format24BitARGB6666, OMX_COLOR_Format24BitABGR6666, - OMX_COLOR_FormatKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_COLOR_FormatKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_COLOR_FormatVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ + /**<Reserved android opaque colorformat. Tells the encoder that + * the actual colorformat will be relayed by the + * Gralloc Buffers. + * FIXME: In the process of reserving some enum values for + * Android-specific OMX IL colorformats. Change this enum to + * an acceptable range once that is done.*/ + OMX_COLOR_FormatAndroidOpaque = 0x7F000001, OMX_TI_COLOR_FormatYUV420PackedSemiPlanar = 0x7F000100, OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00, OMX_COLOR_FormatMax = 0x7FFFFFFF } OMX_COLOR_FORMATTYPE; -/** +/** * Defines the matrix for conversion from RGB to YUV or vice versa. - * iColorMatrix should be initialized with the fixed point values + * iColorMatrix should be initialized with the fixed point values * used in converting between formats. */ typedef struct OMX_CONFIG_COLORCONVERSIONTYPE { OMX_U32 nSize; /**< Size of the structure in bytes */ - OMX_VERSIONTYPE nVersion; /**< OMX specification version info */ + OMX_VERSIONTYPE nVersion; /**< OMX specification version info */ OMX_U32 nPortIndex; /**< Port that this struct applies to */ OMX_S32 xColorMatrix[3][3]; /**< Stored in signed Q16 format */ OMX_S32 xColorOffset[4]; /**< Stored in signed Q16 format */ }OMX_CONFIG_COLORCONVERSIONTYPE; -/** - * Structure defining percent to scale each frame dimension. For example: +/** + * Structure defining percent to scale each frame dimension. For example: * To make the width 50% larger, use fWidth = 1.5 and to make the width * 1/2 the original size, use fWidth = 0.5 */ typedef struct OMX_CONFIG_SCALEFACTORTYPE { OMX_U32 nSize; /**< Size of the structure in bytes */ - OMX_VERSIONTYPE nVersion; /**< OMX specification version info */ + OMX_VERSIONTYPE nVersion; /**< OMX specification version info */ OMX_U32 nPortIndex; /**< Port that this struct applies to */ OMX_S32 xWidth; /**< Fixed point value stored as Q16 */ OMX_S32 xHeight; /**< Fixed point value stored as Q16 */ }OMX_CONFIG_SCALEFACTORTYPE; -/** - * Enumeration of possible image filter types +/** + * Enumeration of possible image filter types */ typedef enum OMX_IMAGEFILTERTYPE { OMX_ImageFilterNone, @@ -195,23 +202,23 @@ typedef enum OMX_IMAGEFILTERTYPE { OMX_ImageFilterOilPaint, OMX_ImageFilterHatch, OMX_ImageFilterGpen, - OMX_ImageFilterAntialias, - OMX_ImageFilterDeRing, + OMX_ImageFilterAntialias, + OMX_ImageFilterDeRing, OMX_ImageFilterSolarize, - OMX_ImageFilterKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_ImageFilterKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_ImageFilterVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ OMX_ImageFilterMax = 0x7FFFFFFF } OMX_IMAGEFILTERTYPE; -/** - * Image filter configuration +/** + * Image filter configuration * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes + * nSize : Size of the structure in bytes * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to - * eImageFilter : Image filter type enumeration + * nPortIndex : Port that this structure applies to + * eImageFilter : Image filter type enumeration */ typedef struct OMX_CONFIG_IMAGEFILTERTYPE { OMX_U32 nSize; @@ -221,22 +228,22 @@ typedef struct OMX_CONFIG_IMAGEFILTERTYPE { } OMX_CONFIG_IMAGEFILTERTYPE; -/** - * Customized U and V for color enhancement +/** + * Customized U and V for color enhancement * * STRUCT MEMBERS: * nSize : Size of the structure in bytes - * nVersion : OMX specification version information + * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to * bColorEnhancement : Enable/disable color enhancement - * nCustomizedU : Practical values: 16-240, range: 0-255, value set for + * nCustomizedU : Practical values: 16-240, range: 0-255, value set for * U component - * nCustomizedV : Practical values: 16-240, range: 0-255, value set for + * nCustomizedV : Practical values: 16-240, range: 0-255, value set for * V component */ typedef struct OMX_CONFIG_COLORENHANCEMENTTYPE { OMX_U32 nSize; - OMX_VERSIONTYPE nVersion; + OMX_VERSIONTYPE nVersion; OMX_U32 nPortIndex; OMX_BOOL bColorEnhancement; OMX_U8 nCustomizedU; @@ -244,12 +251,12 @@ typedef struct OMX_CONFIG_COLORENHANCEMENTTYPE { } OMX_CONFIG_COLORENHANCEMENTTYPE; -/** - * Define color key and color key mask +/** + * Define color key and color key mask * * STRUCT MEMBERS: * nSize : Size of the structure in bytes - * nVersion : OMX specification version information + * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to * nARGBColor : 32bit Alpha, Red, Green, Blue Color * nARGBMask : 32bit Mask for Alpha, Red, Green, Blue channels @@ -263,12 +270,12 @@ typedef struct OMX_CONFIG_COLORKEYTYPE { } OMX_CONFIG_COLORKEYTYPE; -/** - * List of color blend types for pre/post processing +/** + * List of color blend types for pre/post processing * * ENUMS: * None : No color blending present - * AlphaConstant : Function is (alpha_constant * src) + + * AlphaConstant : Function is (alpha_constant * src) + * (1 - alpha_constant) * dst) * AlphaPerPixel : Function is (alpha * src) + (1 - alpha) * dst) * Alternate : Function is alternating pixels from src and dst @@ -284,21 +291,21 @@ typedef enum OMX_COLORBLENDTYPE { OMX_ColorBlendAnd, OMX_ColorBlendOr, OMX_ColorBlendInvert, - OMX_ColorBlendKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_ColorBlendKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_ColorBlendVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ OMX_ColorBlendMax = 0x7FFFFFFF } OMX_COLORBLENDTYPE; -/** - * Color blend configuration +/** + * Color blend configuration * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes - * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to + * nSize : Size of the structure in bytes + * nVersion : OMX specification version information + * nPortIndex : Port that this structure applies to * nRGBAlphaConstant : Constant global alpha values when global alpha is used - * eColorBlend : Color blend type enumeration + * eColorBlend : Color blend type enumeration */ typedef struct OMX_CONFIG_COLORBLENDTYPE { OMX_U32 nSize; @@ -309,15 +316,15 @@ typedef struct OMX_CONFIG_COLORBLENDTYPE { } OMX_CONFIG_COLORBLENDTYPE; -/** +/** * Hold frame dimension * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes + * nSize : Size of the structure in bytes * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to - * nWidth : Frame width in pixels - * nHeight : Frame height in pixels + * nPortIndex : Port that this structure applies to + * nWidth : Frame width in pixels + * nHeight : Frame height in pixels */ typedef struct OMX_FRAMESIZETYPE { OMX_U32 nSize; @@ -329,69 +336,69 @@ typedef struct OMX_FRAMESIZETYPE { /** - * Rotation configuration + * Rotation configuration * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes + * nSize : Size of the structure in bytes * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to - * nRotation : +/- integer rotation value + * nRotation : +/- integer rotation value */ typedef struct OMX_CONFIG_ROTATIONTYPE { OMX_U32 nSize; OMX_VERSIONTYPE nVersion; OMX_U32 nPortIndex; - OMX_S32 nRotation; + OMX_S32 nRotation; } OMX_CONFIG_ROTATIONTYPE; -/** - * Possible mirroring directions for pre/post processing +/** + * Possible mirroring directions for pre/post processing * * ENUMS: - * None : No mirroring - * Vertical : Vertical mirroring, flip on X axis - * Horizontal : Horizontal mirroring, flip on Y axis + * None : No mirroring + * Vertical : Vertical mirroring, flip on X axis + * Horizontal : Horizontal mirroring, flip on Y axis * Both : Both vertical and horizontal mirroring */ typedef enum OMX_MIRRORTYPE { OMX_MirrorNone = 0, OMX_MirrorVertical, OMX_MirrorHorizontal, - OMX_MirrorBoth, - OMX_MirrorKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_MirrorBoth, + OMX_MirrorKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_MirrorVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ - OMX_MirrorMax = 0x7FFFFFFF + OMX_MirrorMax = 0x7FFFFFFF } OMX_MIRRORTYPE; -/** - * Mirroring configuration +/** + * Mirroring configuration * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes + * nSize : Size of the structure in bytes * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to - * eMirror : Mirror type enumeration + * nPortIndex : Port that this structure applies to + * eMirror : Mirror type enumeration */ typedef struct OMX_CONFIG_MIRRORTYPE { OMX_U32 nSize; - OMX_VERSIONTYPE nVersion; + OMX_VERSIONTYPE nVersion; OMX_U32 nPortIndex; OMX_MIRRORTYPE eMirror; } OMX_CONFIG_MIRRORTYPE; -/** - * Position information only +/** + * Position information only * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes + * nSize : Size of the structure in bytes * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to - * nX : X coordinate for the point - * nY : Y coordinate for the point - */ + * nX : X coordinate for the point + * nY : Y coordinate for the point + */ typedef struct OMX_CONFIG_POINTTYPE { OMX_U32 nSize; OMX_VERSIONTYPE nVersion; @@ -401,37 +408,37 @@ typedef struct OMX_CONFIG_POINTTYPE { } OMX_CONFIG_POINTTYPE; -/** - * Frame size plus position +/** + * Frame size plus position * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes - * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to + * nSize : Size of the structure in bytes + * nVersion : OMX specification version information + * nPortIndex : Port that this structure applies to * nLeft : X Coordinate of the top left corner of the rectangle * nTop : Y Coordinate of the top left corner of the rectangle - * nWidth : Width of the rectangle - * nHeight : Height of the rectangle + * nWidth : Width of the rectangle + * nHeight : Height of the rectangle */ typedef struct OMX_CONFIG_RECTTYPE { OMX_U32 nSize; - OMX_VERSIONTYPE nVersion; - OMX_U32 nPortIndex; - OMX_S32 nLeft; + OMX_VERSIONTYPE nVersion; + OMX_U32 nPortIndex; + OMX_S32 nLeft; OMX_S32 nTop; OMX_U32 nWidth; OMX_U32 nHeight; } OMX_CONFIG_RECTTYPE; -/** - * Deblocking state; it is required to be set up before starting the codec +/** + * Deblocking state; it is required to be set up before starting the codec * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes - * nVersion : OMX specification version information + * nSize : Size of the structure in bytes + * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to - * bDeblocking : Enable/disable deblocking mode + * bDeblocking : Enable/disable deblocking mode */ typedef struct OMX_PARAM_DEBLOCKINGTYPE { OMX_U32 nSize; @@ -441,13 +448,13 @@ typedef struct OMX_PARAM_DEBLOCKINGTYPE { } OMX_PARAM_DEBLOCKINGTYPE; -/** - * Stabilization state +/** + * Stabilization state * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes - * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to + * nSize : Size of the structure in bytes + * nVersion : OMX specification version information + * nPortIndex : Port that this structure applies to * bStab : Enable/disable frame stabilization state */ typedef struct OMX_CONFIG_FRAMESTABTYPE { @@ -458,8 +465,8 @@ typedef struct OMX_CONFIG_FRAMESTABTYPE { } OMX_CONFIG_FRAMESTABTYPE; -/** - * White Balance control type +/** + * White Balance control type * * STRUCT MEMBERS: * SunLight : Referenced in JSR-234 @@ -476,20 +483,20 @@ typedef enum OMX_WHITEBALCONTROLTYPE { OMX_WhiteBalControlIncandescent, OMX_WhiteBalControlFlash, OMX_WhiteBalControlHorizon, - OMX_WhiteBalControlKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_WhiteBalControlKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_WhiteBalControlVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ OMX_WhiteBalControlMax = 0x7FFFFFFF } OMX_WHITEBALCONTROLTYPE; -/** - * White Balance control configuration +/** + * White Balance control configuration * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes + * nSize : Size of the structure in bytes * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to - * eWhiteBalControl : White balance enumeration + * nPortIndex : Port that this structure applies to + * eWhiteBalControl : White balance enumeration */ typedef struct OMX_CONFIG_WHITEBALCONTROLTYPE { OMX_U32 nSize; @@ -499,8 +506,8 @@ typedef struct OMX_CONFIG_WHITEBALCONTROLTYPE { } OMX_CONFIG_WHITEBALCONTROLTYPE; -/** - * Exposure control type +/** + * Exposure control type */ typedef enum OMX_EXPOSURECONTROLTYPE { OMX_ExposureControlOff = 0, @@ -513,20 +520,20 @@ typedef enum OMX_EXPOSURECONTROLTYPE { OMX_ExposureControlBeach, OMX_ExposureControlLargeAperture, OMX_ExposureControlSmallApperture, - OMX_ExposureControlKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_ExposureControlKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_ExposureControlVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ OMX_ExposureControlMax = 0x7FFFFFFF } OMX_EXPOSURECONTROLTYPE; -/** - * White Balance control configuration +/** + * White Balance control configuration * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes + * nSize : Size of the structure in bytes * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to - * eExposureControl : Exposure control enumeration + * nPortIndex : Port that this structure applies to + * eExposureControl : Exposure control enumeration */ typedef struct OMX_CONFIG_EXPOSURECONTROLTYPE { OMX_U32 nSize; @@ -536,16 +543,16 @@ typedef struct OMX_CONFIG_EXPOSURECONTROLTYPE { } OMX_CONFIG_EXPOSURECONTROLTYPE; -/** - * Defines sensor supported mode. +/** + * Defines sensor supported mode. * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes + * nSize : Size of the structure in bytes * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to - * nFrameRate : Single shot mode is indicated by a 0 + * nPortIndex : Port that this structure applies to + * nFrameRate : Single shot mode is indicated by a 0 * bOneShot : Enable for single shot, disable for streaming - * sFrameSize : Framesize + * sFrameSize : Framesize */ typedef struct OMX_PARAM_SENSORMODETYPE { OMX_U32 nSize; @@ -557,13 +564,13 @@ typedef struct OMX_PARAM_SENSORMODETYPE { } OMX_PARAM_SENSORMODETYPE; -/** - * Defines contrast level +/** + * Defines contrast level * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes - * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to + * nSize : Size of the structure in bytes + * nVersion : OMX specification version information + * nPortIndex : Port that this structure applies to * nContrast : Values allowed for contrast -100 to 100, zero means no change */ typedef struct OMX_CONFIG_CONTRASTTYPE { @@ -574,14 +581,14 @@ typedef struct OMX_CONFIG_CONTRASTTYPE { } OMX_CONFIG_CONTRASTTYPE; -/** - * Defines brightness level +/** + * Defines brightness level * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes - * nVersion : OMX specification version information - * nPortIndex : Port that this structure applies to - * nBrightness : 0-100% + * nSize : Size of the structure in bytes + * nVersion : OMX specification version information + * nPortIndex : Port that this structure applies to + * nBrightness : 0-100% */ typedef struct OMX_CONFIG_BRIGHTNESSTYPE { OMX_U32 nSize; @@ -591,16 +598,16 @@ typedef struct OMX_CONFIG_BRIGHTNESSTYPE { } OMX_CONFIG_BRIGHTNESSTYPE; -/** - * Defines backlight level configuration for a video sink, e.g. LCD panel +/** + * Defines backlight level configuration for a video sink, e.g. LCD panel * * STRUCT MEMBERS: * nSize : Size of the structure in bytes - * nVersion : OMX specification version information + * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to * nBacklight : Values allowed for backlight 0-100% - * nTimeout : Number of milliseconds before backlight automatically turns - * off. A value of 0x0 disables backight timeout + * nTimeout : Number of milliseconds before backlight automatically turns + * off. A value of 0x0 disables backight timeout */ typedef struct OMX_CONFIG_BACKLIGHTTYPE { OMX_U32 nSize; @@ -611,12 +618,12 @@ typedef struct OMX_CONFIG_BACKLIGHTTYPE { } OMX_CONFIG_BACKLIGHTTYPE; -/** - * Defines setting for Gamma +/** + * Defines setting for Gamma * * STRUCT MEMBERS: * nSize : Size of the structure in bytes - * nVersion : OMX specification version information + * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to * nGamma : Values allowed for gamma -100 to 100, zero means no change */ @@ -628,14 +635,14 @@ typedef struct OMX_CONFIG_GAMMATYPE { } OMX_CONFIG_GAMMATYPE; -/** - * Define for setting saturation - * +/** + * Define for setting saturation + * * STRUCT MEMBERS: * nSize : Size of the structure in bytes * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to - * nSaturation : Values allowed for saturation -100 to 100, zero means + * nSaturation : Values allowed for saturation -100 to 100, zero means * no change */ typedef struct OMX_CONFIG_SATURATIONTYPE { @@ -646,14 +653,14 @@ typedef struct OMX_CONFIG_SATURATIONTYPE { } OMX_CONFIG_SATURATIONTYPE; -/** - * Define for setting Lightness +/** + * Define for setting Lightness * * STRUCT MEMBERS: * nSize : Size of the structure in bytes * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to - * nLightness : Values allowed for lightness -100 to 100, zero means no + * nLightness : Values allowed for lightness -100 to 100, zero means no * change */ typedef struct OMX_CONFIG_LIGHTNESSTYPE { @@ -664,17 +671,17 @@ typedef struct OMX_CONFIG_LIGHTNESSTYPE { } OMX_CONFIG_LIGHTNESSTYPE; -/** - * Plane blend configuration +/** + * Plane blend configuration * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes + * nSize : Size of the structure in bytes * nVersion : OMX specification version information * nPortIndex : Index of input port associated with the plane. - * nDepth : Depth of the plane in relation to the screen. Higher - * numbered depths are "behind" lower number depths. + * nDepth : Depth of the plane in relation to the screen. Higher + * numbered depths are "behind" lower number depths. * This number defaults to the Port Index number. - * nAlpha : Transparency blending component for the entire plane. + * nAlpha : Transparency blending component for the entire plane. * See blending modes for more detail. */ typedef struct OMX_CONFIG_PLANEBLENDTYPE { @@ -686,17 +693,17 @@ typedef struct OMX_CONFIG_PLANEBLENDTYPE { } OMX_CONFIG_PLANEBLENDTYPE; -/** +/** * Define interlace type * * STRUCT MEMBERS: - * nSize : Size of the structure in bytes - * nVersion : OMX specification version information + * nSize : Size of the structure in bytes + * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to - * bEnable : Enable control variable for this functionality + * bEnable : Enable control variable for this functionality * (see below) - * nInterleavePortIndex : Index of input or output port associated with - * the interleaved plane. + * nInterleavePortIndex : Index of input or output port associated with + * the interleaved plane. * pPlanarPortIndexes[4] : Index of input or output planar ports. */ typedef struct OMX_PARAM_INTERLEAVETYPE { @@ -708,8 +715,8 @@ typedef struct OMX_PARAM_INTERLEAVETYPE { } OMX_PARAM_INTERLEAVETYPE; -/** - * Defines the picture effect used for an input picture +/** + * Defines the picture effect used for an input picture */ typedef enum OMX_TRANSITIONEFFECTTYPE { OMX_EffectNone, @@ -719,18 +726,18 @@ typedef enum OMX_TRANSITIONEFFECTTYPE { OMX_EffectDissolve, OMX_EffectWipe, OMX_EffectUnspecifiedMixOfTwoScenes, - OMX_EffectKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_EffectKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_EffectVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ OMX_EffectMax = 0x7FFFFFFF } OMX_TRANSITIONEFFECTTYPE; -/** - * Structure used to configure current transition effect +/** + * Structure used to configure current transition effect * * STRUCT MEMBERS: * nSize : Size of the structure in bytes - * nVersion : OMX specification version information + * nVersion : OMX specification version information * nPortIndex : Port that this structure applies to * eEffect : Effect to enable */ @@ -742,43 +749,43 @@ typedef struct OMX_CONFIG_TRANSITIONEFFECTTYPE { } OMX_CONFIG_TRANSITIONEFFECTTYPE; -/** - * Defines possible data unit types for encoded video data. The data unit +/** + * Defines possible data unit types for encoded video data. The data unit * types are used both for encoded video input for playback as well as - * encoded video output from recording. + * encoded video output from recording. */ typedef enum OMX_DATAUNITTYPE { OMX_DataUnitCodedPicture, OMX_DataUnitVideoSegment, OMX_DataUnitSeveralSegments, OMX_DataUnitArbitraryStreamSection, - OMX_DataUnitKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_DataUnitKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_DataUnitVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ OMX_DataUnitMax = 0x7FFFFFFF } OMX_DATAUNITTYPE; -/** - * Defines possible encapsulation types for coded video data unit. The - * encapsulation information is used both for encoded video input for - * playback as well as encoded video output from recording. +/** + * Defines possible encapsulation types for coded video data unit. The + * encapsulation information is used both for encoded video input for + * playback as well as encoded video output from recording. */ typedef enum OMX_DATAUNITENCAPSULATIONTYPE { OMX_DataEncapsulationElementaryStream, OMX_DataEncapsulationGenericPayload, OMX_DataEncapsulationRtpPayload, - OMX_DataEncapsulationKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_DataEncapsulationKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_DataEncapsulationVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ OMX_DataEncapsulationMax = 0x7FFFFFFF } OMX_DATAUNITENCAPSULATIONTYPE; -/** - * Structure used to configure the type of being decoded/encoded +/** + * Structure used to configure the type of being decoded/encoded */ typedef struct OMX_PARAM_DATAUNITTYPE { OMX_U32 nSize; /**< Size of the structure in bytes */ - OMX_VERSIONTYPE nVersion; /**< OMX specification version information */ + OMX_VERSIONTYPE nVersion; /**< OMX specification version information */ OMX_U32 nPortIndex; /**< Port that this structure applies to */ OMX_DATAUNITTYPE eUnitType; OMX_DATAUNITENCAPSULATIONTYPE eEncapsulationType; @@ -786,25 +793,25 @@ typedef struct OMX_PARAM_DATAUNITTYPE { /** - * Defines dither types + * Defines dither types */ typedef enum OMX_DITHERTYPE { OMX_DitherNone, OMX_DitherOrdered, OMX_DitherErrorDiffusion, OMX_DitherOther, - OMX_DitherKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_DitherKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_DitherVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ OMX_DitherMax = 0x7FFFFFFF } OMX_DITHERTYPE; -/** - * Structure used to configure current type of dithering +/** + * Structure used to configure current type of dithering */ typedef struct OMX_CONFIG_DITHERTYPE { OMX_U32 nSize; /**< Size of the structure in bytes */ - OMX_VERSIONTYPE nVersion; /**< OMX specification version information */ + OMX_VERSIONTYPE nVersion; /**< OMX specification version information */ OMX_U32 nPortIndex; /**< Port that this structure applies to */ OMX_DITHERTYPE eDither; /**< Type of dithering to use */ } OMX_CONFIG_DITHERTYPE; @@ -813,28 +820,28 @@ typedef struct OMX_CONFIG_CAPTUREMODETYPE { OMX_U32 nSize; OMX_VERSIONTYPE nVersion; OMX_U32 nPortIndex; /**< Port that this structure applies to */ - OMX_BOOL bContinuous; /**< If true then ignore frame rate and emit capture + OMX_BOOL bContinuous; /**< If true then ignore frame rate and emit capture * data as fast as possible (otherwise obey port's frame rate). */ - OMX_BOOL bFrameLimited; /**< If true then terminate capture after the port emits the - * specified number of frames (otherwise the port does not - * terminate the capture until instructed to do so by the client). - * Even if set, the client may manually terminate the capture prior + OMX_BOOL bFrameLimited; /**< If true then terminate capture after the port emits the + * specified number of frames (otherwise the port does not + * terminate the capture until instructed to do so by the client). + * Even if set, the client may manually terminate the capture prior * to reaching the limit. */ OMX_U32 nFrameLimit; /**< Limit on number of frames emitted during a capture (only * valid if bFrameLimited is set). */ } OMX_CONFIG_CAPTUREMODETYPE; typedef enum OMX_METERINGTYPE { - + OMX_MeteringModeAverage, /**< Center-weighted average metering. */ OMX_MeteringModeSpot, /**< Spot (partial) metering. */ OMX_MeteringModeMatrix, /**< Matrix or evaluative metering. */ - - OMX_MeteringKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + + OMX_MeteringKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_MeteringVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ OMX_EVModeMax = 0x7fffffff } OMX_METERINGTYPE; - + typedef struct OMX_CONFIG_EXPOSUREVALUETYPE { OMX_U32 nSize; OMX_VERSIONTYPE nVersion; @@ -843,14 +850,14 @@ typedef struct OMX_CONFIG_EXPOSUREVALUETYPE { OMX_S32 xEVCompensation; /**< Fixed point value stored as Q16 */ OMX_U32 nApertureFNumber; /**< e.g. nApertureFNumber = 2 implies "f/2" - Q16 format */ OMX_BOOL bAutoAperture; /**< Whether aperture number is defined automatically */ - OMX_U32 nShutterSpeedMsec; /**< Shutterspeed in milliseconds */ - OMX_BOOL bAutoShutterSpeed; /**< Whether shutter speed is defined automatically */ + OMX_U32 nShutterSpeedMsec; /**< Shutterspeed in milliseconds */ + OMX_BOOL bAutoShutterSpeed; /**< Whether shutter speed is defined automatically */ OMX_U32 nSensitivity; /**< e.g. nSensitivity = 100 implies "ISO 100" */ OMX_BOOL bAutoSensitivity; /**< Whether sensitivity is defined automatically */ } OMX_CONFIG_EXPOSUREVALUETYPE; -/** - * Focus region configuration +/** + * Focus region configuration * * STRUCT MEMBERS: * nSize : Size of the structure in bytes @@ -881,8 +888,8 @@ typedef struct OMX_CONFIG_FOCUSREGIONTYPE { OMX_BOOL bBottomRight; } OMX_CONFIG_FOCUSREGIONTYPE; -/** - * Focus Status type +/** + * Focus Status type */ typedef enum OMX_FOCUSSTATUSTYPE { OMX_FocusStatusOff = 0, @@ -890,13 +897,13 @@ typedef enum OMX_FOCUSSTATUSTYPE { OMX_FocusStatusReached, OMX_FocusStatusUnableToReach, OMX_FocusStatusLost, - OMX_FocusStatusKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ + OMX_FocusStatusKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */ OMX_FocusStatusVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */ OMX_FocusStatusMax = 0x7FFFFFFF } OMX_FOCUSSTATUSTYPE; -/** - * Focus status configuration +/** + * Focus status configuration * * STRUCT MEMBERS: * nSize : Size of the structure in bytes diff --git a/include/surfaceflinger/Surface.h b/include/surfaceflinger/Surface.h index 9c352ad9ab53..0460bbd35a8b 100644 --- a/include/surfaceflinger/Surface.h +++ b/include/surfaceflinger/Surface.h @@ -122,7 +122,10 @@ public: uint32_t reserved[2]; }; + explicit Surface(const sp<ISurfaceTexture>& st); + static status_t writeToParcel(const sp<Surface>& control, Parcel* parcel); + static sp<Surface> readFromParcel(const Parcel& data); static bool isValid(const sp<Surface>& surface) { return (surface != 0) && surface->isValid(); @@ -147,14 +150,14 @@ private: Surface& operator = (Surface& rhs); Surface(const Surface& rhs); - Surface(const sp<SurfaceControl>& control); + explicit Surface(const sp<SurfaceControl>& control); Surface(const Parcel& data, const sp<IBinder>& ref); ~Surface(); /* * private stuff... */ - void init(); + void init(const sp<ISurfaceTexture>& surfaceTexture); static void cleanCachedSurfacesLocked(); diff --git a/include/ui/PixelFormat.h b/include/ui/PixelFormat.h index f46f25c36d14..848c5a114907 100644 --- a/include/ui/PixelFormat.h +++ b/include/ui/PixelFormat.h @@ -55,7 +55,7 @@ enum { PIXEL_FORMAT_OPAQUE = -1, // System chooses an opaque format (no alpha bits required) - + // real pixel formats supported for rendering ----------------------------- PIXEL_FORMAT_RGBA_8888 = HAL_PIXEL_FORMAT_RGBA_8888, // 4x8-bit RGBA @@ -84,7 +84,7 @@ struct PixelFormatInfo INDEX_GREEN = 2, INDEX_BLUE = 3 }; - + enum { // components ALPHA = 1, RGB = 2, @@ -98,10 +98,10 @@ struct PixelFormatInfo uint8_t h; uint8_t l; }; - + inline PixelFormatInfo() : version(sizeof(PixelFormatInfo)) { } size_t getScanlineSize(unsigned int width) const; - size_t getSize(size_t ci) const { + size_t getSize(size_t ci) const { return (ci <= 3) ? (cinfo[ci].h - cinfo[ci].l) : 0; } size_t version; @@ -112,7 +112,7 @@ struct PixelFormatInfo szinfo cinfo[4]; struct { uint8_t h_alpha; - uint8_t l_alpha; + uint8_t l_alpha; uint8_t h_red; uint8_t l_red; uint8_t h_green; diff --git a/libs/gui/ISurfaceTexture.cpp b/libs/gui/ISurfaceTexture.cpp index be90e2eec00f..55246dc902e5 100644 --- a/libs/gui/ISurfaceTexture.cpp +++ b/libs/gui/ISurfaceTexture.cpp @@ -38,7 +38,6 @@ enum { CANCEL_BUFFER, SET_CROP, SET_TRANSFORM, - GET_ALLOCATOR, QUERY, SET_SYNCHRONOUS_MODE, CONNECT, @@ -55,18 +54,18 @@ public: { } - virtual sp<GraphicBuffer> requestBuffer(int bufferIdx) { + virtual status_t requestBuffer(int bufferIdx, sp<GraphicBuffer>* buf) { Parcel data, reply; data.writeInterfaceToken(ISurfaceTexture::getInterfaceDescriptor()); data.writeInt32(bufferIdx); remote()->transact(REQUEST_BUFFER, data, &reply); - sp<GraphicBuffer> buffer; bool nonNull = reply.readInt32(); if (nonNull) { - buffer = new GraphicBuffer(); - reply.read(*buffer); + *buf = new GraphicBuffer(); + reply.read(**buf); } - return buffer; + status_t result = reply.readInt32(); + return result; } virtual status_t setBufferCount(int bufferCount) @@ -144,13 +143,6 @@ public: return result; } - virtual sp<IBinder> getAllocator() { - Parcel data, reply; - data.writeInterfaceToken(ISurfaceTexture::getInterfaceDescriptor()); - remote()->transact(GET_ALLOCATOR, data, &reply); - return reply.readStrongBinder(); - } - virtual int query(int what, int* value) { Parcel data, reply; data.writeInterfaceToken(ISurfaceTexture::getInterfaceDescriptor()); @@ -200,11 +192,13 @@ status_t BnSurfaceTexture::onTransact( case REQUEST_BUFFER: { CHECK_INTERFACE(ISurfaceTexture, data, reply); int bufferIdx = data.readInt32(); - sp<GraphicBuffer> buffer(requestBuffer(bufferIdx)); + sp<GraphicBuffer> buffer; + int result = requestBuffer(bufferIdx, &buffer); reply->writeInt32(buffer != 0); if (buffer != 0) { reply->write(*buffer); } + reply->writeInt32(result); return NO_ERROR; } break; case SET_BUFFER_COUNT: { @@ -270,12 +264,6 @@ status_t BnSurfaceTexture::onTransact( reply->writeInt32(result); return NO_ERROR; } break; - case GET_ALLOCATOR: { - CHECK_INTERFACE(ISurfaceTexture, data, reply); - sp<IBinder> result = getAllocator(); - reply->writeStrongBinder(result); - return NO_ERROR; - } break; case QUERY: { CHECK_INTERFACE(ISurfaceTexture, data, reply); int value; diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index c4f9e53d5dcc..ccf98e500c9a 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -184,6 +184,7 @@ status_t SurfaceControl::writeSurfaceToParcel( identity = control->mIdentity; } parcel->writeStrongBinder(sur!=0 ? sur->asBinder() : NULL); + parcel->writeStrongBinder(NULL); // NULL ISurfaceTexture in this case. parcel->writeInt32(identity); return NO_ERROR; } @@ -192,7 +193,8 @@ sp<Surface> SurfaceControl::getSurface() const { Mutex::Autolock _l(mLock); if (mSurfaceData == 0) { - mSurfaceData = new Surface(const_cast<SurfaceControl*>(this)); + sp<SurfaceControl> surface_control(const_cast<SurfaceControl*>(this)); + mSurfaceData = new Surface(surface_control); } return mSurfaceData; } @@ -208,31 +210,58 @@ Surface::Surface(const sp<SurfaceControl>& surface) mSurface(surface->mSurface), mIdentity(surface->mIdentity) { - init(); + sp<ISurfaceTexture> st; + if (mSurface != NULL) { + st = mSurface->getSurfaceTexture(); + } + init(st); } Surface::Surface(const Parcel& parcel, const sp<IBinder>& ref) : SurfaceTextureClient() { - mSurface = interface_cast<ISurface>(ref); + mSurface = interface_cast<ISurface>(ref); + sp<IBinder> st_binder(parcel.readStrongBinder()); + sp<ISurfaceTexture> st; + if (st_binder != NULL) { + st = interface_cast<ISurfaceTexture>(st_binder); + } else if (mSurface != NULL) { + st = mSurface->getSurfaceTexture(); + } + mIdentity = parcel.readInt32(); - init(); + init(st); +} + +Surface::Surface(const sp<ISurfaceTexture>& st) + : SurfaceTextureClient(), + mSurface(NULL), + mIdentity(0) +{ + init(st); } status_t Surface::writeToParcel( const sp<Surface>& surface, Parcel* parcel) { sp<ISurface> sur; + sp<ISurfaceTexture> st; uint32_t identity = 0; if (Surface::isValid(surface)) { sur = surface->mSurface; + st = surface->getISurfaceTexture(); identity = surface->mIdentity; - } else if (surface != 0 && surface->mSurface != 0) { - LOGW("Parceling invalid surface with non-NULL ISurface as NULL: " - "mSurface = %p, mIdentity = %d", - surface->mSurface.get(), surface->mIdentity); + } else if (surface != 0 && + (surface->mSurface != NULL || + surface->getISurfaceTexture() != NULL)) { + LOGE("Parceling invalid surface with non-NULL ISurface/ISurfaceTexture as NULL: " + "mSurface = %p, surfaceTexture = %p, mIdentity = %d, ", + surface->mSurface.get(), surface->getISurfaceTexture().get(), + surface->mIdentity); } - parcel->writeStrongBinder(sur!=0 ? sur->asBinder() : NULL); + + parcel->writeStrongBinder(sur != NULL ? sur->asBinder() : NULL); + parcel->writeStrongBinder(st != NULL ? st->asBinder() : NULL); parcel->writeInt32(identity); return NO_ERROR; @@ -249,8 +278,8 @@ sp<Surface> Surface::readFromParcel(const Parcel& data) { surface = new Surface(data, binder); sCachedSurfaces.add(binder, surface); } - if (surface->mSurface == 0) { - surface = 0; + if (surface->mSurface == NULL && surface->getISurfaceTexture() == NULL) { + surface = 0; } cleanCachedSurfacesLocked(); return surface; @@ -267,10 +296,9 @@ void Surface::cleanCachedSurfacesLocked() { } } -void Surface::init() +void Surface::init(const sp<ISurfaceTexture>& surfaceTexture) { - if (mSurface != NULL) { - sp<ISurfaceTexture> surfaceTexture(mSurface->getSurfaceTexture()); + if (mSurface != NULL || surfaceTexture != NULL) { LOGE_IF(surfaceTexture==0, "got a NULL ISurfaceTexture from ISurface"); if (surfaceTexture != NULL) { setISurfaceTexture(surfaceTexture); diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp index 0f08570ef76f..8d199574f433 100644 --- a/libs/gui/SurfaceTexture.cpp +++ b/libs/gui/SurfaceTexture.cpp @@ -86,7 +86,6 @@ SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode) : mClientBufferCount(0), mServerBufferCount(MIN_ASYNC_BUFFER_SLOTS), mCurrentTexture(INVALID_BUFFER_SLOT), - mCurrentTextureTarget(GL_TEXTURE_EXTERNAL_OES), mCurrentTransform(0), mCurrentTimestamp(0), mNextTransform(0), @@ -94,7 +93,8 @@ SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode) : mTexName(tex), mSynchronousMode(false), mAllowSynchronousMode(allowSynchronousMode), - mConnectedApi(NO_CONNECTED_API) { + mConnectedApi(NO_CONNECTED_API), + mAbandoned(false) { LOGV("SurfaceTexture::SurfaceTexture"); sp<ISurfaceComposer> composer(ComposerService::getComposerService()); mGraphicBufferAlloc = composer->createGraphicBufferAlloc(); @@ -150,6 +150,11 @@ status_t SurfaceTexture::setBufferCount(int bufferCount) { LOGV("SurfaceTexture::setBufferCount"); Mutex::Autolock lock(mMutex); + if (mAbandoned) { + LOGE("setBufferCount: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + if (bufferCount > NUM_BUFFER_SLOTS) { LOGE("setBufferCount: bufferCount larger than slots available"); return BAD_VALUE; @@ -199,22 +204,32 @@ status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h) return OK; } -sp<GraphicBuffer> SurfaceTexture::requestBuffer(int buf) { +status_t SurfaceTexture::requestBuffer(int slot, sp<GraphicBuffer>* buf) { LOGV("SurfaceTexture::requestBuffer"); Mutex::Autolock lock(mMutex); - if (buf < 0 || mBufferCount <= buf) { + if (mAbandoned) { + LOGE("requestBuffer: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + if (slot < 0 || mBufferCount <= slot) { LOGE("requestBuffer: slot index out of range [0, %d]: %d", - mBufferCount, buf); - return 0; + mBufferCount, slot); + return BAD_VALUE; } - mSlots[buf].mRequestBufferCalled = true; - return mSlots[buf].mGraphicBuffer; + mSlots[slot].mRequestBufferCalled = true; + *buf = mSlots[slot].mGraphicBuffer; + return NO_ERROR; } status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, uint32_t format, uint32_t usage) { LOGV("SurfaceTexture::dequeueBuffer"); + if (mAbandoned) { + LOGE("dequeueBuffer: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + if ((w && !h) || (!w && h)) { LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h); return BAD_VALUE; @@ -252,6 +267,11 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, // wait for the FIFO to drain while (!mQueue.isEmpty()) { mDequeueCondition.wait(mMutex); + if (mAbandoned) { + LOGE("dequeueBuffer: SurfaceTexture was abandoned while " + "blocked!"); + return NO_INIT; + } } minBufferCountNeeded = mSynchronousMode ? MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS; @@ -380,6 +400,11 @@ status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, status_t SurfaceTexture::setSynchronousMode(bool enabled) { Mutex::Autolock lock(mMutex); + if (mAbandoned) { + LOGE("setSynchronousMode: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + status_t err = OK; if (!mAllowSynchronousMode && enabled) return err; @@ -410,6 +435,10 @@ status_t SurfaceTexture::queueBuffer(int buf, int64_t timestamp, { // scope for the lock Mutex::Autolock lock(mMutex); + if (mAbandoned) { + LOGE("queueBuffer: SurfaceTexture has been abandoned!"); + return NO_INIT; + } if (buf < 0 || buf >= mBufferCount) { LOGE("queueBuffer: slot index out of range [0, %d]: %d", mBufferCount, buf); @@ -475,6 +504,12 @@ status_t SurfaceTexture::queueBuffer(int buf, int64_t timestamp, void SurfaceTexture::cancelBuffer(int buf) { LOGV("SurfaceTexture::cancelBuffer"); Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + LOGW("cancelBuffer: SurfaceTexture has been abandoned!"); + return; + } + if (buf < 0 || buf >= mBufferCount) { LOGE("cancelBuffer: slot index out of range [0, %d]: %d", mBufferCount, buf); @@ -491,6 +526,10 @@ void SurfaceTexture::cancelBuffer(int buf) { status_t SurfaceTexture::setCrop(const Rect& crop) { LOGV("SurfaceTexture::setCrop"); Mutex::Autolock lock(mMutex); + if (mAbandoned) { + LOGE("setCrop: SurfaceTexture has been abandoned!"); + return NO_INIT; + } mNextCrop = crop; return OK; } @@ -498,6 +537,10 @@ status_t SurfaceTexture::setCrop(const Rect& crop) { status_t SurfaceTexture::setTransform(uint32_t transform) { LOGV("SurfaceTexture::setTransform"); Mutex::Autolock lock(mMutex); + if (mAbandoned) { + LOGE("setTransform: SurfaceTexture has been abandoned!"); + return NO_INIT; + } mNextTransform = transform; return OK; } @@ -505,6 +548,12 @@ status_t SurfaceTexture::setTransform(uint32_t transform) { status_t SurfaceTexture::connect(int api) { LOGV("SurfaceTexture::connect(this=%p, %d)", this, api); Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + LOGE("connect: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + int err = NO_ERROR; switch (api) { case NATIVE_WINDOW_API_EGL: @@ -529,6 +578,12 @@ status_t SurfaceTexture::connect(int api) { status_t SurfaceTexture::disconnect(int api) { LOGV("SurfaceTexture::disconnect(this=%p, %d)", this, api); Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + LOGE("connect: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + int err = NO_ERROR; switch (api) { case NATIVE_WINDOW_API_EGL: @@ -595,12 +650,8 @@ status_t SurfaceTexture::updateTexImage() { LOGW("updateTexImage: clearing GL error: %#04x", error); } - GLenum target = getTextureTarget(mSlots[buf].mGraphicBuffer->format); - if (target != mCurrentTextureTarget) { - glDeleteTextures(1, &mTexName); - } - glBindTexture(target, mTexName); - glEGLImageTargetTexture2DOES(target, (GLeglImageOES)image); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTexName); + glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)image); bool failed = false; while ((error = glGetError()) != GL_NO_ERROR) { @@ -622,7 +673,6 @@ status_t SurfaceTexture::updateTexImage() { // Update the SurfaceTexture state. mCurrentTexture = buf; - mCurrentTextureTarget = target; mCurrentTextureBuf = mSlots[buf].mGraphicBuffer; mCurrentCrop = mSlots[buf].mCrop; mCurrentTransform = mSlots[buf].mTransform; @@ -636,7 +686,7 @@ status_t SurfaceTexture::updateTexImage() { mDequeueCondition.signal(); } else { // We always bind the texture even if we don't update its contents. - glBindTexture(mCurrentTextureTarget, mTexName); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTexName); } return OK; @@ -661,20 +711,8 @@ bool SurfaceTexture::isExternalFormat(uint32_t format) return false; } -GLenum SurfaceTexture::getTextureTarget(uint32_t format) -{ - GLenum target = GL_TEXTURE_2D; -#if defined(GL_OES_EGL_image_external) - if (isExternalFormat(format)) { - target = GL_TEXTURE_EXTERNAL_OES; - } -#endif - return target; -} - GLenum SurfaceTexture::getCurrentTextureTarget() const { - Mutex::Autolock lock(mMutex); - return mCurrentTextureTarget; + return GL_TEXTURE_EXTERNAL_OES; } void SurfaceTexture::getTransformMatrix(float mtx[16]) { @@ -786,11 +824,6 @@ void SurfaceTexture::setFrameAvailableListener( mFrameAvailableListener = listener; } -sp<IBinder> SurfaceTexture::getAllocator() { - LOGV("SurfaceTexture::getAllocator"); - return mGraphicBufferAlloc->asBinder(); -} - void SurfaceTexture::freeAllBuffers() { for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { mSlots[i].mGraphicBuffer = 0; @@ -842,6 +875,12 @@ uint32_t SurfaceTexture::getCurrentScalingMode() const { int SurfaceTexture::query(int what, int* outValue) { Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + LOGE("query: SurfaceTexture has been abandoned!"); + return NO_INIT; + } + int value; switch (what) { case NATIVE_WINDOW_WIDTH: @@ -868,6 +907,13 @@ int SurfaceTexture::query(int what, int* outValue) return NO_ERROR; } +void SurfaceTexture::abandon() { + Mutex::Autolock lock(mMutex); + freeAllBuffers(); + mAbandoned = true; + mDequeueCondition.signal(); +} + void SurfaceTexture::dump(String8& result) const { char buffer[1024]; @@ -895,12 +941,12 @@ void SurfaceTexture::dump(String8& result, const char* prefix, } snprintf(buffer, SIZE, - "%scurrent: {crop=[%d,%d,%d,%d], transform=0x%02x, current=%d, target=0x%04x}\n" + "%scurrent: {crop=[%d,%d,%d,%d], transform=0x%02x, current=%d}\n" "%snext : {crop=[%d,%d,%d,%d], transform=0x%02x, FIFO(%d)={%s}}\n" , prefix, mCurrentCrop.left, mCurrentCrop.top, mCurrentCrop.right, mCurrentCrop.bottom, - mCurrentTransform, mCurrentTexture, mCurrentTextureTarget, + mCurrentTransform, mCurrentTexture, prefix, mNextCrop.left, mNextCrop.top, mNextCrop.right, mNextCrop.bottom, mCurrentTransform, fifoSize, fifo.string() ); diff --git a/libs/gui/SurfaceTextureClient.cpp b/libs/gui/SurfaceTextureClient.cpp index 1dc6cd2cf751..df0ad5abe3e2 100644 --- a/libs/gui/SurfaceTextureClient.cpp +++ b/libs/gui/SurfaceTextureClient.cpp @@ -55,6 +55,9 @@ void SurfaceTextureClient::init() { mQueryWidth = 0; mQueryHeight = 0; mQueryFormat = 0; + mDefaultWidth = 0; + mDefaultHeight = 0; + mTransformHint = 0; mConnectedToCpu = false; } @@ -62,9 +65,6 @@ void SurfaceTextureClient::setISurfaceTexture( const sp<ISurfaceTexture>& surfaceTexture) { mSurfaceTexture = surfaceTexture; - - // Get a reference to the allocator. - mAllocator = mSurfaceTexture->getAllocator(); } sp<ISurfaceTexture> SurfaceTextureClient::getISurfaceTexture() const { @@ -148,10 +148,11 @@ int SurfaceTextureClient::dequeueBuffer(android_native_buffer_t** buffer) { } if ((result & ISurfaceTexture::BUFFER_NEEDS_REALLOCATION) || gbuf == 0) { - gbuf = mSurfaceTexture->requestBuffer(buf); - if (gbuf == 0) { - LOGE("dequeueBuffer: ISurfaceTexture::requestBuffer failed"); - return NO_MEMORY; + result = mSurfaceTexture->requestBuffer(buf, &gbuf); + if (result != NO_ERROR) { + LOGE("dequeueBuffer: ISurfaceTexture::requestBuffer failed: %d", + result); + return result; } mQueryWidth = gbuf->width; mQueryHeight = gbuf->height; diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp index 9abe89d9709c..44babcf873b6 100644 --- a/libs/gui/tests/SurfaceTexture_test.cpp +++ b/libs/gui/tests/SurfaceTexture_test.cpp @@ -832,9 +832,7 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BuffersRepeatedly) { pt->requestExitAndWait(); } -// XXX: This test is disabled because there are currently no drivers that can -// handle RGBA textures with the GL_TEXTURE_EXTERNAL_OES target. -TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromCpuFilledRGBABufferNpot) { +TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferNpot) { const int texWidth = 64; const int texHeight = 66; @@ -871,26 +869,24 @@ TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromCpuFilledRGBABufferNpot) { EXPECT_TRUE(checkPixel( 0, 65, 35, 35, 35, 35)); EXPECT_TRUE(checkPixel(15, 10, 35, 231, 231, 231)); - EXPECT_TRUE(checkPixel(24, 63, 38, 228, 231, 35)); + EXPECT_TRUE(checkPixel(23, 65, 231, 35, 231, 35)); EXPECT_TRUE(checkPixel(19, 40, 35, 231, 35, 35)); EXPECT_TRUE(checkPixel(38, 30, 231, 35, 35, 35)); EXPECT_TRUE(checkPixel(42, 54, 35, 35, 35, 231)); - EXPECT_TRUE(checkPixel(37, 33, 228, 38, 38, 38)); + EXPECT_TRUE(checkPixel(37, 34, 35, 231, 231, 231)); EXPECT_TRUE(checkPixel(31, 8, 231, 35, 35, 231)); - EXPECT_TRUE(checkPixel(36, 47, 228, 35, 231, 231)); - EXPECT_TRUE(checkPixel(24, 63, 38, 228, 231, 35)); - EXPECT_TRUE(checkPixel(48, 3, 228, 228, 38, 35)); + EXPECT_TRUE(checkPixel(37, 47, 231, 35, 231, 231)); + EXPECT_TRUE(checkPixel(25, 38, 35, 35, 35, 35)); + EXPECT_TRUE(checkPixel(49, 6, 35, 231, 35, 35)); EXPECT_TRUE(checkPixel(54, 50, 35, 231, 231, 231)); - EXPECT_TRUE(checkPixel(24, 25, 41, 41, 231, 231)); - EXPECT_TRUE(checkPixel(10, 9, 38, 38, 231, 231)); + EXPECT_TRUE(checkPixel(27, 26, 231, 231, 231, 231)); + EXPECT_TRUE(checkPixel(10, 6, 35, 35, 231, 231)); EXPECT_TRUE(checkPixel(29, 4, 35, 35, 35, 231)); - EXPECT_TRUE(checkPixel(56, 31, 38, 228, 231, 35)); + EXPECT_TRUE(checkPixel(55, 28, 35, 35, 231, 35)); EXPECT_TRUE(checkPixel(58, 55, 35, 35, 231, 231)); } -// XXX: This test is disabled because there are currently no drivers that can -// handle RGBA textures with the GL_TEXTURE_EXTERNAL_OES target. -TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromCpuFilledRGBABufferPow2) { +TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferPow2) { const int texWidth = 64; const int texHeight = 64; @@ -944,9 +940,7 @@ TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromCpuFilledRGBABufferPow2) { EXPECT_TRUE(checkPixel( 3, 52, 35, 231, 35, 35)); } -// XXX: This test is disabled because there are currently no drivers that can -// handle RGBA textures with the GL_TEXTURE_EXTERNAL_OES target. -TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromGLFilledRGBABufferPow2) { +TEST_F(SurfaceTextureGLTest, TexturingFromGLFilledRGBABufferPow2) { const int texWidth = 64; const int texHeight = 64; @@ -956,7 +950,7 @@ TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromGLFilledRGBABufferPow2) { EGLSurface stcEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig, mANW.get(), NULL); ASSERT_EQ(EGL_SUCCESS, eglGetError()); - ASSERT_NE(EGL_NO_SURFACE, mEglSurface); + ASSERT_NE(EGL_NO_SURFACE, stcEglSurface); EXPECT_TRUE(eglMakeCurrent(mEglDisplay, stcEglSurface, stcEglSurface, mEglContext)); @@ -980,6 +974,8 @@ TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromGLFilledRGBABufferPow2) { eglSwapBuffers(mEglDisplay, stcEglSurface); + eglDestroySurface(mEglDisplay, stcEglSurface); + // Do the consumer side of things EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)); @@ -1018,6 +1014,83 @@ TEST_F(SurfaceTextureGLTest, DISABLED_TexturingFromGLFilledRGBABufferPow2) { EXPECT_TRUE(checkPixel( 3, 52, 153, 153, 153, 153)); } +TEST_F(SurfaceTextureGLTest, AbandonUnblocksDequeueBuffer) { + class ProducerThread : public Thread { + public: + ProducerThread(const sp<ANativeWindow>& anw): + mANW(anw), + mDequeueError(NO_ERROR) { + } + + virtual ~ProducerThread() { + } + + virtual bool threadLoop() { + Mutex::Autolock lock(mMutex); + ANativeWindowBuffer* anb; + + // Frame 1 + if (mANW->dequeueBuffer(mANW.get(), &anb) != NO_ERROR) { + return false; + } + if (anb == NULL) { + return false; + } + if (mANW->queueBuffer(mANW.get(), anb) + != NO_ERROR) { + return false; + } + + // Frame 2 + if (mANW->dequeueBuffer(mANW.get(), &anb) != NO_ERROR) { + return false; + } + if (anb == NULL) { + return false; + } + if (mANW->queueBuffer(mANW.get(), anb) + != NO_ERROR) { + return false; + } + + // Frame 3 - error expected + mDequeueError = mANW->dequeueBuffer(mANW.get(), &anb); + return false; + } + + status_t getDequeueError() { + Mutex::Autolock lock(mMutex); + return mDequeueError; + } + + private: + sp<ANativeWindow> mANW; + status_t mDequeueError; + Mutex mMutex; + }; + + sp<FrameWaiter> fw(new FrameWaiter); + mST->setFrameAvailableListener(fw); + ASSERT_EQ(OK, mST->setSynchronousMode(true)); + ASSERT_EQ(OK, mST->setBufferCountServer(2)); + + sp<Thread> pt(new ProducerThread(mANW)); + pt->run(); + + fw->waitForFrame(); + fw->waitForFrame(); + + // Sleep for 100ms to allow the producer thread's dequeueBuffer call to + // block waiting for a buffer to become available. + usleep(100000); + + mST->abandon(); + + pt->requestExitAndWait(); + ASSERT_EQ(NO_INIT, + reinterpret_cast<ProducerThread*>(pt.get())->getDequeueError()); +} + /* * This test is for testing GL -> GL texture streaming via SurfaceTexture. It * contains functionality to create a producer thread that will perform GL @@ -1205,7 +1278,7 @@ protected: sp<FrameCondition> mFC; }; -TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageBeforeFrameFinishedWorks) { +TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageBeforeFrameFinishedCompletes) { class PT : public ProducerThread { virtual void render() { glClearColor(0.0f, 1.0f, 0.0f, 1.0f); @@ -1223,7 +1296,7 @@ TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageBeforeFrameFinishedWorks) { // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! } -TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageAfterFrameFinishedWorks) { +TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageAfterFrameFinishedCompletes) { class PT : public ProducerThread { virtual void render() { glClearColor(0.0f, 1.0f, 0.0f, 1.0f); @@ -1241,7 +1314,7 @@ TEST_F(SurfaceTextureGLToGLTest, UpdateTexImageAfterFrameFinishedWorks) { // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported! } -TEST_F(SurfaceTextureGLToGLTest, RepeatedUpdateTexImageBeforeFrameFinishedWorks) { +TEST_F(SurfaceTextureGLToGLTest, RepeatedUpdateTexImageBeforeFrameFinishedCompletes) { enum { NUM_ITERATIONS = 1024 }; class PT : public ProducerThread { @@ -1269,7 +1342,7 @@ TEST_F(SurfaceTextureGLToGLTest, RepeatedUpdateTexImageBeforeFrameFinishedWorks) } } -TEST_F(SurfaceTextureGLToGLTest, RepeatedUpdateTexImageAfterFrameFinishedWorks) { +TEST_F(SurfaceTextureGLToGLTest, RepeatedUpdateTexImageAfterFrameFinishedCompletes) { enum { NUM_ITERATIONS = 1024 }; class PT : public ProducerThread { @@ -1297,4 +1370,70 @@ TEST_F(SurfaceTextureGLToGLTest, RepeatedUpdateTexImageAfterFrameFinishedWorks) } } +// XXX: This test is disabled because it is currently hanging on some devices. +TEST_F(SurfaceTextureGLToGLTest, DISABLED_RepeatedSwapBuffersWhileDequeueStalledCompletes) { + enum { NUM_ITERATIONS = 64 }; + + class PT : public ProducerThread { + virtual void render() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + LOGV("+swapBuffers"); + swapBuffers(); + LOGV("-swapBuffers"); + } + } + }; + + ASSERT_EQ(OK, mST->setSynchronousMode(true)); + ASSERT_EQ(OK, mST->setBufferCountServer(2)); + + runProducerThread(new PT()); + + // Allow three frames to be rendered and queued before starting the + // rendering in this thread. For the latter two frames we don't call + // updateTexImage so the next dequeue from the producer thread will block + // waiting for a frame to become available. + mFC->waitForFrame(); + mFC->finishFrame(); + + // We must call updateTexImage to consume the first frame so that the + // SurfaceTexture is able to reduce the buffer count to 2. This is because + // the GL driver may dequeue a buffer when the EGLSurface is created, and + // that happens before we call setBufferCountServer. It's possible that the + // driver does not dequeue a buffer at EGLSurface creation time, so we + // cannot rely on this to cause the second dequeueBuffer call to block. + mST->updateTexImage(); + + mFC->waitForFrame(); + mFC->finishFrame(); + mFC->waitForFrame(); + mFC->finishFrame(); + + // Sleep for 100ms to allow the producer thread's dequeueBuffer call to + // block waiting for a buffer to become available. + usleep(100000); + + // Render and present a number of images. This thread should not be blocked + // by the fact that the producer thread is blocking in dequeue. + for (int i = 0; i < NUM_ITERATIONS; i++) { + glClear(GL_COLOR_BUFFER_BIT); + eglSwapBuffers(mEglDisplay, mEglSurface); + } + + // Consume the two pending buffers to unblock the producer thread. + mST->updateTexImage(); + mST->updateTexImage(); + + // Consume the remaining buffers from the producer thread. + for (int i = 0; i < NUM_ITERATIONS-3; i++) { + mFC->waitForFrame(); + mFC->finishFrame(); + LOGV("+updateTexImage"); + mST->updateTexImage(); + LOGV("-updateTexImage"); + } +} + } // namespace android diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 8b1caeeedf59..886c05c64c07 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -883,7 +883,6 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) /////////////////////////////////////////////////////////////////////////////// DisplayListRenderer::DisplayListRenderer(): mWriter(MIN_WRITER_SIZE) { - mDisplayList = NULL; } DisplayListRenderer::~DisplayListRenderer() { @@ -923,13 +922,13 @@ void DisplayListRenderer::reset() { // Operations /////////////////////////////////////////////////////////////////////////////// -DisplayList* DisplayListRenderer::getDisplayList() { - if (mDisplayList == NULL) { - mDisplayList = new DisplayList(*this); +DisplayList* DisplayListRenderer::getDisplayList(DisplayList* displayList) { + if (!displayList) { + displayList = new DisplayList(*this); } else { - mDisplayList->initFromDisplayListRenderer(*this, true); + displayList->initFromDisplayListRenderer(*this, true); } - return mDisplayList; + return displayList; } void DisplayListRenderer::setViewport(int width, int height) { diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index b83259f82450..81576313c15f 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -217,7 +217,7 @@ public: DisplayListRenderer(); ~DisplayListRenderer(); - DisplayList* getDisplayList(); + DisplayList* getDisplayList(DisplayList* displayList); void setViewport(int width, int height); void prepareDirty(float left, float top, float right, float bottom, bool opaque); @@ -474,8 +474,6 @@ private: SkWriter32 mWriter; - DisplayList *mDisplayList; - int mRestoreSaveCount; friend class DisplayList; diff --git a/libs/ui/FramebufferNativeWindow.cpp b/libs/ui/FramebufferNativeWindow.cpp index 412552ec8eca..0e8ae619fc5c 100644 --- a/libs/ui/FramebufferNativeWindow.cpp +++ b/libs/ui/FramebufferNativeWindow.cpp @@ -310,35 +310,21 @@ int FramebufferNativeWindow::perform(ANativeWindow* window, int operation, ...) { switch (operation) { - case NATIVE_WINDOW_SET_USAGE: - // TODO: we should implement this - return NO_ERROR; case NATIVE_WINDOW_CONNECT: - // TODO: we should implement this - return NO_ERROR; case NATIVE_WINDOW_DISCONNECT: - // TODO: we should implement this + case NATIVE_WINDOW_SET_USAGE: + case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY: + case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: + case NATIVE_WINDOW_SET_BUFFERS_FORMAT: + case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM: + // TODO: we should implement these return NO_ERROR; + case NATIVE_WINDOW_LOCK: - return INVALID_OPERATION; case NATIVE_WINDOW_UNLOCK_AND_POST: - return INVALID_OPERATION; case NATIVE_WINDOW_SET_CROP: - return INVALID_OPERATION; case NATIVE_WINDOW_SET_BUFFER_COUNT: - // TODO: we should implement this - return INVALID_OPERATION; - case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY: - return INVALID_OPERATION; - case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM: - return INVALID_OPERATION; case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP: - return INVALID_OPERATION; - case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: - return INVALID_OPERATION; - case NATIVE_WINDOW_SET_BUFFERS_FORMAT: - // TODO: we should implement this - return NO_ERROR; case NATIVE_WINDOW_SET_SCALING_MODE: return INVALID_OPERATION; } diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index 31e463124ae2..82e8d770e03d 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -54,7 +54,8 @@ public class AudioFormat { /** Default audio channel mask */ public static final int CHANNEL_OUT_DEFAULT = 1; - // Channel mask definitions must be kept in sync with native values in include/media/AudioSystem.h + // Channel mask definitions must be kept in sync with native values + // in /system/core/include/system/audio.h public static final int CHANNEL_OUT_FRONT_LEFT = 0x4; public static final int CHANNEL_OUT_FRONT_RIGHT = 0x8; public static final int CHANNEL_OUT_FRONT_CENTER = 0x10; @@ -64,6 +65,25 @@ public class AudioFormat { public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100; public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200; public static final int CHANNEL_OUT_BACK_CENTER = 0x400; + /** @hide */ + public static final int CHANNEL_OUT_SIDE_LEFT = 0x800; + /** @hide */ + public static final int CHANNEL_OUT_SIDE_RIGHT = 0x1000; + /** @hide */ + public static final int CHANNEL_OUT_TOP_CENTER = 0x2000; + /** @hide */ + public static final int CHANNEL_OUT_TOP_FRONT_LEFT = 0x4000; + /** @hide */ + public static final int CHANNEL_OUT_TOP_FRONT_CENTER = 0x8000; + /** @hide */ + public static final int CHANNEL_OUT_TOP_FRONT_RIGHT = 0x10000; + /** @hide */ + public static final int CHANNEL_OUT_TOP_BACK_LEFT = 0x20000; + /** @hide */ + public static final int CHANNEL_OUT_TOP_BACK_CENTER = 0x40000; + /** @hide */ + public static final int CHANNEL_OUT_TOP_BACK_RIGHT = 0x80000; + public static final int CHANNEL_OUT_MONO = CHANNEL_OUT_FRONT_LEFT; public static final int CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT); public static final int CHANNEL_OUT_QUAD = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | @@ -75,6 +95,12 @@ public class AudioFormat { public static final int CHANNEL_OUT_7POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT | CHANNEL_OUT_FRONT_LEFT_OF_CENTER | CHANNEL_OUT_FRONT_RIGHT_OF_CENTER); + /** @hide */ + public static final int CHANNEL_OUT_7POINT1_SURROUND = ( + CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_FRONT_RIGHT | + CHANNEL_OUT_SIDE_LEFT | CHANNEL_OUT_SIDE_RIGHT | + CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT | + CHANNEL_OUT_LOW_FREQUENCY); public static final int CHANNEL_IN_DEFAULT = 1; public static final int CHANNEL_IN_LEFT = 0x4; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 482b437dd3dc..95671bc47c15 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -611,7 +611,7 @@ public class MediaPlayer * needed. Not calling this method when playing back a video will * result in only the audio track being played. * - * Either a surface or surface texture must be set if a display or video sink + * Either a surface holder or surface must be set if a display or video sink * is needed. Not calling this method or {@link #setTexture(SurfaceTexture)} * when playing back a video will result in only the audio track being played. * @@ -630,6 +630,27 @@ public class MediaPlayer } /** + * Sets the {@link Surface} to be used as the sink for the video portion of + * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but does not + * support {@link #setScreenOnWhilePlaying(boolean)} or {@link #updateSurfaceScreenOn()}. + * Setting a Surface will un-set any Surface or SurfaceHolder that was previously set. + * + * @param surface The {@link Surface} to be used for the video portion of the media. + * + * @hide Pending review by API council. + */ + public void setSurface(Surface surface) { + if (mScreenOnWhilePlaying && surface != null && mSurface != null) { + Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface"); + } + mSurfaceHolder = null; + mSurface = surface; + mParcelSurfaceTexture = null; // TODO(tedbo): Remove. + _setVideoSurfaceOrSurfaceTexture(); + updateSurfaceScreenOn(); + } + + /** * Sets the {@link SurfaceTexture} to be used as the sink for the * video portion of the media. Either a surface or surface texture * must be set if a video sink is needed. The same surface texture @@ -665,7 +686,7 @@ public class MediaPlayer * @param pst The {@link ParcelSurfaceTexture} to be used as the sink for * the video portion of the media. * - * @hide Pending review by API council. + * @hide Pending removal when there are no more callers. */ public void setParcelSurfaceTexture(ParcelSurfaceTexture pst) { if (mScreenOnWhilePlaying && pst != null && mParcelSurfaceTexture == null) { @@ -1000,8 +1021,8 @@ public class MediaPlayer */ public void setScreenOnWhilePlaying(boolean screenOn) { if (mScreenOnWhilePlaying != screenOn) { - if (screenOn && mParcelSurfaceTexture != null) { - Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for SurfaceTexture"); + if (screenOn && mSurfaceHolder == null) { + Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder"); } mScreenOnWhilePlaying = screenOn; updateSurfaceScreenOn(); diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index e3cbd5783c8f..72069ac19a90 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -81,9 +81,6 @@ public class MediaRecorder private String mPath; private FileDescriptor mFd; - private boolean mPrepareAuxiliaryFile = false; - private String mPathAux; - private FileDescriptor mFdAux; private EventHandler mEventHandler; private OnErrorListener mOnErrorListener; private OnInfoListener mOnInfoListener; @@ -557,84 +554,23 @@ public class MediaRecorder } /** - * Sets the auxiliary time lapse video's resolution and bitrate. - * - * The auxiliary video's resolution and bitrate are determined by the CamcorderProfile - * quality level {@link android.media.CamcorderProfile#QUALITY_HIGH}. - */ - private void setAuxVideoParameters() { - CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); - setParameter(String.format("video-aux-param-width=%d", profile.videoFrameWidth)); - setParameter(String.format("video-aux-param-height=%d", profile.videoFrameHeight)); - setParameter(String.format("video-aux-param-encoding-bitrate=%d", profile.videoBitRate)); - } - - /** - * Pass in the file descriptor for the auxiliary time lapse video. Call this before - * prepare(). - * - * Sets file descriptor and parameters for auxiliary time lapse video. Time lapse mode - * can capture video (using the still camera) at resolutions higher than that can be - * played back on the device. This function or - * {@link #setAuxiliaryOutputFile(String)} enable capture of a smaller video in - * parallel with the main time lapse video, which can be used to play back on the - * device. The smaller video is created by downsampling the main video. This call is - * optional and does not have to be called if parallel capture of a downsampled video - * is not desired. - * - * Note that while the main video resolution and bitrate is determined from the - * CamcorderProfile in {@link #setProfile(CamcorderProfile)}, the auxiliary video's - * resolution and bitrate are determined by the CamcorderProfile quality level - * {@link android.media.CamcorderProfile#QUALITY_HIGH}. All other encoding parameters - * remain the same for the main video and the auxiliary video. - * - * E.g. if the device supports the time lapse profile quality level - * {@link android.media.CamcorderProfile#QUALITY_TIME_LAPSE_1080P} but can playback at - * most 480p, the application might want to capture an auxiliary video of resolution - * 480p using this call. - * - * @param fd an open file descriptor to be written into. + * Currently not implemented. It does nothing. + * @deprecated Time lapse mode video recording using camera still image capture + * is not desirable, and will not be supported. */ public void setAuxiliaryOutputFile(FileDescriptor fd) { - mPrepareAuxiliaryFile = true; - mPathAux = null; - mFdAux = fd; - setAuxVideoParameters(); + Log.w(TAG, "setAuxiliaryOutputFile(FileDescriptor) is no longer supported."); } /** - * Pass in the file path for the auxiliary time lapse video. Call this before - * prepare(). - * - * Sets file path and parameters for auxiliary time lapse video. Time lapse mode can - * capture video (using the still camera) at resolutions higher than that can be - * played back on the device. This function or - * {@link #setAuxiliaryOutputFile(FileDescriptor)} enable capture of a smaller - * video in parallel with the main time lapse video, which can be used to play back on - * the device. The smaller video is created by downsampling the main video. This call - * is optional and does not have to be called if parallel capture of a downsampled - * video is not desired. - * - * Note that while the main video resolution and bitrate is determined from the - * CamcorderProfile in {@link #setProfile(CamcorderProfile)}, the auxiliary video's - * resolution and bitrate are determined by the CamcorderProfile quality level - * {@link android.media.CamcorderProfile#QUALITY_HIGH}. All other encoding parameters - * remain the same for the main video and the auxiliary video. - * - * E.g. if the device supports the time lapse profile quality level - * {@link android.media.CamcorderProfile#QUALITY_TIME_LAPSE_1080P} but can playback at - * most 480p, the application might want to capture an auxiliary video of resolution - * 480p using this call. - * - * @param path The pathname to use. + * Currently not implemented. It does nothing. + * @deprecated Time lapse mode video recording using camera still image capture + * is not desirable, and will not be supported. */ public void setAuxiliaryOutputFile(String path) { - mPrepareAuxiliaryFile = true; - mFdAux = null; - mPathAux = path; - setAuxVideoParameters(); + Log.w(TAG, "setAuxiliaryOutputFile(String) is no longer supported."); } /** @@ -668,8 +604,6 @@ public class MediaRecorder // native implementation private native void _setOutputFile(FileDescriptor fd, long offset, long length) throws IllegalStateException, IOException; - private native void _setOutputFileAux(FileDescriptor fd) - throws IllegalStateException, IOException; private native void _prepare() throws IllegalStateException, IOException; /** @@ -696,21 +630,6 @@ public class MediaRecorder throw new IOException("No valid output file"); } - if (mPrepareAuxiliaryFile) { - if (mPathAux != null) { - FileOutputStream fos = new FileOutputStream(mPathAux); - try { - _setOutputFileAux(fos.getFD()); - } finally { - fos.close(); - } - } else if (mFdAux != null) { - _setOutputFileAux(mFdAux); - } else { - throw new IOException("No valid output file"); - } - } - _prepare(); } diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 12391c87171e..922f7edcb721 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -127,7 +127,7 @@ static bool process_media_recorder_call(JNIEnv *env, status_t opStatus, const ch return false; } -static sp<MediaRecorder> getMediaRecorder(JNIEnv* env, jobject thiz) +sp<MediaRecorder> getMediaRecorder(JNIEnv* env, jobject thiz) { Mutex::Autolock l(sLock); MediaRecorder* const p = (MediaRecorder*)env->GetIntField(thiz, fields.context); @@ -261,20 +261,6 @@ android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject f } static void -android_media_MediaRecorder_setOutputFileAuxFD(JNIEnv *env, jobject thiz, jobject fileDescriptor) -{ - LOGV("setOutputFile"); - if (fileDescriptor == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return; - } - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - sp<MediaRecorder> mr = getMediaRecorder(env, thiz); - status_t opStatus = mr->setOutputFileAuxiliary(fd); - process_media_recorder_call(env, opStatus, "java/io/IOException", "setOutputFile failed."); -} - -static void android_media_MediaRecorder_setVideoSize(JNIEnv *env, jobject thiz, jint width, jint height) { LOGV("setVideoSize(%d, %d)", width, height); @@ -475,7 +461,6 @@ static JNINativeMethod gMethods[] = { {"setAudioEncoder", "(I)V", (void *)android_media_MediaRecorder_setAudioEncoder}, {"setParameter", "(Ljava/lang/String;)V", (void *)android_media_MediaRecorder_setParameter}, {"_setOutputFile", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaRecorder_setOutputFileFD}, - {"_setOutputFileAux", "(Ljava/io/FileDescriptor;)V", (void *)android_media_MediaRecorder_setOutputFileAuxFD}, {"setVideoSize", "(II)V", (void *)android_media_MediaRecorder_setVideoSize}, {"setVideoFrameRate", "(I)V", (void *)android_media_MediaRecorder_setVideoFrameRate}, {"setMaxDuration", "(I)V", (void *)android_media_MediaRecorder_setMaxDuration}, diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp index a44ef5ab1b95..7e44c2991c5c 100644 --- a/media/libmedia/IMediaRecorder.cpp +++ b/media/libmedia/IMediaRecorder.cpp @@ -23,14 +23,17 @@ #include <camera/ICamera.h> #include <media/IMediaRecorderClient.h> #include <media/IMediaRecorder.h> +#include <gui/ISurfaceTexture.h> #include <unistd.h> + namespace android { enum { RELEASE = IBinder::FIRST_CALL_TRANSACTION, INIT, CLOSE, + QUERY_SURFACE_MEDIASOURCE, RESET, STOP, START, @@ -71,6 +74,19 @@ public: return reply.readInt32(); } + sp<ISurfaceTexture> querySurfaceMediaSource() + { + LOGV("Query SurfaceMediaSource"); + Parcel data, reply; + data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor()); + remote()->transact(QUERY_SURFACE_MEDIASOURCE, data, &reply); + int returnedNull = reply.readInt32(); + if (returnedNull) { + return NULL; + } + return interface_cast<ISurfaceTexture>(reply.readStrongBinder()); + } + status_t setPreviewSurface(const sp<Surface>& surface) { LOGV("setPreviewSurface(%p)", surface.get()); @@ -440,6 +456,20 @@ status_t BnMediaRecorder::onTransact( reply->writeInt32(setCamera(camera, proxy)); return NO_ERROR; } break; + case QUERY_SURFACE_MEDIASOURCE: { + LOGV("QUERY_SURFACE_MEDIASOURCE"); + CHECK_INTERFACE(IMediaRecorder, data, reply); + // call the mediaserver side to create + // a surfacemediasource + sp<ISurfaceTexture> surfaceMediaSource = querySurfaceMediaSource(); + // The mediaserver might have failed to create a source + int returnedNull= (surfaceMediaSource == NULL) ? 1 : 0 ; + reply->writeInt32(returnedNull); + if (!returnedNull) { + reply->writeStrongBinder(surfaceMediaSource->asBinder()); + } + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp index ed6e3c7aa7a5..a11fb804f134 100644 --- a/media/libmedia/mediaplayer.cpp +++ b/media/libmedia/mediaplayer.cpp @@ -228,6 +228,7 @@ status_t MediaPlayer::setVideoSurface(const sp<Surface>& surface) NATIVE_WINDOW_API_MEDIA); if (err != OK) { + LOGE("setVideoSurface failed: %d", err); // Note that we must do the reset before disconnecting from the ANW. // Otherwise queue/dequeue calls could be made on the disconnected // ANW, which may result in errors. @@ -277,6 +278,7 @@ status_t MediaPlayer::setVideoSurfaceTexture( NATIVE_WINDOW_API_MEDIA); if (err != OK) { + LOGE("setVideoSurfaceTexture failed: %d", err); // Note that we must do the reset before disconnecting from the ANW. // Otherwise queue/dequeue calls could be made on the disconnected // ANW, which may result in errors. diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp index 9e4edd09c8c9..fab674ca515c 100644 --- a/media/libmedia/mediarecorder.cpp +++ b/media/libmedia/mediarecorder.cpp @@ -25,6 +25,7 @@ #include <media/IMediaPlayerService.h> #include <media/IMediaRecorder.h> #include <media/mediaplayer.h> // for MEDIA_ERROR_SERVER_DIED +#include <gui/ISurfaceTexture.h> namespace android { @@ -127,7 +128,9 @@ status_t MediaRecorder::setVideoSource(int vs) return INVALID_OPERATION; } + // following call is made over the Binder Interface status_t ret = mMediaRecorder->setVideoSource(vs); + if (OK != ret) { LOGV("setVideoSource failed: %d", ret); mCurrentState = MEDIA_RECORDER_ERROR; @@ -357,7 +360,7 @@ status_t MediaRecorder::setVideoSize(int width, int height) return INVALID_OPERATION; } if (!mIsVideoSourceSet) { - LOGE("try to set video size without setting video source first"); + LOGE("Cannot set video size without setting video source first"); return INVALID_OPERATION; } @@ -367,9 +370,27 @@ status_t MediaRecorder::setVideoSize(int width, int height) mCurrentState = MEDIA_RECORDER_ERROR; return ret; } + return ret; } +// Query a SurfaceMediaSurface through the Mediaserver, over the +// binder interface. This is used by the Filter Framework (MeidaEncoder) +// to get an <ISurfaceTexture> object to hook up to ANativeWindow. +sp<ISurfaceTexture> MediaRecorder:: + querySurfaceMediaSourceFromMediaServer() +{ + Mutex::Autolock _l(mLock); + mSurfaceMediaSource = + mMediaRecorder->querySurfaceMediaSource(); + if (mSurfaceMediaSource == NULL) { + LOGE("SurfaceMediaSource could not be initialized!"); + } + return mSurfaceMediaSource; +} + + + status_t MediaRecorder::setVideoFrameRate(int frames_per_second) { LOGV("setVideoFrameRate(%d)", frames_per_second); @@ -382,7 +403,7 @@ status_t MediaRecorder::setVideoFrameRate(int frames_per_second) return INVALID_OPERATION; } if (!mIsVideoSourceSet) { - LOGE("try to set video frame rate without setting video source first"); + LOGE("Cannot set video frame rate without setting video source first"); return INVALID_OPERATION; } @@ -621,7 +642,7 @@ status_t MediaRecorder::release() return INVALID_OPERATION; } -MediaRecorder::MediaRecorder() +MediaRecorder::MediaRecorder() : mSurfaceMediaSource(NULL) { LOGV("constructor"); @@ -632,6 +653,8 @@ MediaRecorder::MediaRecorder() if (mMediaRecorder != NULL) { mCurrentState = MEDIA_RECORDER_IDLE; } + + doCleanUp(); } @@ -646,6 +669,10 @@ MediaRecorder::~MediaRecorder() if (mMediaRecorder != NULL) { mMediaRecorder.clear(); } + + if (mSurfaceMediaSource != NULL) { + mSurfaceMediaSource.clear(); + } } status_t MediaRecorder::setListener(const sp<MediaRecorderListener>& listener) diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp index 115db1ad2904..905b88568eb7 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.cpp +++ b/media/libmediaplayerservice/MediaRecorderClient.cpp @@ -41,6 +41,7 @@ #include "MediaPlayerService.h" #include "StagefrightRecorder.h" +#include <gui/ISurfaceTexture.h> namespace android { @@ -57,6 +58,20 @@ static bool checkPermission(const char* permissionString) { return ok; } + +sp<ISurfaceTexture> MediaRecorderClient::querySurfaceMediaSource() +{ + LOGV("Query SurfaceMediaSource"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + LOGE("recorder is not initialized"); + return NULL; + } + return mRecorder->querySurfaceMediaSource(); +} + + + status_t MediaRecorderClient::setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy) { diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h index bbca5291cd5c..c87a3c0a23e8 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.h +++ b/media/libmediaplayerservice/MediaRecorderClient.h @@ -25,45 +25,51 @@ namespace android { class MediaRecorderBase; class MediaPlayerService; class ICameraRecordingProxy; +class ISurfaceTexture; class MediaRecorderClient : public BnMediaRecorder { public: - virtual status_t setCamera(const sp<ICamera>& camera, - const sp<ICameraRecordingProxy>& proxy); - virtual status_t setPreviewSurface(const sp<Surface>& surface); - virtual status_t setVideoSource(int vs); - virtual status_t setAudioSource(int as); - virtual status_t setOutputFormat(int of); - virtual status_t setVideoEncoder(int ve); - virtual status_t setAudioEncoder(int ae); - virtual status_t setOutputFile(const char* path); - virtual status_t setOutputFile(int fd, int64_t offset, int64_t length); - virtual status_t setOutputFileAuxiliary(int fd); - virtual status_t setVideoSize(int width, int height); - virtual status_t setVideoFrameRate(int frames_per_second); - virtual status_t setParameters(const String8& params); - virtual status_t setListener(const sp<IMediaRecorderClient>& listener); - virtual status_t prepare(); - virtual status_t getMaxAmplitude(int* max); - virtual status_t start(); - virtual status_t stop(); - virtual status_t reset(); - virtual status_t init(); - virtual status_t close(); - virtual status_t release(); + virtual status_t setCamera(const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy); + virtual status_t setPreviewSurface(const sp<Surface>& surface); + virtual status_t setVideoSource(int vs); + virtual status_t setAudioSource(int as); + virtual status_t setOutputFormat(int of); + virtual status_t setVideoEncoder(int ve); + virtual status_t setAudioEncoder(int ae); + virtual status_t setOutputFile(const char* path); + virtual status_t setOutputFile(int fd, int64_t offset, + int64_t length); + virtual status_t setOutputFileAuxiliary(int fd); + virtual status_t setVideoSize(int width, int height); + virtual status_t setVideoFrameRate(int frames_per_second); + virtual status_t setParameters(const String8& params); + virtual status_t setListener( + const sp<IMediaRecorderClient>& listener); + virtual status_t prepare(); + virtual status_t getMaxAmplitude(int* max); + virtual status_t start(); + virtual status_t stop(); + virtual status_t reset(); + virtual status_t init(); + virtual status_t close(); + virtual status_t release(); + virtual status_t dump(int fd, const Vector<String16>& args) const; + virtual sp<ISurfaceTexture> querySurfaceMediaSource(); - virtual status_t dump(int fd, const Vector<String16>& args) const; private: - friend class MediaPlayerService; // for accessing private constructor + friend class MediaPlayerService; // for accessing private constructor - MediaRecorderClient(const sp<MediaPlayerService>& service, pid_t pid); - virtual ~MediaRecorderClient(); + MediaRecorderClient( + const sp<MediaPlayerService>& service, + pid_t pid); + virtual ~MediaRecorderClient(); - pid_t mPid; - Mutex mLock; - MediaRecorderBase *mRecorder; - sp<MediaPlayerService> mMediaPlayerService; + pid_t mPid; + Mutex mLock; + MediaRecorderBase *mRecorder; + sp<MediaPlayerService> mMediaPlayerService; }; }; // namespace android diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp index 223e0be21a75..6427bb701646 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.cpp +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -38,10 +38,12 @@ #include <media/stagefright/MetaData.h> #include <media/stagefright/OMXClient.h> #include <media/stagefright/OMXCodec.h> +#include <media/stagefright/SurfaceMediaSource.h> #include <media/MediaProfiles.h> #include <camera/ICamera.h> #include <camera/CameraParameters.h> #include <surfaceflinger/Surface.h> + #include <utils/Errors.h> #include <sys/types.h> #include <ctype.h> @@ -69,7 +71,7 @@ StagefrightRecorder::StagefrightRecorder() mOutputFd(-1), mOutputFdAux(-1), mAudioSource(AUDIO_SOURCE_CNT), mVideoSource(VIDEO_SOURCE_LIST_END), - mStarted(false) { + mStarted(false), mSurfaceMediaSource(NULL) { LOGV("Constructor"); reset(); @@ -85,6 +87,14 @@ status_t StagefrightRecorder::init() { return OK; } +// The client side of mediaserver asks it to creat a SurfaceMediaSource +// and return a interface reference. The client side will use that +// while encoding GL Frames +sp<ISurfaceTexture> StagefrightRecorder::querySurfaceMediaSource() const { + LOGV("Get SurfaceMediaSource"); + return mSurfaceMediaSource; +} + status_t StagefrightRecorder::setAudioSource(audio_source_t as) { LOGV("setAudioSource: %d", as); if (as < AUDIO_SOURCE_DEFAULT || @@ -1006,13 +1016,13 @@ status_t StagefrightRecorder::startRTPRecording() { source = createAudioSource(); } else { - sp<CameraSource> cameraSource; - status_t err = setupCameraSource(&cameraSource); + sp<MediaSource> mediaSource; + status_t err = setupMediaSource(&mediaSource); if (err != OK) { return err; } - err = setupVideoEncoder(cameraSource, mVideoBitRate, &source); + err = setupVideoEncoder(mediaSource, mVideoBitRate, &source); if (err != OK) { return err; } @@ -1042,20 +1052,19 @@ status_t StagefrightRecorder::startMPEG2TSRecording() { } } - if (mVideoSource == VIDEO_SOURCE_DEFAULT - || mVideoSource == VIDEO_SOURCE_CAMERA) { + if (mVideoSource < VIDEO_SOURCE_LIST_END) { if (mVideoEncoder != VIDEO_ENCODER_H264) { return ERROR_UNSUPPORTED; } - sp<CameraSource> cameraSource; - status_t err = setupCameraSource(&cameraSource); + sp<MediaSource> mediaSource; + status_t err = setupMediaSource(&mediaSource); if (err != OK) { return err; } sp<MediaSource> encoder; - err = setupVideoEncoder(cameraSource, mVideoBitRate, &encoder); + err = setupVideoEncoder(mediaSource, mVideoBitRate, &encoder); if (err != OK) { return err; @@ -1289,6 +1298,60 @@ void StagefrightRecorder::clipVideoFrameHeight() { } } +// Set up the appropriate MediaSource depending on the chosen option +status_t StagefrightRecorder::setupMediaSource( + sp<MediaSource> *mediaSource) { + if (mVideoSource == VIDEO_SOURCE_DEFAULT + || mVideoSource == VIDEO_SOURCE_CAMERA) { + sp<CameraSource> cameraSource; + status_t err = setupCameraSource(&cameraSource); + if (err != OK) { + return err; + } + *mediaSource = cameraSource; + } else if (mVideoSource == VIDEO_SOURCE_GRALLOC_BUFFER) { + // If using GRAlloc buffers, setup surfacemediasource. + // Later a handle to that will be passed + // to the client side when queried + status_t err = setupSurfaceMediaSource(); + if (err != OK) { + return err; + } + *mediaSource = mSurfaceMediaSource; + } else { + return INVALID_OPERATION; + } + return OK; +} + +// setupSurfaceMediaSource creates a source with the given +// width and height and framerate. +// TODO: This could go in a static function inside SurfaceMediaSource +// similar to that in CameraSource +status_t StagefrightRecorder::setupSurfaceMediaSource() { + status_t err = OK; + mSurfaceMediaSource = new SurfaceMediaSource(mVideoWidth, mVideoHeight); + if (mSurfaceMediaSource == NULL) { + return NO_INIT; + } + + if (mFrameRate == -1) { + int32_t frameRate = 0; + CHECK (mSurfaceMediaSource->getFormat()->findInt32( + kKeyFrameRate, &frameRate)); + LOGI("Frame rate is not explicitly set. Use the current frame " + "rate (%d fps)", frameRate); + mFrameRate = frameRate; + } else { + err = mSurfaceMediaSource->setFrameRate(mFrameRate); + } + CHECK(mFrameRate != -1); + + mIsMetaDataStoredInVideoBuffers = + mSurfaceMediaSource->isMetaDataStoredInVideoBuffers(); + return err; +} + status_t StagefrightRecorder::setupCameraSource( sp<CameraSource> *cameraSource) { status_t err = OK; @@ -1465,29 +1528,37 @@ status_t StagefrightRecorder::setupMPEG4Recording( status_t err = OK; sp<MediaWriter> writer = new MPEG4Writer(outputFd); - if (mVideoSource == VIDEO_SOURCE_DEFAULT - || mVideoSource == VIDEO_SOURCE_CAMERA) { + if (mVideoSource < VIDEO_SOURCE_LIST_END) { - sp<MediaSource> cameraMediaSource; + sp<MediaSource> mediaSource; if (useSplitCameraSource) { + // TODO: Check if there is a better way to handle this + if (mVideoSource == VIDEO_SOURCE_GRALLOC_BUFFER) { + LOGE("Cannot use split camera when encoding frames"); + return INVALID_OPERATION; + } LOGV("Using Split camera source"); - cameraMediaSource = mCameraSourceSplitter->createClient(); + mediaSource = mCameraSourceSplitter->createClient(); } else { - sp<CameraSource> cameraSource; - err = setupCameraSource(&cameraSource); - cameraMediaSource = cameraSource; + err = setupMediaSource(&mediaSource); } + if ((videoWidth != mVideoWidth) || (videoHeight != mVideoHeight)) { + // TODO: Might be able to handle downsampling even if using GRAlloc + if (mVideoSource == VIDEO_SOURCE_GRALLOC_BUFFER) { + LOGE("Cannot change size or Downsample when encoding frames"); + return INVALID_OPERATION; + } // Use downsampling from the original source. - cameraMediaSource = - new VideoSourceDownSampler(cameraMediaSource, videoWidth, videoHeight); + mediaSource = + new VideoSourceDownSampler(mediaSource, videoWidth, videoHeight); } if (err != OK) { return err; } sp<MediaSource> encoder; - err = setupVideoEncoder(cameraMediaSource, videoBitRate, &encoder); + err = setupVideoEncoder(mediaSource, videoBitRate, &encoder); if (err != OK) { return err; } diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h index 034b373337dc..1618b92a78ae 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.h +++ b/media/libmediaplayerservice/StagefrightRecorder.h @@ -36,6 +36,8 @@ struct MediaWriter; class MetaData; struct AudioSource; class MediaProfiles; +class ISurfaceTexture; +class SurfaceMediaSource; struct StagefrightRecorder : public MediaRecorderBase { StagefrightRecorder(); @@ -64,6 +66,8 @@ struct StagefrightRecorder : public MediaRecorderBase { virtual status_t reset(); virtual status_t getMaxAmplitude(int *max); virtual status_t dump(int fd, const Vector<String16>& args) const; + // Querying a SurfaceMediaSourcer + virtual sp<ISurfaceTexture> querySurfaceMediaSource() const; private: sp<ICamera> mCamera; @@ -109,12 +113,18 @@ private: sp<MediaSourceSplitter> mCameraSourceSplitter; sp<CameraSourceTimeLapse> mCameraSourceTimeLapse; + String8 mParams; bool mIsMetaDataStoredInVideoBuffers; MediaProfiles *mEncoderProfiles; bool mStarted; + // Needed when GLFrames are encoded. + // An <ISurfaceTexture> pointer + // will be sent to the client side using which the + // frame buffers will be queued and dequeued + sp<SurfaceMediaSource> mSurfaceMediaSource; status_t setupMPEG4Recording( bool useSplitCameraSource, @@ -134,7 +144,14 @@ private: sp<MediaSource> createAudioSource(); status_t checkVideoEncoderCapabilities(); status_t checkAudioEncoderCapabilities(); + // Generic MediaSource set-up. Returns the appropriate + // source (CameraSource or SurfaceMediaSource) + // depending on the videosource type + status_t setupMediaSource(sp<MediaSource> *mediaSource); status_t setupCameraSource(sp<CameraSource> *cameraSource); + // setup the surfacemediasource for the encoder + status_t setupSurfaceMediaSource(); + status_t setupAudioEncoder(const sp<MediaWriter>& writer); status_t setupVideoEncoder( sp<MediaSource> cameraSource, @@ -176,6 +193,7 @@ private: void clipNumberOfAudioChannels(); void setDefaultProfileIfNecessary(); + StagefrightRecorder(const StagefrightRecorder &); StagefrightRecorder &operator=(const StagefrightRecorder &); }; diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index e17e1e835797..3a3c082b08de 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -42,6 +42,7 @@ LOCAL_SRC_FILES:= \ SampleTable.cpp \ StagefrightMediaScanner.cpp \ StagefrightMetadataRetriever.cpp \ + SurfaceMediaSource.cpp \ ThrottledSource.cpp \ TimeSource.cpp \ TimedEventQueue.cpp \ diff --git a/media/libstagefright/NuCachedSource2.cpp b/media/libstagefright/NuCachedSource2.cpp index 77a66024a60d..4edb613c33b6 100644 --- a/media/libstagefright/NuCachedSource2.cpp +++ b/media/libstagefright/NuCachedSource2.cpp @@ -185,7 +185,8 @@ NuCachedSource2::NuCachedSource2(const sp<DataSource> &source) mFinalStatus(OK), mLastAccessPos(0), mFetching(true), - mLastFetchTimeUs(-1) { + mLastFetchTimeUs(-1), + mNumRetriesLeft(kMaxNumRetries) { mLooper->setName("NuCachedSource2"); mLooper->registerHandler(mReflector); mLooper->start(); @@ -254,7 +255,27 @@ void NuCachedSource2::onMessageReceived(const sp<AMessage> &msg) { void NuCachedSource2::fetchInternal() { LOGV("fetchInternal"); - CHECK_EQ(mFinalStatus, (status_t)OK); + { + Mutex::Autolock autoLock(mLock); + CHECK(mFinalStatus == OK || mNumRetriesLeft > 0); + + if (mFinalStatus != OK) { + --mNumRetriesLeft; + + status_t err = + mSource->reconnectAtOffset(mCacheOffset + mCache->totalSize()); + + if (err == ERROR_UNSUPPORTED) { + mNumRetriesLeft = 0; + return; + } else if (err != OK) { + LOGI("The attempt to reconnect failed, %d retries remaining", + mNumRetriesLeft); + + return; + } + } + } PageCache::Page *page = mCache->acquirePage(); @@ -264,14 +285,23 @@ void NuCachedSource2::fetchInternal() { Mutex::Autolock autoLock(mLock); if (n < 0) { - LOGE("source returned error %ld", n); + LOGE("source returned error %ld, %d retries left", n, mNumRetriesLeft); mFinalStatus = n; mCache->releasePage(page); } else if (n == 0) { LOGI("ERROR_END_OF_STREAM"); + + mNumRetriesLeft = 0; mFinalStatus = ERROR_END_OF_STREAM; + mCache->releasePage(page); } else { + if (mFinalStatus != OK) { + LOGI("retrying a previously failed read succeeded."); + } + mNumRetriesLeft = kMaxNumRetries; + mFinalStatus = OK; + page->mSize = n; mCache->appendPage(page); } @@ -280,7 +310,7 @@ void NuCachedSource2::fetchInternal() { void NuCachedSource2::onFetch() { LOGV("onFetch"); - if (mFinalStatus != OK) { + if (mFinalStatus != OK && mNumRetriesLeft == 0) { LOGV("EOS reached, done prefetching for now"); mFetching = false; } @@ -308,8 +338,19 @@ void NuCachedSource2::onFetch() { restartPrefetcherIfNecessary_l(); } - (new AMessage(kWhatFetchMore, mReflector->id()))->post( - mFetching ? 0 : 100000ll); + int64_t delayUs; + if (mFetching) { + if (mFinalStatus != OK && mNumRetriesLeft > 0) { + // We failed this time and will try again in 3 seconds. + delayUs = 3000000ll; + } else { + delayUs = 0; + } + } else { + delayUs = 100000ll; + } + + (new AMessage(kWhatFetchMore, mReflector->id()))->post(delayUs); } void NuCachedSource2::onRead(const sp<AMessage> &msg) { @@ -345,7 +386,7 @@ void NuCachedSource2::restartPrefetcherIfNecessary_l( bool ignoreLowWaterThreshold, bool force) { static const size_t kGrayArea = 1024 * 1024; - if (mFetching || mFinalStatus != OK) { + if (mFetching || (mFinalStatus != OK && mNumRetriesLeft == 0)) { return; } @@ -427,6 +468,12 @@ size_t NuCachedSource2::approxDataRemaining(status_t *finalStatus) { size_t NuCachedSource2::approxDataRemaining_l(status_t *finalStatus) { *finalStatus = mFinalStatus; + + if (mFinalStatus != OK && mNumRetriesLeft > 0) { + // Pretend that everything is fine until we're out of retries. + *finalStatus = OK; + } + off64_t lastBytePosCached = mCacheOffset + mCache->totalSize(); if (mLastAccessPos < lastBytePosCached) { return lastBytePosCached - mLastAccessPos; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 7bcbdcf80ff7..ac73351e4722 100755 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -48,6 +48,10 @@ namespace android { +// Treat time out as an error if we have not received any output +// buffers after 3 seconds. +const static int64_t kBufferFilledEventTimeOutUs = 3000000000LL; + struct CodecInfo { const char *mime; const char *codec; @@ -3191,6 +3195,16 @@ void OMXCodec::setState(State newState) { mBufferFilled.signal(); } +status_t OMXCodec::waitForBufferFilled_l() { + status_t err = mBufferFilled.waitRelative(mLock, kBufferFilledEventTimeOutUs); + if (err != OK) { + LOGE("Timed out waiting for buffers from video encoder: %d/%d", + countBuffersWeOwn(mPortBuffers[kPortIndexInput]), + countBuffersWeOwn(mPortBuffers[kPortIndexOutput])); + } + return err; +} + void OMXCodec::setRawAudioFormat( OMX_U32 portIndex, int32_t sampleRate, int32_t numChannels) { @@ -3623,6 +3637,7 @@ sp<MetaData> OMXCodec::getFormat() { status_t OMXCodec::read( MediaBuffer **buffer, const ReadOptions *options) { + status_t err = OK; *buffer = NULL; Mutex::Autolock autoLock(mLock); @@ -3663,7 +3678,9 @@ status_t OMXCodec::read( if (seeking) { while (mState == RECONFIGURING) { - mBufferFilled.wait(mLock); + if ((err = waitForBufferFilled_l()) != OK) { + return err; + } } if (mState != EXECUTING) { @@ -3694,19 +3711,15 @@ status_t OMXCodec::read( } while (mSeekTimeUs >= 0) { - mBufferFilled.wait(mLock); + if ((err = waitForBufferFilled_l()) != OK) { + return err; + } } } while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) { - if (mIsEncoder) { - if (NO_ERROR != mBufferFilled.waitRelative(mLock, 3000000000LL)) { - LOGW("Timed out waiting for buffers from video encoder: %d/%d", - countBuffersWeOwn(mPortBuffers[kPortIndexInput]), - countBuffersWeOwn(mPortBuffers[kPortIndexOutput])); - } - } else { - mBufferFilled.wait(mLock); + if ((err = waitForBufferFilled_l()) != OK) { + return err; } } @@ -4415,6 +4428,13 @@ status_t QueryCodecs( return OK; } +status_t QueryCodecs( + const sp<IOMX> &omx, + const char *mimeType, bool queryDecoders, + Vector<CodecCapabilities> *results) { + return QueryCodecs(omx, mimeType, queryDecoders, false /*hwCodecOnly*/, results); +} + void OMXCodec::restorePatchedDataPointer(BufferInfo *info) { CHECK(mIsEncoder && (mQuirks & kAvoidMemcopyInputRecordingFrames)); CHECK(mOMXLivesLocally); diff --git a/media/libstagefright/SurfaceMediaSource.cpp b/media/libstagefright/SurfaceMediaSource.cpp new file mode 100644 index 000000000000..ff4b08fb6c3f --- /dev/null +++ b/media/libstagefright/SurfaceMediaSource.cpp @@ -0,0 +1,756 @@ +/* + * Copyright (C) 2011 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_NDEBUG 0 +#define LOG_TAG "SurfaceMediaSource" + +#include <media/stagefright/SurfaceMediaSource.h> +#include <ui/GraphicBuffer.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/openmax/OMX_IVCommon.h> + +#include <surfaceflinger/ISurfaceComposer.h> +#include <surfaceflinger/SurfaceComposerClient.h> +#include <surfaceflinger/IGraphicBufferAlloc.h> +#include <OMX_Component.h> + +#include <utils/Log.h> +#include <utils/String8.h> + +namespace android { + +SurfaceMediaSource::SurfaceMediaSource(uint32_t bufW, uint32_t bufH) : + mDefaultWidth(bufW), + mDefaultHeight(bufH), + mPixelFormat(0), + mBufferCount(MIN_ASYNC_BUFFER_SLOTS), + mClientBufferCount(0), + mServerBufferCount(MIN_ASYNC_BUFFER_SLOTS), + mCurrentSlot(INVALID_BUFFER_SLOT), + mCurrentTimestamp(0), + mSynchronousMode(true), + mConnectedApi(NO_CONNECTED_API), + mFrameRate(30), + mStarted(false) { + LOGV("SurfaceMediaSource::SurfaceMediaSource"); + sp<ISurfaceComposer> composer(ComposerService::getComposerService()); + mGraphicBufferAlloc = composer->createGraphicBufferAlloc(); +} + +SurfaceMediaSource::~SurfaceMediaSource() { + LOGV("SurfaceMediaSource::~SurfaceMediaSource"); + if (mStarted) { + stop(); + } + freeAllBuffers(); +} + +size_t SurfaceMediaSource::getQueuedCount() const { + Mutex::Autolock lock(mMutex); + return mQueue.size(); +} + +status_t SurfaceMediaSource::setBufferCountServerLocked(int bufferCount) { + if (bufferCount > NUM_BUFFER_SLOTS) + return BAD_VALUE; + + // special-case, nothing to do + if (bufferCount == mBufferCount) + return OK; + + if (!mClientBufferCount && + bufferCount >= mBufferCount) { + // easy, we just have more buffers + mBufferCount = bufferCount; + mServerBufferCount = bufferCount; + mDequeueCondition.signal(); + } else { + // we're here because we're either + // - reducing the number of available buffers + // - or there is a client-buffer-count in effect + + // less than 2 buffers is never allowed + if (bufferCount < 2) + return BAD_VALUE; + + // when there is non client-buffer-count in effect, the client is not + // allowed to dequeue more than one buffer at a time, + // so the next time they dequeue a buffer, we know that they don't + // own one. the actual resizing will happen during the next + // dequeueBuffer. + + mServerBufferCount = bufferCount; + } + return OK; +} + +// Called from the consumer side +status_t SurfaceMediaSource::setBufferCountServer(int bufferCount) { + Mutex::Autolock lock(mMutex); + return setBufferCountServerLocked(bufferCount); +} + +status_t SurfaceMediaSource::setBufferCount(int bufferCount) { + LOGV("SurfaceMediaSource::setBufferCount"); + if (bufferCount > NUM_BUFFER_SLOTS) { + LOGE("setBufferCount: bufferCount is larger than the number of buffer slots"); + return BAD_VALUE; + } + + Mutex::Autolock lock(mMutex); + // Error out if the user has dequeued buffers + for (int i = 0 ; i < mBufferCount ; i++) { + if (mSlots[i].mBufferState == BufferSlot::DEQUEUED) { + LOGE("setBufferCount: client owns some buffers"); + return INVALID_OPERATION; + } + } + + if (bufferCount == 0) { + const int minBufferSlots = mSynchronousMode ? + MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS; + mClientBufferCount = 0; + bufferCount = (mServerBufferCount >= minBufferSlots) ? + mServerBufferCount : minBufferSlots; + return setBufferCountServerLocked(bufferCount); + } + + // We don't allow the client to set a buffer-count less than + // MIN_ASYNC_BUFFER_SLOTS (3), there is no reason for it. + if (bufferCount < MIN_ASYNC_BUFFER_SLOTS) { + return BAD_VALUE; + } + + // here we're guaranteed that the client doesn't have dequeued buffers + // and will release all of its buffer references. + freeAllBuffers(); + mBufferCount = bufferCount; + mClientBufferCount = bufferCount; + mCurrentSlot = INVALID_BUFFER_SLOT; + mQueue.clear(); + mDequeueCondition.signal(); + return OK; +} + +status_t SurfaceMediaSource::requestBuffer(int slot, sp<GraphicBuffer>* buf) { + LOGV("SurfaceMediaSource::requestBuffer"); + Mutex::Autolock lock(mMutex); + if (slot < 0 || mBufferCount <= slot) { + LOGE("requestBuffer: slot index out of range [0, %d]: %d", + mBufferCount, slot); + return BAD_VALUE; + } + mSlots[slot].mRequestBufferCalled = true; + *buf = mSlots[slot].mGraphicBuffer; + return NO_ERROR; +} + +status_t SurfaceMediaSource::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h, + uint32_t format, uint32_t usage) { + LOGV("dequeueBuffer"); + + + // Check for the buffer size- the client should just use the + // default width and height, and not try to set those. + // This is needed since + // the getFormat() returns mDefaultWidth/ Height for the OMX. It is + // queried by OMX in the beginning and not every time a frame comes. + // Not sure if there is a way to update the + // frame size while recording. So as of now, the client side + // sets the default values via the constructor, and the encoder is + // setup to encode frames of that size + // The design might need to change in the future. + // TODO: Currently just uses mDefaultWidth/Height. In the future + // we might declare mHeight and mWidth and check against those here. + if ((w != 0) || (h != 0)) { + LOGE("dequeuebuffer: invalid buffer size! Req: %dx%d, Found: %dx%d", + mDefaultWidth, mDefaultHeight, w, h); + return BAD_VALUE; + } + + Mutex::Autolock lock(mMutex); + + status_t returnFlags(OK); + + int found, foundSync; + int dequeuedCount = 0; + bool tryAgain = true; + while (tryAgain) { + // We need to wait for the FIFO to drain if the number of buffer + // needs to change. + // + // The condition "number of buffer needs to change" is true if + // - the client doesn't care about how many buffers there are + // - AND the actual number of buffer is different from what was + // set in the last setBufferCountServer() + // - OR - + // setBufferCountServer() was set to a value incompatible with + // the synchronization mode (for instance because the sync mode + // changed since) + // + // As long as this condition is true AND the FIFO is not empty, we + // wait on mDequeueCondition. + + int minBufferCountNeeded = mSynchronousMode ? + MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS; + + if (!mClientBufferCount && + ((mServerBufferCount != mBufferCount) || + (mServerBufferCount < minBufferCountNeeded))) { + // wait for the FIFO to drain + while (!mQueue.isEmpty()) { + LOGV("Waiting for the FIFO to drain"); + mDequeueCondition.wait(mMutex); + } + // need to check again since the mode could have changed + // while we were waiting + minBufferCountNeeded = mSynchronousMode ? + MIN_SYNC_BUFFER_SLOTS : MIN_ASYNC_BUFFER_SLOTS; + } + + if (!mClientBufferCount && + ((mServerBufferCount != mBufferCount) || + (mServerBufferCount < minBufferCountNeeded))) { + // here we're guaranteed that mQueue is empty + freeAllBuffers(); + mBufferCount = mServerBufferCount; + if (mBufferCount < minBufferCountNeeded) + mBufferCount = minBufferCountNeeded; + mCurrentSlot = INVALID_BUFFER_SLOT; + returnFlags |= ISurfaceTexture::RELEASE_ALL_BUFFERS; + } + + // look for a free buffer to give to the client + found = INVALID_BUFFER_SLOT; + foundSync = INVALID_BUFFER_SLOT; + dequeuedCount = 0; + for (int i = 0; i < mBufferCount; i++) { + const int state = mSlots[i].mBufferState; + if (state == BufferSlot::DEQUEUED) { + dequeuedCount++; + continue; // won't be continuing if could + // dequeue a non 'FREE' current slot like + // that in SurfaceTexture + } + // In case of Encoding, we do not deque the mCurrentSlot buffer + // since we follow synchronous mode (unlike possibly in + // SurfaceTexture that could be using the asynch mode + // or has some mechanism in GL to be able to wait till the + // currentslot is done using the data) + // Here, we have to wait for the MPEG4Writer(or equiv) + // to tell us when it's done using the current buffer + if (state == BufferSlot::FREE) { + foundSync = i; + // Unlike that in SurfaceTexture, + // We don't need to worry if it is the + // currentslot or not as it is in state FREE + found = i; + break; + } + } + + // clients are not allowed to dequeue more than one buffer + // if they didn't set a buffer count. + if (!mClientBufferCount && dequeuedCount) { + return -EINVAL; + } + + // See whether a buffer has been queued since the last setBufferCount so + // we know whether to perform the MIN_UNDEQUEUED_BUFFERS check below. + bool bufferHasBeenQueued = mCurrentSlot != INVALID_BUFFER_SLOT; + if (bufferHasBeenQueued) { + // make sure the client is not trying to dequeue more buffers + // than allowed. + const int avail = mBufferCount - (dequeuedCount+1); + if (avail < (MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode))) { + LOGE("dequeueBuffer: MIN_UNDEQUEUED_BUFFERS=%d exceeded (dequeued=%d)", + MIN_UNDEQUEUED_BUFFERS-int(mSynchronousMode), + dequeuedCount); + return -EBUSY; + } + } + + // we're in synchronous mode and didn't find a buffer, we need to wait + // for for some buffers to be consumed + tryAgain = mSynchronousMode && (foundSync == INVALID_BUFFER_SLOT); + if (tryAgain) { + LOGW("Waiting..In synchronous mode and no buffer to dQ"); + mDequeueCondition.wait(mMutex); + } + } + + if (mSynchronousMode && found == INVALID_BUFFER_SLOT) { + // foundSync guaranteed to be != INVALID_BUFFER_SLOT + found = foundSync; + } + + if (found == INVALID_BUFFER_SLOT) { + return -EBUSY; + } + + const int buf = found; + *outBuf = found; + + const bool useDefaultSize = !w && !h; + if (useDefaultSize) { + // use the default size + w = mDefaultWidth; + h = mDefaultHeight; + } + + const bool updateFormat = (format != 0); + if (!updateFormat) { + // keep the current (or default) format + format = mPixelFormat; + } + + // buffer is now in DEQUEUED (but can also be current at the same time, + // if we're in synchronous mode) + mSlots[buf].mBufferState = BufferSlot::DEQUEUED; + + const sp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer); + if ((buffer == NULL) || + (uint32_t(buffer->width) != w) || + (uint32_t(buffer->height) != h) || + (uint32_t(buffer->format) != format) || + ((uint32_t(buffer->usage) & usage) != usage)) { + usage |= GraphicBuffer::USAGE_HW_TEXTURE; + status_t error; + sp<GraphicBuffer> graphicBuffer( + mGraphicBufferAlloc->createGraphicBuffer( + w, h, format, usage, &error)); + if (graphicBuffer == 0) { + LOGE("dequeueBuffer: SurfaceComposer::createGraphicBuffer failed"); + return error; + } + if (updateFormat) { + mPixelFormat = format; + } + mSlots[buf].mGraphicBuffer = graphicBuffer; + mSlots[buf].mRequestBufferCalled = false; + returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION; + } + return returnFlags; +} + +status_t SurfaceMediaSource::setSynchronousMode(bool enabled) { + Mutex::Autolock lock(mMutex); + + status_t err = OK; + if (!enabled) { + // going to asynchronous mode, drain the queue + while (mSynchronousMode != enabled && !mQueue.isEmpty()) { + mDequeueCondition.wait(mMutex); + } + } + + if (mSynchronousMode != enabled) { + // - if we're going to asynchronous mode, the queue is guaranteed to be + // empty here + // - if the client set the number of buffers, we're guaranteed that + // we have at least 3 (because we don't allow less) + mSynchronousMode = enabled; + mDequeueCondition.signal(); + } + return err; +} + +status_t SurfaceMediaSource::connect(int api) { + LOGV("SurfaceMediaSource::connect"); + Mutex::Autolock lock(mMutex); + status_t err = NO_ERROR; + switch (api) { + case NATIVE_WINDOW_API_EGL: + case NATIVE_WINDOW_API_CPU: + case NATIVE_WINDOW_API_MEDIA: + case NATIVE_WINDOW_API_CAMERA: + if (mConnectedApi != NO_CONNECTED_API) { + err = -EINVAL; + } else { + mConnectedApi = api; + } + break; + default: + err = -EINVAL; + break; + } + return err; +} + +status_t SurfaceMediaSource::disconnect(int api) { + LOGV("SurfaceMediaSource::disconnect"); + Mutex::Autolock lock(mMutex); + status_t err = NO_ERROR; + switch (api) { + case NATIVE_WINDOW_API_EGL: + case NATIVE_WINDOW_API_CPU: + case NATIVE_WINDOW_API_MEDIA: + case NATIVE_WINDOW_API_CAMERA: + if (mConnectedApi == api) { + mConnectedApi = NO_CONNECTED_API; + } else { + err = -EINVAL; + } + break; + default: + err = -EINVAL; + break; + } + return err; +} + +status_t SurfaceMediaSource::queueBuffer(int buf, int64_t timestamp, + uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform) { + LOGV("queueBuffer"); + + Mutex::Autolock lock(mMutex); + if (buf < 0 || buf >= mBufferCount) { + LOGE("queueBuffer: slot index out of range [0, %d]: %d", + mBufferCount, buf); + return -EINVAL; + } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { + LOGE("queueBuffer: slot %d is not owned by the client (state=%d)", + buf, mSlots[buf].mBufferState); + return -EINVAL; + } else if (!mSlots[buf].mRequestBufferCalled) { + LOGE("queueBuffer: slot %d was enqueued without requesting a " + "buffer", buf); + return -EINVAL; + } + + if (mSynchronousMode) { + // in synchronous mode we queue all buffers in a FIFO + mQueue.push_back(buf); + LOGV("Client queued buffer on slot: %d, Q size = %d", + buf, mQueue.size()); + } else { + // in asynchronous mode we only keep the most recent buffer + if (mQueue.empty()) { + mQueue.push_back(buf); + } else { + Fifo::iterator front(mQueue.begin()); + // buffer currently queued is freed + mSlots[*front].mBufferState = BufferSlot::FREE; + // and we record the new buffer index in the queued list + *front = buf; + } + } + + mSlots[buf].mBufferState = BufferSlot::QUEUED; + mSlots[buf].mTimestamp = timestamp; + // TODO: (Confirm) Don't want to signal dequeue here. + // May be just in asynchronous mode? + // mDequeueCondition.signal(); + + // Once the queuing is done, we need to let the listener + // and signal the buffer consumer (encoder) know that a + // buffer is available + onFrameReceivedLocked(); + + *outWidth = mDefaultWidth; + *outHeight = mDefaultHeight; + *outTransform = 0; + + return OK; +} + + +// onFrameReceivedLocked informs the buffer consumers (StageFrightRecorder) +// or listeners that a frame has been received +// It is supposed to be called only from queuebuffer. +// The buffer is NOT made available for dequeueing immediately. We need to +// wait to hear from StageFrightRecorder to set the buffer FREE +// Make sure this is called when the mutex is locked +status_t SurfaceMediaSource::onFrameReceivedLocked() { + LOGV("On Frame Received"); + // Signal the encoder that a new frame has arrived + mFrameAvailableCondition.signal(); + + // call back the listener + // TODO: The listener may not be needed in SurfaceMediaSource at all. + // This can be made a SurfaceTexture specific thing + sp<FrameAvailableListener> listener; + if (mSynchronousMode || mQueue.empty()) { + listener = mFrameAvailableListener; + } + + if (listener != 0) { + listener->onFrameAvailable(); + } + return OK; +} + + +void SurfaceMediaSource::cancelBuffer(int buf) { + LOGV("SurfaceMediaSource::cancelBuffer"); + Mutex::Autolock lock(mMutex); + if (buf < 0 || buf >= mBufferCount) { + LOGE("cancelBuffer: slot index out of range [0, %d]: %d", + mBufferCount, buf); + return; + } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { + LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)", + buf, mSlots[buf].mBufferState); + return; + } + mSlots[buf].mBufferState = BufferSlot::FREE; + mDequeueCondition.signal(); +} + +nsecs_t SurfaceMediaSource::getTimestamp() { + LOGV("SurfaceMediaSource::getTimestamp"); + Mutex::Autolock lock(mMutex); + return mCurrentTimestamp; +} + + +void SurfaceMediaSource::setFrameAvailableListener( + const sp<FrameAvailableListener>& listener) { + LOGV("SurfaceMediaSource::setFrameAvailableListener"); + Mutex::Autolock lock(mMutex); + mFrameAvailableListener = listener; +} + +void SurfaceMediaSource::freeAllBuffers() { + LOGV("freeAllBuffers"); + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + mSlots[i].mGraphicBuffer = 0; + mSlots[i].mBufferState = BufferSlot::FREE; + } +} + +sp<GraphicBuffer> SurfaceMediaSource::getCurrentBuffer() const { + Mutex::Autolock lock(mMutex); + return mCurrentBuf; +} + +int SurfaceMediaSource::query(int what, int* outValue) +{ + LOGV("query"); + Mutex::Autolock lock(mMutex); + int value; + switch (what) { + case NATIVE_WINDOW_WIDTH: + value = mDefaultWidth; + if (!mDefaultWidth && !mDefaultHeight && mCurrentBuf != 0) + value = mCurrentBuf->width; + break; + case NATIVE_WINDOW_HEIGHT: + value = mDefaultHeight; + if (!mDefaultWidth && !mDefaultHeight && mCurrentBuf != 0) + value = mCurrentBuf->height; + break; + case NATIVE_WINDOW_FORMAT: + value = mPixelFormat; + break; + case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS: + value = mSynchronousMode ? + (MIN_UNDEQUEUED_BUFFERS-1) : MIN_UNDEQUEUED_BUFFERS; + break; + default: + return BAD_VALUE; + } + outValue[0] = value; + return NO_ERROR; +} + +void SurfaceMediaSource::dump(String8& result) const +{ + char buffer[1024]; + dump(result, "", buffer, 1024); +} + +void SurfaceMediaSource::dump(String8& result, const char* prefix, + char* buffer, size_t SIZE) const +{ + Mutex::Autolock _l(mMutex); + snprintf(buffer, SIZE, + "%smBufferCount=%d, mSynchronousMode=%d, default-size=[%dx%d], " + "mPixelFormat=%d, \n", + prefix, mBufferCount, mSynchronousMode, mDefaultWidth, mDefaultHeight, + mPixelFormat); + result.append(buffer); + + String8 fifo; + int fifoSize = 0; + Fifo::const_iterator i(mQueue.begin()); + while (i != mQueue.end()) { + snprintf(buffer, SIZE, "%02d ", *i++); + fifoSize++; + fifo.append(buffer); + } + + result.append(buffer); + + struct { + const char * operator()(int state) const { + switch (state) { + case BufferSlot::DEQUEUED: return "DEQUEUED"; + case BufferSlot::QUEUED: return "QUEUED"; + case BufferSlot::FREE: return "FREE"; + default: return "Unknown"; + } + } + } stateName; + + for (int i = 0; i < mBufferCount; i++) { + const BufferSlot& slot(mSlots[i]); + snprintf(buffer, SIZE, + "%s%s[%02d] state=%-8s, " + "timestamp=%lld\n", + prefix, (i==mCurrentSlot)?">":" ", i, stateName(slot.mBufferState), + slot.mTimestamp + ); + result.append(buffer); + } +} + +status_t SurfaceMediaSource::setFrameRate(int32_t fps) +{ + Mutex::Autolock lock(mMutex); + const int MAX_FRAME_RATE = 60; + if (fps < 0 || fps > MAX_FRAME_RATE) { + return BAD_VALUE; + } + mFrameRate = fps; + return OK; +} + +bool SurfaceMediaSource::isMetaDataStoredInVideoBuffers() const { + LOGV("isMetaDataStoredInVideoBuffers"); + return true; +} + +int32_t SurfaceMediaSource::getFrameRate( ) const { + Mutex::Autolock lock(mMutex); + return mFrameRate; +} + +status_t SurfaceMediaSource::start(MetaData *params) +{ + LOGV("start"); + Mutex::Autolock lock(mMutex); + CHECK(!mStarted); + mStarted = true; + return OK; +} + + +status_t SurfaceMediaSource::stop() +{ + LOGV("Stop"); + + Mutex::Autolock lock(mMutex); + // TODO: Add waiting on mFrameCompletedCondition here? + mStarted = false; + mFrameAvailableCondition.signal(); + + return OK; +} + +sp<MetaData> SurfaceMediaSource::getFormat() +{ + LOGV("getFormat"); + Mutex::Autolock autoLock(mMutex); + sp<MetaData> meta = new MetaData; + + meta->setInt32(kKeyWidth, mDefaultWidth); + meta->setInt32(kKeyHeight, mDefaultHeight); + // The encoder format is set as an opaque colorformat + // The encoder will later find out the actual colorformat + // from the GL Frames itself. + meta->setInt32(kKeyColorFormat, OMX_COLOR_FormatAndroidOpaque); + meta->setInt32(kKeyStride, mDefaultWidth); + meta->setInt32(kKeySliceHeight, mDefaultHeight); + meta->setInt32(kKeyFrameRate, mFrameRate); + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW); + return meta; +} + +status_t SurfaceMediaSource::read( MediaBuffer **buffer, + const ReadOptions *options) +{ + LOGV("Read. Size of queued buffer: %d", mQueue.size()); + *buffer = NULL; + + Mutex::Autolock autoLock(mMutex) ; + // If the recording has started and the queue is empty, then just + // wait here till the frames come in from the client side + while (mStarted && mQueue.empty()) { + LOGV("NO FRAMES! Recorder waiting for FrameAvailableCondition"); + mFrameAvailableCondition.wait(mMutex); + } + + // If the loop was exited as a result of stopping the recording, + // it is OK + if (!mStarted) { + return OK; + } + + // Update the current buffer info + // TODO: mCurrentSlot can be made a bufferstate since there + // can be more than one "current" slots. + Fifo::iterator front(mQueue.begin()); + mCurrentSlot = *front; + mCurrentBuf = mSlots[mCurrentSlot].mGraphicBuffer; + mCurrentTimestamp = mSlots[mCurrentSlot].mTimestamp; + + // Pass the data to the MediaBuffer + // TODO: Change later to pass in only the metadata + *buffer = new MediaBuffer(mCurrentBuf); + (*buffer)->setObserver(this); + (*buffer)->add_ref(); + (*buffer)->meta_data()->setInt64(kKeyTime, mCurrentTimestamp); + + return OK; +} + +void SurfaceMediaSource::signalBufferReturned(MediaBuffer *buffer) { + LOGV("signalBufferReturned"); + + bool foundBuffer = false; + Mutex::Autolock autoLock(mMutex); + + if (!mStarted) { + LOGV("started = false. Nothing to do"); + return; + } + + for (Fifo::iterator it = mQueue.begin(); it != mQueue.end(); ++it) { + if (mSlots[*it].mGraphicBuffer == buffer->graphicBuffer()) { + LOGV("Buffer %d returned. Setting it 'FREE'. New Queue size = %d", + *it, mQueue.size()-1); + mSlots[*it].mBufferState = BufferSlot::FREE; + mQueue.erase(it); + buffer->setObserver(0); + buffer->release(); + mDequeueCondition.signal(); + mFrameCompleteCondition.signal(); + foundBuffer = true; + break; + } + } + + if (!foundBuffer) { + CHECK_EQ(0, "signalBufferReturned: bogus buffer"); + } +} + + + +} // end of namespace android diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp index bf978d789dc5..c406964bd96d 100644 --- a/media/libstagefright/WAVExtractor.cpp +++ b/media/libstagefright/WAVExtractor.cpp @@ -370,7 +370,9 @@ status_t WAVSource::read( int16_t *dst = (int16_t *)tmp->data(); const uint8_t *src = (const uint8_t *)buffer->data(); - while (n-- > 0) { + ssize_t numBytes = n; + + while (numBytes-- > 0) { *dst++ = ((int16_t)(*src) - 128) * 256; ++src; } diff --git a/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp b/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp index 588a74d99242..07a9eb892c5d 100644 --- a/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp +++ b/media/libstagefright/chromium_http/ChromiumHTTPDataSource.cpp @@ -25,6 +25,8 @@ #include "support.h" +#include <cutils/properties.h> // for property_get + namespace android { ChromiumHTTPDataSource::ChromiumHTTPDataSource(uint32_t flags) @@ -111,7 +113,7 @@ void ChromiumHTTPDataSource::onConnectionFailed(status_t err) { mState = DISCONNECTED; mCondition.broadcast(); - mURI.clear(); + // mURI.clear(); mIOResult = err; @@ -150,8 +152,18 @@ ssize_t ChromiumHTTPDataSource::readAt(off64_t offset, void *data, size_t size) Mutex::Autolock autoLock(mLock); if (mState != CONNECTED) { - return ERROR_NOT_CONNECTED; + return INVALID_OPERATION; + } + +#if 0 + char value[PROPERTY_VALUE_MAX]; + if (property_get("media.stagefright.disable-net", value, 0) + && (!strcasecmp(value, "true") || !strcmp(value, "1"))) { + LOG_PRI(ANDROID_LOG_INFO, LOG_TAG, "Simulating that the network is down."); + disconnect_l(); + return ERROR_IO; } +#endif if (offset != mCurrentOffset) { AString tmp = mURI; @@ -236,7 +248,7 @@ void ChromiumHTTPDataSource::onDisconnectComplete() { CHECK_EQ((int)mState, (int)DISCONNECTING); mState = DISCONNECTED; - mURI.clear(); + // mURI.clear(); mCondition.broadcast(); @@ -299,5 +311,21 @@ void ChromiumHTTPDataSource::clearDRMState_l() { } } +status_t ChromiumHTTPDataSource::reconnectAtOffset(off64_t offset) { + Mutex::Autolock autoLock(mLock); + + if (mURI.empty()) { + return INVALID_OPERATION; + } + + LOG_PRI(ANDROID_LOG_INFO, LOG_TAG, "Reconnecting..."); + status_t err = connect_l(mURI.c_str(), &mHeaders, offset); + if (err != OK) { + LOG_PRI(ANDROID_LOG_INFO, LOG_TAG, "Reconnect failed w/ err 0x%08x", err); + } + + return err; +} + } // namespace android diff --git a/media/libstagefright/include/ChromiumHTTPDataSource.h b/media/libstagefright/include/ChromiumHTTPDataSource.h index d833e2efc1ba..18f89130c747 100644 --- a/media/libstagefright/include/ChromiumHTTPDataSource.h +++ b/media/libstagefright/include/ChromiumHTTPDataSource.h @@ -51,6 +51,8 @@ struct ChromiumHTTPDataSource : public HTTPBase { virtual String8 getMIMEType() const; + virtual status_t reconnectAtOffset(off64_t offset); + protected: virtual ~ChromiumHTTPDataSource(); diff --git a/media/libstagefright/include/NuCachedSource2.h b/media/libstagefright/include/NuCachedSource2.h index 2d6cb848292a..22b2855204e9 100644 --- a/media/libstagefright/include/NuCachedSource2.h +++ b/media/libstagefright/include/NuCachedSource2.h @@ -77,6 +77,10 @@ private: kWhatRead = 'read', }; + enum { + kMaxNumRetries = 10, + }; + sp<DataSource> mSource; sp<AHandlerReflector<NuCachedSource2> > mReflector; sp<ALooper> mLooper; @@ -93,6 +97,8 @@ private: bool mFetching; int64_t mLastFetchTimeUs; + int32_t mNumRetriesLeft; + void onMessageReceived(const sp<AMessage> &msg); void onFetch(); void onRead(const sp<AMessage> &msg); diff --git a/media/libstagefright/tests/Android.mk b/media/libstagefright/tests/Android.mk new file mode 100644 index 000000000000..3ea8f39ba699 --- /dev/null +++ b/media/libstagefright/tests/Android.mk @@ -0,0 +1,53 @@ +# Build the unit tests. +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +ifneq ($(TARGET_SIMULATOR),true) + +LOCAL_MODULE := SurfaceMediaSource_test + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := \ + SurfaceMediaSource_test.cpp \ + DummyRecorder.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libEGL \ + libGLESv2 \ + libandroid \ + libbinder \ + libcutils \ + libgui \ + libstlport \ + libui \ + libutils \ + libstagefright \ + libstagefright_omx \ + libstagefright_foundation \ + +LOCAL_STATIC_LIBRARIES := \ + libgtest \ + libgtest_main \ + +LOCAL_C_INCLUDES := \ + bionic \ + bionic/libstdc++/include \ + external/gtest/include \ + external/stlport/stlport \ + frameworks/base/media/libstagefright \ + frameworks/base/media/libstagefright/include \ + $(TOP)/frameworks/base/include/media/stagefright/openmax \ + +include $(BUILD_EXECUTABLE) + +endif + +# Include subdirectory makefiles +# ============================================================ + +# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework +# team really wants is to build the stuff defined by this makefile. +ifeq (,$(ONE_SHOT_MAKEFILE)) +include $(call first-makefiles-under,$(LOCAL_PATH)) +endif diff --git a/media/libstagefright/tests/DummyRecorder.cpp b/media/libstagefright/tests/DummyRecorder.cpp new file mode 100644 index 000000000000..8d75d6bc86ae --- /dev/null +++ b/media/libstagefright/tests/DummyRecorder.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011 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 "DummyRecorder" +// #define LOG_NDEBUG 0 + +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaSource.h> +#include "DummyRecorder.h" + +#include <utils/Log.h> + +namespace android { + +// static +void *DummyRecorder::threadWrapper(void *pthis) { + LOGV("ThreadWrapper: %p", pthis); + DummyRecorder *writer = static_cast<DummyRecorder *>(pthis); + writer->readFromSource(); + return NULL; +} + + +status_t DummyRecorder::start() { + LOGV("Start"); + mStarted = true; + + mSource->start(); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + int err = pthread_create(&mThread, &attr, threadWrapper, this); + pthread_attr_destroy(&attr); + + if (err) { + LOGE("Error creating thread!"); + return -ENODEV; + } + return OK; +} + + +status_t DummyRecorder::stop() { + LOGV("Stop"); + mStarted = false; + + mSource->stop(); + void *dummy; + pthread_join(mThread, &dummy); + status_t err = (status_t) dummy; + + LOGV("Ending the reading thread"); + return err; +} + +// pretend to read the source buffers +void DummyRecorder::readFromSource() { + LOGV("ReadFromSource"); + if (!mStarted) { + return; + } + + status_t err = OK; + MediaBuffer *buffer; + LOGV("A fake writer accessing the frames"); + while (mStarted && (err = mSource->read(&buffer)) == OK){ + // if not getting a valid buffer from source, then exit + if (buffer == NULL) { + return; + } + buffer->release(); + buffer = NULL; + } +} + + +} // end of namespace android diff --git a/media/libstagefright/tests/DummyRecorder.h b/media/libstagefright/tests/DummyRecorder.h new file mode 100644 index 000000000000..1cbea1b0dea2 --- /dev/null +++ b/media/libstagefright/tests/DummyRecorder.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DUMMY_RECORDER_H_ +#define DUMMY_RECORDER_H_ + +#include <pthread.h> +#include <utils/String8.h> +#include <media/stagefright/foundation/ABase.h> + + +namespace android { + +class MediaSource; +class MediaBuffer; + +class DummyRecorder { + public: + // The media source from which this will receive frames + sp<MediaSource> mSource; + bool mStarted; + pthread_t mThread; + + status_t start(); + status_t stop(); + + // actual entry point for the thread + void readFromSource(); + + // static function to wrap the actual thread entry point + static void *threadWrapper(void *pthis); + + DummyRecorder(const sp<MediaSource> &source) : mSource(source) + , mStarted(false) {} + ~DummyRecorder( ) {} + + private: + + DISALLOW_EVIL_CONSTRUCTORS(DummyRecorder); +}; + +} // end of namespace android +#endif + + diff --git a/media/libstagefright/tests/SurfaceMediaSource_test.cpp b/media/libstagefright/tests/SurfaceMediaSource_test.cpp new file mode 100644 index 000000000000..ce108122b8f7 --- /dev/null +++ b/media/libstagefright/tests/SurfaceMediaSource_test.cpp @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2011 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 "SurfaceMediaSource_test" +// #define LOG_NDEBUG 0 + +#include <gtest/gtest.h> +#include <utils/String8.h> +#include <utils/Errors.h> + +#include <media/stagefright/SurfaceMediaSource.h> + +#include <gui/SurfaceTextureClient.h> +#include <ui/GraphicBuffer.h> +#include <surfaceflinger/ISurfaceComposer.h> +#include <surfaceflinger/Surface.h> +#include <surfaceflinger/SurfaceComposerClient.h> + +#include <binder/ProcessState.h> +#include <ui/FramebufferNativeWindow.h> + +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MPEG4Writer.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/OMXCodec.h> +#include <OMX_Component.h> + +#include "DummyRecorder.h" + +namespace android { + + +class SurfaceMediaSourceTest : public ::testing::Test { +public: + + SurfaceMediaSourceTest( ): mYuvTexWidth(64), mYuvTexHeight(66) { } + sp<MPEG4Writer> setUpWriter(OMXClient &client ); + void oneBufferPass(int width, int height ); + static void fillYV12Buffer(uint8_t* buf, int w, int h, int stride) ; + static void fillYV12BufferRect(uint8_t* buf, int w, int h, + int stride, const android_native_rect_t& rect) ; +protected: + + virtual void SetUp() { + mSMS = new SurfaceMediaSource(mYuvTexWidth, mYuvTexHeight); + mSMS->setSynchronousMode(true); + mSTC = new SurfaceTextureClient(mSMS); + mANW = mSTC; + + } + + + virtual void TearDown() { + mSMS.clear(); + mSTC.clear(); + mANW.clear(); + } + + const int mYuvTexWidth;// = 64; + const int mYuvTexHeight;// = 66; + + sp<SurfaceMediaSource> mSMS; + sp<SurfaceTextureClient> mSTC; + sp<ANativeWindow> mANW; + +}; + +void SurfaceMediaSourceTest::oneBufferPass(int width, int height ) { + LOGV("One Buffer Pass"); + ANativeWindowBuffer* anb; + ASSERT_EQ(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); + ASSERT_TRUE(anb != NULL); + + sp<GraphicBuffer> buf(new GraphicBuffer(anb, false)); + ASSERT_EQ(NO_ERROR, mANW->lockBuffer(mANW.get(), buf->getNativeBuffer())); + + // Fill the buffer with the a checkerboard pattern + uint8_t* img = NULL; + buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img)); + SurfaceMediaSourceTest::fillYV12Buffer(img, width, height, buf->getStride()); + buf->unlock(); + + ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer())); +} + +sp<MPEG4Writer> SurfaceMediaSourceTest::setUpWriter(OMXClient &client ) { + // Writing to a file + const char *fileName = "/sdcard/outputSurfEnc.mp4"; + sp<MetaData> enc_meta = new MetaData; + enc_meta->setInt32(kKeyBitRate, 300000); + enc_meta->setInt32(kKeyFrameRate, 30); + + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); + + sp<MetaData> meta = mSMS->getFormat(); + + int32_t width, height, stride, sliceHeight, colorFormat; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + CHECK(meta->findInt32(kKeyStride, &stride)); + CHECK(meta->findInt32(kKeySliceHeight, &sliceHeight)); + CHECK(meta->findInt32(kKeyColorFormat, &colorFormat)); + + enc_meta->setInt32(kKeyWidth, width); + enc_meta->setInt32(kKeyHeight, height); + enc_meta->setInt32(kKeyIFramesInterval, 1); + enc_meta->setInt32(kKeyStride, stride); + enc_meta->setInt32(kKeySliceHeight, sliceHeight); + // TODO: overwriting the colorformat since the format set by GRAlloc + // could be wrong or not be read by OMX + enc_meta->setInt32(kKeyColorFormat, OMX_COLOR_FormatYUV420Planar); + // colorFormat); + + + sp<MediaSource> encoder = + OMXCodec::Create( + client.interface(), enc_meta, true /* createEncoder */, mSMS); + + sp<MPEG4Writer> writer = new MPEG4Writer(fileName); + writer->addSource(encoder); + + return writer; +} + +// Fill a YV12 buffer with a multi-colored checkerboard pattern +void SurfaceMediaSourceTest::fillYV12Buffer(uint8_t* buf, int w, int h, int stride) { + const int blockWidth = w > 16 ? w / 16 : 1; + const int blockHeight = h > 16 ? h / 16 : 1; + const int yuvTexOffsetY = 0; + int yuvTexStrideY = stride; + int yuvTexOffsetV = yuvTexStrideY * h; + int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; + int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2; + int yuvTexStrideU = yuvTexStrideV; + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + int parityX = (x / blockWidth) & 1; + int parityY = (y / blockHeight) & 1; + unsigned char intensity = (parityX ^ parityY) ? 63 : 191; + buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = intensity; + if (x < w / 2 && y < h / 2) { + buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = intensity; + if (x * 2 < w / 2 && y * 2 < h / 2) { + buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 0] = + buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 1] = + buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 0] = + buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 1] = + intensity; + } + } + } + } +} + +// Fill a YV12 buffer with red outside a given rectangle and green inside it. +void SurfaceMediaSourceTest::fillYV12BufferRect(uint8_t* buf, int w, + int h, int stride, const android_native_rect_t& rect) { + const int yuvTexOffsetY = 0; + int yuvTexStrideY = stride; + int yuvTexOffsetV = yuvTexStrideY * h; + int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf; + int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2; + int yuvTexStrideU = yuvTexStrideV; + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + bool inside = rect.left <= x && x < rect.right && + rect.top <= y && y < rect.bottom; + buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = inside ? 240 : 64; + if (x < w / 2 && y < h / 2) { + bool inside = rect.left <= 2*x && 2*x < rect.right && + rect.top <= 2*y && 2*y < rect.bottom; + buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = 16; + buf[yuvTexOffsetV + (y * yuvTexStrideV) + x] = + inside ? 16 : 255; + } + } + } +} ///////// End of class SurfaceMediaSourceTest + +/////////////////////////////////////////////////////////////////// +// Class to imitate the recording ///////////////////////////// +// //////////////////////////////////////////////////////////////// +struct SimpleDummyRecorder { + sp<MediaSource> mSource; + + SimpleDummyRecorder + (const sp<MediaSource> &source): mSource(source) {} + + status_t start() { return mSource->start();} + status_t stop() { return mSource->stop();} + + // fakes reading from a media source + status_t readFromSource() { + MediaBuffer *buffer; + status_t err = mSource->read(&buffer); + if (err != OK) { + return err; + } + buffer->release(); + buffer = NULL; + return OK; + } +}; + +/////////////////////////////////////////////////////////////////// +// TESTS +// Just pass one buffer from the native_window to the SurfaceMediaSource +TEST_F(SurfaceMediaSourceTest, EncodingFromCpuFilledYV12BufferNpotOneBufferPass) { + LOGV("Testing OneBufferPass ******************************"); + + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + 0, 0, HAL_PIXEL_FORMAT_YV12)); + // OMX_COLOR_FormatYUV420Planar)); // )); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + + oneBufferPass(mYuvTexWidth, mYuvTexHeight); +} + +// Pass the buffer with the wrong height and weight and should not be accepted +TEST_F(SurfaceMediaSourceTest, EncodingFromCpuFilledYV12BufferNpotWrongSizeBufferPass) { + LOGV("Testing Wrong size BufferPass ******************************"); + + // setting the client side buffer size different than the server size + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + 10, 10, HAL_PIXEL_FORMAT_YV12)); + // OMX_COLOR_FormatYUV420Planar)); // )); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + + ANativeWindowBuffer* anb; + + // make sure we get an error back when dequeuing! + ASSERT_NE(NO_ERROR, mANW->dequeueBuffer(mANW.get(), &anb)); +} + + +// pass multiple buffers from the native_window the SurfaceMediaSource +// A dummy writer is used to simulate actual MPEG4Writer +TEST_F(SurfaceMediaSourceTest, EncodingFromCpuFilledYV12BufferNpotMultiBufferPass) { + LOGV("Testing MultiBufferPass, Dummy Recorder *********************"); + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + 0, 0, HAL_PIXEL_FORMAT_YV12)); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + SimpleDummyRecorder writer(mSMS); + writer.start(); + + int32_t nFramesCount = 0; + while (nFramesCount < 300) { + oneBufferPass(mYuvTexWidth, mYuvTexHeight); + + ASSERT_EQ(NO_ERROR, writer.readFromSource()); + + nFramesCount++; + } + writer.stop(); +} + +// Delayed pass of multiple buffers from the native_window the SurfaceMediaSource +// A dummy writer is used to simulate actual MPEG4Writer +TEST_F(SurfaceMediaSourceTest, EncodingFromCpuFilledYV12BufferNpotMultiBufferPassLag) { + LOGV("Testing MultiBufferPass, Dummy Recorder Lagging **************"); + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + 0, 0, HAL_PIXEL_FORMAT_YV12)); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + SimpleDummyRecorder writer(mSMS); + writer.start(); + + int32_t nFramesCount = 1; + const int FRAMES_LAG = mSMS->getBufferCount() - 1; + while (nFramesCount <= 300) { + oneBufferPass(mYuvTexWidth, mYuvTexHeight); + // Forcing the writer to lag behind a few frames + if (nFramesCount > FRAMES_LAG) { + ASSERT_EQ(NO_ERROR, writer.readFromSource()); + } + nFramesCount++; + } + writer.stop(); +} + +// pass multiple buffers from the native_window the SurfaceMediaSource +// A dummy writer (MULTITHREADED) is used to simulate actual MPEG4Writer +TEST_F(SurfaceMediaSourceTest, EncodingFromCpuFilledYV12BufferNpotMultiBufferPassThreaded) { + LOGV("Testing MultiBufferPass, Dummy Recorder Multi-Threaded **********"); + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + 0, 0, HAL_PIXEL_FORMAT_YV12)); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + + DummyRecorder writer(mSMS); + writer.start(); + + int32_t nFramesCount = 0; + while (nFramesCount <= 300) { + oneBufferPass(mYuvTexWidth, mYuvTexHeight); + + nFramesCount++; + } + writer.stop(); +} + +// Test to examine the actual encoding. Temporarily disabled till the +// colorformat and encoding from GRAlloc data is resolved +TEST_F(SurfaceMediaSourceTest, DISABLED_EncodingFromCpuFilledYV12BufferNpotWrite) { + LOGV("Testing the whole pipeline with actual Recorder"); + ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(), + 0, 0, HAL_PIXEL_FORMAT_YV12)); // OMX_COLOR_FormatYUV420Planar)); // )); + ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)); + + OMXClient client; + CHECK_EQ(OK, client.connect()); + + sp<MPEG4Writer> writer = setUpWriter(client); + int64_t start = systemTime(); + CHECK_EQ(OK, writer->start()); + + int32_t nFramesCount = 0; + while (nFramesCount <= 300) { + oneBufferPass(mYuvTexWidth, mYuvTexHeight); + nFramesCount++; + } + + CHECK_EQ(OK, writer->stop()); + writer.clear(); + int64_t end = systemTime(); + client.disconnect(); +} + + +} // namespace android diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 5298f2e66927..d7d7817fc15b 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -34,7 +34,7 @@ <string name="config_systemBarComponent" translatable="false">com.android.systemui.statusbar.tablet.TabletStatusBar</string> <!-- Whether or not we show the number in the bar. --> - <bool name="config_statusBarShowNumber">true</bool> + <bool name="config_statusBarShowNumber">false</bool> <!-- How many icons may be shown at once in the system bar. Includes any slots that may be reused for things like IME control. --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 082dab312aeb..fc8ab96e3487 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -158,7 +158,7 @@ <!-- Checkbox label for application compatibility mode OFF (normal mode on tablets). [CHAR LIMIT=25] --> <string name="compat_mode_off">Stretch to fill screen</string> - + <!-- Compatibility mode help screen: header text. [CHAR LIMIT=50] --> <string name="compat_mode_help_header">Compatibility Zoom</string> @@ -168,7 +168,7 @@ <!-- toast message displayed when a screenshot is saved to the Gallery. --> <string name="screenshot_saving_toast">Screenshot saved to Gallery</string> <!-- toast message displayed when we fail to take a screenshot. --> - <string name="screenshot_failed_toast">Could not save screenshot</string> + <string name="screenshot_failed_toast">Could not save screenshot. External storage may be in use.</string> <!-- Title for the USB function chooser in UsbPreferenceActivity. [CHAR LIMIT=30] --> <string name="usb_preference_title">USB file transfer options</string> @@ -299,5 +299,4 @@ <!-- Content description of the ringer silent icon in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_ringer_silent">Ringer silent.</string> - </resources> diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java index cca7947e827d..ea544456430d 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java @@ -329,6 +329,7 @@ public class RecentsPanelView extends RelativeLayout mRecentsGlowView = findViewById(R.id.recents_glow); + mRecentsScrim = (View) findViewById(R.id.recents_bg_protect); mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this); mRecentsDismissButton = findViewById(R.id.recents_dismiss_button); mRecentsDismissButton.setOnClickListener(new OnClickListener() { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 83a5578cfa36..02a955badd3d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -87,7 +87,7 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi Context context = params[0].context; Bitmap image = params[0].image; - try{ + try { long currentTime = System.currentTimeMillis(); String date = new SimpleDateFormat("MM-dd-yy-kk-mm-ss").format(new Date(currentTime)); String imageDir = Environment.getExternalStoragePublicDirectory( @@ -114,7 +114,9 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi out.close(); params[0].result = 0; - }catch(IOException e){ + } catch (Exception e) { + // IOException/UnsupportedOperationException may be thrown if external storage is not + // mounted params[0].result = 1; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java index b4d2a14ecb07..d5885bb81c25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java @@ -148,6 +148,11 @@ public class NotificationRowLayout extends ViewGroup { "now sliding child %d: %s (touchY=%.1f, rowHeight=%d, count=%d)", childIdx, mSlidingChild, mInitialTouchY, mRowHeight, count)); } + + + // We need to prevent the surrounding ScrollView from intercepting us now; + // the scroll position will be locked while we swipe + requestDisallowInterceptTouchEvent(true); } } break; diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index b963b131f59f..bfc3cddb334f 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -388,6 +388,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { st.isHandled = false; mPreparedPanel = st; + if (st.frozenActionViewState != null) { + st.menu.restoreActionViewStates(st.frozenActionViewState); + st.frozenActionViewState = null; + } + return true; } @@ -652,7 +657,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void invalidatePanelMenu(int featureId) { PanelFeatureState st = getPanelState(featureId, true); + Bundle savedActionViewStates = null; if (st.menu != null) { + savedActionViewStates = new Bundle(); + st.menu.saveActionViewStates(savedActionViewStates); + if (savedActionViewStates.size() > 0) { + st.frozenActionViewState = savedActionViewStates; + } st.menu.clear(); } st.refreshMenuContent = true; @@ -2029,13 +2040,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mActionModePopup = new PopupWindow(mContext, null, com.android.internal.R.attr.actionModePopupWindowStyle); mActionModePopup.setLayoutInScreenEnabled(true); + mActionModePopup.setLayoutInsetDecor(true); mActionModePopup.setClippingEnabled(false); mActionModePopup.setContentView(mActionModeView); mActionModePopup.setWidth(MATCH_PARENT); TypedValue heightValue = new TypedValue(); mContext.getTheme().resolveAttribute( - com.android.internal.R.attr.actionBarSize, heightValue, false); + com.android.internal.R.attr.actionBarSize, heightValue, true); final int height = TypedValue.complexToDimensionPixelSize(heightValue.data, mContext.getResources().getDisplayMetrics()); mActionModePopup.setHeight(height); @@ -3024,6 +3036,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { */ Bundle frozenMenuState; + /** + * Contains the state of associated action views when told to freeze. + * These are saved across invalidations. + */ + Bundle frozenActionViewState; + PanelFeatureState(int featureId) { this.featureId = featureId; diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index ad6cebb60b78..b7f6adffca1a 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -166,22 +166,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int SYSTEM_DIALOG_LAYER = 6; // toasts and the plugged-in battery thing static final int TOAST_LAYER = 7; - static final int STATUS_BAR_LAYER = 8; - static final int STATUS_BAR_PANEL_LAYER = 9; // SIM errors and unlock. Not sure if this really should be in a high layer. - static final int PRIORITY_PHONE_LAYER = 10; + static final int PRIORITY_PHONE_LAYER = 8; // like the ANR / app crashed dialogs - static final int SYSTEM_ALERT_LAYER = 11; + static final int SYSTEM_ALERT_LAYER = 9; // system-level error dialogs - static final int SYSTEM_ERROR_LAYER = 12; + static final int SYSTEM_ERROR_LAYER = 10; // on-screen keyboards and other such input method user interfaces go here. - static final int INPUT_METHOD_LAYER = 13; + static final int INPUT_METHOD_LAYER = 11; // on-screen keyboards and other such input method user interfaces go here. - static final int INPUT_METHOD_DIALOG_LAYER = 14; + static final int INPUT_METHOD_DIALOG_LAYER = 12; // the keyguard; nothing on top of these can take focus, since they are // responsible for power management when displayed. - static final int KEYGUARD_LAYER = 15; - static final int KEYGUARD_DIALOG_LAYER = 16; + static final int KEYGUARD_LAYER = 13; + static final int KEYGUARD_DIALOG_LAYER = 14; + static final int STATUS_BAR_LAYER = 15; + static final int STATUS_BAR_PANEL_LAYER = 16; // the navigation bar, if available, shows atop most things static final int NAVIGATION_BAR_LAYER = 17; // the drag layer: input for drag-and-drop is associated with this window, @@ -1703,7 +1703,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } } - if (DEBUG_LAYOUT) Log.i(TAG, "mNavigationBar frame: " + navr); + if (DEBUG_LAYOUT) { + Log.i(TAG, "mNavigationBar frame: " + navr); + Log.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)", + mDockLeft, mDockTop, mDockRight, mDockBottom)); + } // apply navigation bar insets pf.left = df.left = vf.left = mDockLeft; @@ -1713,6 +1717,25 @@ public class PhoneWindowManager implements WindowManagerPolicy { mStatusBar.computeFrameLw(pf, df, vf, vf); + // now, let's consider the navigation bar; if it exists, it must be removed from the + // available screen real estate (like an un-hideable status bar) + if (navr != null) { + if (navr.top == 0) { + // Navigation bar is vertical + if (mRestrictedScreenLeft == navr.left) { + mRestrictedScreenLeft = navr.right; + mRestrictedScreenWidth -= (navr.right - navr.left); + } else if ((mRestrictedScreenLeft+mRestrictedScreenWidth) == navr.right) { + mRestrictedScreenWidth -= (navr.right - navr.left); + } + } else { + // Navigation bar horizontal, at bottom + if ((mRestrictedScreenHeight+mRestrictedScreenTop) == navr.bottom) { + mRestrictedScreenHeight -= (navr.bottom-navr.top); + } + } + } + if (mStatusBar.isVisibleLw()) { // If the status bar is hidden, we don't want to cause // windows behind it to scroll. @@ -1745,23 +1768,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mRestrictedScreenHeight -= (r.bottom-r.top); } - if (navr != null) { - if (navr.top == 0) { - // Navigation bar is vertical - if (mRestrictedScreenLeft == navr.left) { - mRestrictedScreenLeft = navr.right; - mRestrictedScreenWidth -= (navr.right - navr.left); - } else if ((mRestrictedScreenLeft+mRestrictedScreenWidth) == navr.right) { - mRestrictedScreenWidth -= (navr.right - navr.left); - } - } else { - // Navigation bar horizontal, at bottom - if ((mRestrictedScreenHeight-mRestrictedScreenTop) == r.bottom) { - mRestrictedScreenHeight -= (navr.bottom-navr.top); - } - } - } - mContentTop = mCurTop = mDockTop = mRestrictedScreenTop; mContentBottom = mCurBottom = mDockBottom = mRestrictedScreenTop + mRestrictedScreenHeight; @@ -1873,19 +1879,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { // permission, so they have the same privileges as the status // bar itself. // - // However, they should still dodge the navigation bar if it exists. A - // straightforward way to do this is to only allow the status bar panels to - // extend to the extrema of the allowable region for the IME dock. + // However, they should still dodge the navigation bar if it exists. pf.left = df.left = hasNavBar ? mDockLeft : mUnrestrictedScreenLeft; pf.top = df.top = mUnrestrictedScreenTop; pf.right = df.right = hasNavBar - ? mDockRight + ? mRestrictedScreenLeft+mRestrictedScreenWidth : mUnrestrictedScreenLeft+mUnrestrictedScreenWidth; pf.bottom = df.bottom = hasNavBar - ? mDockBottom + ? mRestrictedScreenTop+mRestrictedScreenHeight : mUnrestrictedScreenTop+mUnrestrictedScreenHeight; + if (DEBUG_LAYOUT) { + Log.v(TAG, String.format( + "Laying out status bar window: (%d,%d - %d,%d)", + pf.left, pf.top, pf.right, pf.bottom)); + } } else { pf.left = df.left = mRestrictedScreenLeft; pf.top = df.top = mRestrictedScreenTop; @@ -1922,12 +1931,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { pf.left = df.left = cf.left = hasNavBar ? mDockLeft : mUnrestrictedScreenLeft; pf.top = df.top = cf.top = mUnrestrictedScreenTop; pf.right = df.right = cf.right = hasNavBar - ? mDockRight + ? mRestrictedScreenLeft+mRestrictedScreenWidth : mUnrestrictedScreenLeft+mUnrestrictedScreenWidth; pf.bottom = df.bottom = cf.bottom = hasNavBar - ? mDockBottom + ? mRestrictedScreenTop+mRestrictedScreenHeight : mUnrestrictedScreenTop+mUnrestrictedScreenHeight; + } else if (attrs.type == TYPE_NAVIGATION_BAR) { + // The navigation bar has Real Ultimate Power. + pf.left = df.left = mUnrestrictedScreenLeft; + pf.top = df.top = mUnrestrictedScreenTop; + pf.right = df.right = mUnrestrictedScreenLeft+mUnrestrictedScreenWidth; + pf.bottom = df.bottom = mUnrestrictedScreenTop+mUnrestrictedScreenHeight; + if (DEBUG_LAYOUT) { + Log.v(TAG, String.format( + "Laying out navigation bar window: (%d,%d - %d,%d)", + pf.left, pf.top, pf.right, pf.bottom)); + } } else { pf.left = df.left = cf.left = mRestrictedScreenLeft; pf.top = df.top = cf.top = mRestrictedScreenTop; @@ -2424,20 +2444,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_DOWN: if (down) { - // If the power key down was already triggered, take the screenshot - if (mPowerDownTriggered) { - // Dismiss the power-key longpress - mHandler.removeCallbacks(mPowerLongPress); - mPowerKeyHandled = true; - - // Take the screenshot - takeScreenshot(); - - // Prevent the event from being passed through to the current activity - result &= ~ACTION_PASS_TO_USER; - break; + if (isScreenOn) { + // If the power key down was already triggered, take the screenshot + if (mPowerDownTriggered) { + // Dismiss the power-key longpress + mHandler.removeCallbacks(mPowerLongPress); + mPowerKeyHandled = true; + + // Take the screenshot + takeScreenshot(); + + // Prevent the event from being passed through to the current activity + result &= ~ACTION_PASS_TO_USER; + break; + } + mVolumeDownTriggered = true; } - mVolumeDownTriggered = true; } else { mVolumeDownTriggered = false; } @@ -2521,17 +2543,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_POWER: { result &= ~ACTION_PASS_TO_USER; if (down) { - // If the volume down key has been triggered, then just take the screenshot - if (mVolumeDownTriggered) { - // Take the screenshot - takeScreenshot(); - mPowerKeyHandled = true; - - // Prevent the event from being passed through to the current activity - break; + if (isScreenOn) { + // If the volume down key has been triggered, then just take the screenshot + if (mVolumeDownTriggered) { + // Take the screenshot + takeScreenshot(); + mPowerKeyHandled = true; + + // Prevent the event from being passed through to the current activity + break; + } + mPowerDownTriggered = true; } - mPowerDownTriggered = true; - ITelephony telephonyService = getTelephonyService(); boolean hungUp = false; diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 482336ba9eb8..0323fe0001a3 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -2954,7 +2954,7 @@ AudioFlinger::PlaybackThread::Track::Track( mStreamType = streamType; // NOTE: audio_track_cblk_t::frameSize for 8 bit PCM data is based on a sample size of // 16 bit because data is converted to 16 bit before being stored in buffer by AudioTrack - mCblk->frameSize = audio_is_linear_pcm(format) ? mChannelCount * audio_bytes_per_sample(format) : sizeof(uint8_t); + mCblk->frameSize = audio_is_linear_pcm(format) ? mChannelCount * sizeof(int16_t) : sizeof(uint8_t); } } diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 438883e9caf3..a679ca72daa2 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -230,8 +230,14 @@ class AppWidgetService extends IAppWidgetService.Stub pw.println(':'); pw.print(" min=("); pw.print(info.minWidth); pw.print("x"); pw.print(info.minHeight); + pw.print(") minResize=("); pw.print(info.minResizeWidth); + pw.print("x"); pw.print(info.minResizeHeight); pw.print(") updatePeriodMillis="); pw.print(info.updatePeriodMillis); + pw.print(" resizeMode="); + pw.print(info.resizeMode); + pw.print(" autoAdvanceViewId="); + pw.print(info.autoAdvanceViewId); pw.print(" initialLayout=#"); pw.print(Integer.toHexString(info.initialLayout)); pw.print(" zombie="); pw.println(p.zombie); diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 5dd3a6a3a11f..79c067570982 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -1090,7 +1090,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { try { InetAddress addr = InetAddress.getByAddress(hostAddress); LinkProperties lp = tracker.getLinkProperties(); - return addRoute(lp, RouteInfo.makeHostRoute(addr)); + return addRouteToAddress(lp, addr); } catch (UnknownHostException e) {} return false; } @@ -1103,6 +1103,31 @@ public class ConnectivityService extends IConnectivityManager.Stub { return modifyRoute(p.getInterfaceName(), p, r, 0, false); } + private boolean addRouteToAddress(LinkProperties lp, InetAddress addr) { + return modifyRouteToAddress(lp, addr, true); + } + + private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr) { + return modifyRouteToAddress(lp, addr, false); + } + + private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd) { + RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), addr); + if (bestRoute == null) { + bestRoute = RouteInfo.makeHostRoute(addr); + } else { + if (bestRoute.getGateway().equals(addr)) { + // if there is no better route, add the implied hostroute for our gateway + bestRoute = RouteInfo.makeHostRoute(addr); + } else { + // if we will connect to this through another route, add a direct route + // to it's gateway + bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway()); + } + } + return modifyRoute(lp.getInterfaceName(), lp, bestRoute, 0, doAdd); + } + private boolean modifyRoute(String ifaceName, LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd) { if ((ifaceName == null) || (lp == null) || (r == null)) return false; @@ -1713,49 +1738,50 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private void updateRoutes(LinkProperties newLp, LinkProperties curLp, boolean isLinkDefault) { Collection<RouteInfo> routesToAdd = null; - CompareResult<InetAddress> dnsDiff = null; - + CompareResult<InetAddress> dnsDiff = new CompareResult<InetAddress>(); + CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>(); if (curLp != null) { // check for the delta between the current set and the new - CompareResult<RouteInfo> routeDiff = curLp.compareRoutes(newLp); + routeDiff = curLp.compareRoutes(newLp); dnsDiff = curLp.compareDnses(newLp); - - for (RouteInfo r : routeDiff.removed) { - if (isLinkDefault || ! r.isDefaultRoute()) { - removeRoute(curLp, r); - } - } - routesToAdd = routeDiff.added; + } else if (newLp != null) { + routeDiff.added = newLp.getRoutes(); + dnsDiff.added = newLp.getDnses(); } - if (newLp != null) { - // if we didn't get a diff from cur -> new, then just use the new - if (routesToAdd == null) { - routesToAdd = newLp.getRoutes(); + for (RouteInfo r : routeDiff.removed) { + if (isLinkDefault || ! r.isDefaultRoute()) { + removeRoute(curLp, r); } + } - for (RouteInfo r : routesToAdd) { - if (isLinkDefault || ! r.isDefaultRoute()) { - addRoute(newLp, r); - } + for (RouteInfo r : routeDiff.added) { + if (isLinkDefault || ! r.isDefaultRoute()) { + addRoute(newLp, r); } } if (!isLinkDefault) { // handle DNS routes - Collection<InetAddress> dnsToAdd = null; - if (dnsDiff != null) { - dnsToAdd = dnsDiff.added; - for (InetAddress dnsAddress : dnsDiff.removed) { - removeRoute(curLp, RouteInfo.makeHostRoute(dnsAddress)); + if (routeDiff.removed.size() == 0 && routeDiff.added.size() == 0) { + // no change in routes, check for change in dns themselves + for (InetAddress oldDns : dnsDiff.removed) { + removeRouteToAddress(curLp, oldDns); } - } - if (newLp != null) { - if (dnsToAdd == null) { - dnsToAdd = newLp.getDnses(); + for (InetAddress newDns : dnsDiff.added) { + addRouteToAddress(newLp, newDns); } - for(InetAddress dnsAddress : dnsToAdd) { - addRoute(newLp, RouteInfo.makeHostRoute(dnsAddress)); + } else { + // routes changed - remove all old dns entries and add new + if (curLp != null) { + for (InetAddress oldDns : curLp.getDnses()) { + removeRouteToAddress(curLp, oldDns); + } + } + if (newLp != null) { + for (InetAddress newDns : newLp.getDnses()) { + addRouteToAddress(newLp, newDns); + } } } } @@ -2236,6 +2262,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + public int setUsbTethering(boolean enable) { + enforceTetherAccessPermission(); + if (isTetheringSupported()) { + return mTethering.setUsbTethering(enable); + } else { + return ConnectivityManager.TETHER_ERROR_UNSUPPORTED; + } + } + // TODO - move iface listing, queries, etc to new module // javadoc from interface public String[] getTetherableIfaces() { diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 73996795205e..73d790a073c0 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -166,7 +166,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private final KeyguardManager mKeyguardManager; private final Notification mImeSwitcherNotification; private final PendingIntent mImeSwitchPendingIntent; - private final boolean mShowOngoingImeSwitcherForPhones; + private boolean mShowOngoingImeSwitcherForPhones; private boolean mNotificationShown; class SessionState { @@ -538,8 +538,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mImeSwitcherNotification.vibrate = null; Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER); mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); - mShowOngoingImeSwitcherForPhones = mRes.getBoolean( - com.android.internal.R.bool.show_ongoing_ime_switcher); + + mShowOngoingImeSwitcherForPhones = false; synchronized (mMethodMap) { mFileManager = new InputMethodFileManager(mMethodMap); @@ -612,6 +612,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { if (!mSystemReady) { mSystemReady = true; + mShowOngoingImeSwitcherForPhones = mRes.getBoolean( + com.android.internal.R.bool.show_ongoing_ime_switcher); try { startInputInnerLocked(); } catch (RuntimeException e) { @@ -1125,13 +1127,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mBackDisposition = backDisposition; mStatusBar.setImeWindowStatus(token, vis, backDisposition); final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0; - if (iconVisibility && needsToShowImeSwitchOngoingNotification()) { + final InputMethodInfo imi = mMethodMap.get(mCurMethodId); + if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) { final PackageManager pm = mContext.getPackageManager(); - final CharSequence label = mMethodMap.get(mCurMethodId).loadLabel(pm); final CharSequence title = mRes.getText( com.android.internal.R.string.select_input_method); + final CharSequence imiLabel = imi.loadLabel(pm); + final CharSequence summary = mCurrentSubtype != null + ? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext, + imi.getPackageName(), imi.getServiceInfo().applicationInfo), + (TextUtils.isEmpty(imiLabel) ? + "" : " (" + imiLabel + ")")) + : imiLabel; + mImeSwitcherNotification.setLatestEventInfo( - mContext, title, label, mImeSwitchPendingIntent); + mContext, title, summary, mImeSwitchPendingIntent); mNotificationManager.notify( com.android.internal.R.string.select_input_method, mImeSwitcherNotification); diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 41e8a3148223..17ad268f0386 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -93,6 +93,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { private static final String KEY_TX = "tx_bytes"; class NetdResponseCode { + /* Keep in sync with system/netd/ResponseCode.h */ public static final int InterfaceListResult = 110; public static final int TetherInterfaceListResult = 111; public static final int TetherDnsFwdTgtListResult = 112; @@ -108,6 +109,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { public static final int InterfaceTxThrottleResult = 219; public static final int InterfaceChange = 600; + public static final int BandwidthControl = 601; } /** @@ -265,6 +267,20 @@ class NetworkManagementService extends INetworkManagementService.Stub { } /** + * Notify our observers of a limit reached. + */ + private void notifyLimitReached(String limitName, String iface) { + for (INetworkManagementEventObserver obs : mObservers) { + try { + obs.limitReached(limitName, iface); + Slog.d(TAG, "Observer notified limit reached for " + limitName + " " + iface); + } catch (Exception ex) { + Slog.w(TAG, "Observer notifier failed", ex); + } + } + } + + /** * Let us know the daemon is connected */ protected void onConnected() { @@ -286,33 +302,52 @@ class NetworkManagementService extends INetworkManagementService.Stub { }.start(); } public boolean onEvent(int code, String raw, String[] cooked) { - if (code == NetdResponseCode.InterfaceChange) { - /* - * a network interface change occured - * Format: "NNN Iface added <name>" - * "NNN Iface removed <name>" - * "NNN Iface changed <name> <up/down>" - * "NNN Iface linkstatus <name> <up/down>" - */ - if (cooked.length < 4 || !cooked[1].equals("Iface")) { + switch (code) { + case NetdResponseCode.InterfaceChange: + /* + * a network interface change occured + * Format: "NNN Iface added <name>" + * "NNN Iface removed <name>" + * "NNN Iface changed <name> <up/down>" + * "NNN Iface linkstatus <name> <up/down>" + */ + if (cooked.length < 4 || !cooked[1].equals("Iface")) { + throw new IllegalStateException( + String.format("Invalid event from daemon (%s)", raw)); + } + if (cooked[2].equals("added")) { + notifyInterfaceAdded(cooked[3]); + return true; + } else if (cooked[2].equals("removed")) { + notifyInterfaceRemoved(cooked[3]); + return true; + } else if (cooked[2].equals("changed") && cooked.length == 5) { + notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up")); + return true; + } else if (cooked[2].equals("linkstate") && cooked.length == 5) { + notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up")); + return true; + } throw new IllegalStateException( String.format("Invalid event from daemon (%s)", raw)); - } - if (cooked[2].equals("added")) { - notifyInterfaceAdded(cooked[3]); - return true; - } else if (cooked[2].equals("removed")) { - notifyInterfaceRemoved(cooked[3]); - return true; - } else if (cooked[2].equals("changed") && cooked.length == 5) { - notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up")); - return true; - } else if (cooked[2].equals("linkstate") && cooked.length == 5) { - notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up")); - return true; - } - throw new IllegalStateException( - String.format("Invalid event from daemon (%s)", raw)); + // break; + case NetdResponseCode.BandwidthControl: + /* + * Bandwidth control needs some attention + * Format: "NNN limit alert <alertName> <ifaceName>" + */ + if (cooked.length < 5 || !cooked[1].equals("limit")) { + throw new IllegalStateException( + String.format("Invalid event from daemon (%s)", raw)); + } + if (cooked[2].equals("alert")) { + notifyLimitReached(cooked[3], cooked[4]); + return true; + } + throw new IllegalStateException( + String.format("Invalid event from daemon (%s)", raw)); + // break; + default: break; } return false; } @@ -814,7 +849,6 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); try { - mConnector.doCommand(String.format("softap stop " + wlanIface)); mConnector.doCommand(String.format("softap fwreload " + wlanIface + " AP")); mConnector.doCommand(String.format("softap start " + wlanIface)); if (wifiConfig == null) { @@ -862,13 +896,15 @@ class NetworkManagementService extends INetworkManagementService.Stub { } } - public void stopAccessPoint() throws IllegalStateException { + public void stopAccessPoint(String wlanIface) throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); try { mConnector.doCommand("softap stopap"); + mConnector.doCommand("softap stop " + wlanIface); + mConnector.doCommand(String.format("softap fwreload " + wlanIface + " STA")); } catch (NativeDaemonConnectorException e) { throw new IllegalStateException("Error communicating to native daemon to stop soft AP", e); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 8c7e279b7132..f15eca67d70d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -37,6 +37,7 @@ import android.provider.Settings; import android.server.BluetoothA2dpService; import android.server.BluetoothService; import android.server.search.SearchManagerService; +import android.server.WifiP2pService; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Slog; @@ -108,6 +109,7 @@ class ServerThread extends Thread { NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; ConnectivityService connectivity = null; + WifiP2pService wifiP2p = null; IPackageManager pm = null; Context context = null; WindowManagerService wm = null; @@ -230,6 +232,7 @@ class ServerThread extends Thread { WallpaperManagerService wallpaper = null; LocationManagerService location = null; CountryDetectorService countryDetector = null; + TextServicesManagerService tsms = null; if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { @@ -273,6 +276,14 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Text Service Manager Service"); + tsms = new TextServicesManagerService(context); + ServiceManager.addService(Context.TEXT_SERVICES_MANAGER_SERVICE, tsms); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting Text Service Manager Service", e); + } + + try { Slog.i(TAG, "NetworkStats Service"); networkStats = new NetworkStatsService(context, networkManagement, alarm); ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats); @@ -290,6 +301,14 @@ class ServerThread extends Thread { Slog.e(TAG, "Failure starting NetworkPolicy Service", e); } + try { + Slog.i(TAG, "Wi-Fi P2pService"); + wifiP2p = new WifiP2pService(context); + ServiceManager.addService(Context.WIFI_P2P_SERVICE, wifiP2p); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting Wi-Fi P2pService", e); + } + try { Slog.i(TAG, "Connectivity Service"); connectivity = new ConnectivityService(context, networkManagement, networkPolicy); @@ -538,6 +557,7 @@ class ServerThread extends Thread { final LocationManagerService locationF = location; final CountryDetectorService countryDetectorF = countryDetector; final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater; + final TextServicesManagerService textServiceManagerServiceF = tsms; // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state @@ -571,6 +591,7 @@ class ServerThread extends Thread { if (countryDetectorF != null) countryDetectorF.systemReady(); if (throttleF != null) throttleF.systemReady(); if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady(); + if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady(); } }); diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java new file mode 100644 index 000000000000..ffdc288db05d --- /dev/null +++ b/services/java/com/android/server/TextServicesManagerService.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2011 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; + +import com.android.internal.content.PackageMonitor; +import com.android.internal.textservice.ISpellCheckerService; +import com.android.internal.textservice.ISpellCheckerSession; +import com.android.internal.textservice.ISpellCheckerSessionListener; +import com.android.internal.textservice.ITextServicesManager; +import com.android.internal.textservice.ITextServicesSessionListener; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.provider.Settings; +import android.text.TextUtils; +import android.service.textservice.SpellCheckerService; +import android.util.Log; +import android.util.Slog; +import android.view.textservice.SpellCheckerInfo; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +public class TextServicesManagerService extends ITextServicesManager.Stub { + private static final String TAG = TextServicesManagerService.class.getSimpleName(); + private static final boolean DBG = false; + + private final Context mContext; + private boolean mSystemReady; + private final TextServicesMonitor mMonitor; + private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap = + new HashMap<String, SpellCheckerInfo>(); + private final ArrayList<SpellCheckerInfo> mSpellCheckerList = new ArrayList<SpellCheckerInfo>(); + private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups = + new HashMap<String, SpellCheckerBindGroup>(); + + public void systemReady() { + if (!mSystemReady) { + mSystemReady = true; + } + } + + public TextServicesManagerService(Context context) { + mSystemReady = false; + mContext = context; + mMonitor = new TextServicesMonitor(); + mMonitor.register(context, true); + synchronized (mSpellCheckerMap) { + buildSpellCheckerMapLocked(context, mSpellCheckerList, mSpellCheckerMap); + } + } + + private class TextServicesMonitor extends PackageMonitor { + @Override + public void onSomePackagesChanged() { + synchronized (mSpellCheckerMap) { + buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap); + // TODO: Update for each locale + SpellCheckerInfo sci = getCurrentSpellChecker(null); + if (sci == null) { + sci = findAvailSpellCheckerLocked(null, null); + if (sci == null) return; + // Set the current spell checker if there is one or more spell checkers + // available. In this case, "sci" is the first one in the available spell + // checkers. + setCurrentSpellChecker(sci); + } + final String packageName = sci.getPackageName(); + final int change = isPackageDisappearing(packageName); + if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) { + // Package disappearing + setCurrentSpellChecker(findAvailSpellCheckerLocked(null, packageName)); + } else if (isPackageModified(packageName)) { + // Package modified + setCurrentSpellChecker(findAvailSpellCheckerLocked(null, packageName)); + } + } + } + } + + private static void buildSpellCheckerMapLocked(Context context, + ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map) { + list.clear(); + map.clear(); + final PackageManager pm = context.getPackageManager(); + List<ResolveInfo> services = pm.queryIntentServices( + new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA); + final int N = services.size(); + for (int i = 0; i < N; ++i) { + final ResolveInfo ri = services.get(i); + final ServiceInfo si = ri.serviceInfo; + final ComponentName compName = new ComponentName(si.packageName, si.name); + if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) { + Slog.w(TAG, "Skipping text service " + compName + + ": it does not require the permission " + + android.Manifest.permission.BIND_TEXT_SERVICE); + continue; + } + if (DBG) Slog.d(TAG, "Add: " + compName); + final SpellCheckerInfo sci = new SpellCheckerInfo(context, ri); + list.add(sci); + map.put(sci.getId(), sci); + } + } + + // TODO: find an appropriate spell checker for specified locale + private SpellCheckerInfo findAvailSpellCheckerLocked(String locale, String prefPackage) { + final int spellCheckersCount = mSpellCheckerList.size(); + if (spellCheckersCount == 0) { + Slog.w(TAG, "no available spell checker services found"); + return null; + } + if (prefPackage != null) { + for (int i = 0; i < spellCheckersCount; ++i) { + final SpellCheckerInfo sci = mSpellCheckerList.get(i); + if (prefPackage.equals(sci.getPackageName())) { + return sci; + } + } + } + if (spellCheckersCount > 1) { + Slog.w(TAG, "more than one spell checker service found, picking first"); + } + return mSpellCheckerList.get(0); + } + + // TODO: Save SpellCheckerService by supported languages. Currently only one spell + // checker is saved. + @Override + public SpellCheckerInfo getCurrentSpellChecker(String locale) { + synchronized (mSpellCheckerMap) { + final String curSpellCheckerId = + Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.SPELL_CHECKER_SERVICE); + if (DBG) { + Slog.w(TAG, "getCurrentSpellChecker: " + curSpellCheckerId); + } + if (TextUtils.isEmpty(curSpellCheckerId)) { + return null; + } + return mSpellCheckerMap.get(curSpellCheckerId); + } + } + + @Override + public void getSpellCheckerService(SpellCheckerInfo info, String locale, + ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener) { + if (!mSystemReady) { + return; + } + if (info == null || tsListener == null) { + Slog.e(TAG, "getSpellCheckerService: Invalid input."); + return; + } + final String sciId = info.getId(); + synchronized(mSpellCheckerMap) { + if (!mSpellCheckerMap.containsKey(sciId)) { + return; + } + if (mSpellCheckerBindGroups.containsKey(sciId)) { + mSpellCheckerBindGroups.get(sciId).addListener(tsListener, locale, scListener); + return; + } + final InternalServiceConnection connection = new InternalServiceConnection( + sciId, locale, scListener); + final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE); + serviceIntent.setComponent(info.getComponent()); + if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) { + Slog.e(TAG, "Failed to get a spell checker service."); + return; + } + final SpellCheckerBindGroup group = new SpellCheckerBindGroup( + connection, tsListener, locale, scListener); + mSpellCheckerBindGroups.put(sciId, group); + } + return; + } + + @Override + public SpellCheckerInfo[] getEnabledSpellCheckers() { + return mSpellCheckerList.toArray(new SpellCheckerInfo[mSpellCheckerList.size()]); + } + + @Override + public void finishSpellCheckerService(ISpellCheckerSessionListener listener) { + synchronized(mSpellCheckerMap) { + for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) { + if (group == null) continue; + group.removeListener(listener); + } + } + } + + private void setCurrentSpellChecker(SpellCheckerInfo sci) { + if (DBG) { + Slog.w(TAG, "setCurrentSpellChecker: " + sci.getId()); + } + if (sci == null || mSpellCheckerMap.containsKey(sci.getId())) return; + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.SPELL_CHECKER_SERVICE, sci == null ? "" : sci.getId()); + } + + // SpellCheckerBindGroup contains active text service session listeners. + // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from + // mSpellCheckerBindGroups + private class SpellCheckerBindGroup { + final InternalServiceConnection mInternalConnection; + final ArrayList<InternalDeathRecipient> mListeners = + new ArrayList<InternalDeathRecipient>(); + + public SpellCheckerBindGroup(InternalServiceConnection connection, + ITextServicesSessionListener listener, String locale, + ISpellCheckerSessionListener scListener) { + mInternalConnection = connection; + addListener(listener, locale, scListener); + } + + public void onServiceConnected(ISpellCheckerService spellChecker) { + synchronized(mSpellCheckerMap) { + for (InternalDeathRecipient listener : mListeners) { + try { + final ISpellCheckerSession session = spellChecker.getISpellCheckerSession( + listener.mScLocale, listener.mScListener); + listener.mTsListener.onServiceConnected(session); + } catch (RemoteException e) { + } + } + } + } + + public void addListener(ITextServicesSessionListener tsListener, String locale, + ISpellCheckerSessionListener scListener) { + synchronized(mSpellCheckerMap) { + try { + final int size = mListeners.size(); + for (int i = 0; i < size; ++i) { + if (mListeners.get(i).hasSpellCheckerListener(scListener)) { + // do not add the lister if the group already contains this. + return; + } + } + final InternalDeathRecipient recipient = new InternalDeathRecipient( + this, tsListener, locale, scListener); + scListener.asBinder().linkToDeath(recipient, 0); + mListeners.add(new InternalDeathRecipient( + this, tsListener, locale, scListener)); + } catch(RemoteException e) { + // do nothing + } + cleanLocked(); + } + } + + public void removeListener(ISpellCheckerSessionListener listener) { + synchronized(mSpellCheckerMap) { + final int size = mListeners.size(); + final ArrayList<InternalDeathRecipient> removeList = + new ArrayList<InternalDeathRecipient>(); + for (int i = 0; i < size; ++i) { + final InternalDeathRecipient tempRecipient = mListeners.get(i); + if(tempRecipient.hasSpellCheckerListener(listener)) { + removeList.add(tempRecipient); + } + } + final int removeSize = removeList.size(); + for (int i = 0; i < removeSize; ++i) { + mListeners.remove(removeList.get(i)); + } + cleanLocked(); + } + } + + private void cleanLocked() { + if (mListeners.isEmpty()) { + mSpellCheckerBindGroups.remove(this); + // Unbind service when there is no active clients. + mContext.unbindService(mInternalConnection); + } + } + } + + private class InternalServiceConnection implements ServiceConnection { + private final ISpellCheckerSessionListener mListener; + private final String mSciId; + private final String mLocale; + public InternalServiceConnection( + String id, String locale, ISpellCheckerSessionListener listener) { + mSciId = id; + mLocale = locale; + mListener = listener; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized(mSpellCheckerMap) { + ISpellCheckerService spellChecker = ISpellCheckerService.Stub.asInterface(service); + final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId); + if (group != null) { + group.onServiceConnected(spellChecker); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mSpellCheckerBindGroups.remove(mSciId); + } + } + + private class InternalDeathRecipient implements IBinder.DeathRecipient { + public final ITextServicesSessionListener mTsListener; + public final ISpellCheckerSessionListener mScListener; + public final String mScLocale; + private final SpellCheckerBindGroup mGroup; + public InternalDeathRecipient(SpellCheckerBindGroup group, + ITextServicesSessionListener tsListener, String scLocale, + ISpellCheckerSessionListener scListener) { + mTsListener = tsListener; + mScListener = scListener; + mScLocale = scLocale; + mGroup = group; + } + + public boolean hasSpellCheckerListener(ISpellCheckerSessionListener listener) { + return mScListener.equals(listener); + } + + @Override + public void binderDied() { + mGroup.removeListener(mScListener); + } + } +} diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index b8890aa2aa8e..cd649ce4c554 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -194,6 +194,7 @@ public class ThrottleService extends IThrottleManager.Stub { } public void interfaceRemoved(String iface) {} + public void limitReached(String limitName, String iface) {} } diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 711255327499..f9f63b1d1bd6 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -1461,7 +1461,7 @@ public class WifiService extends IWifiManager.Stub { if (mMulticasters.size() != 0) { return; } else { - mWifiStateMachine.startPacketFiltering(); + mWifiStateMachine.startFilteringMulticastV4Packets(); } } } @@ -1472,11 +1472,11 @@ public class WifiService extends IWifiManager.Stub { synchronized (mMulticasters) { mMulticastEnabled++; mMulticasters.add(new Multicaster(tag, binder)); - // Note that we could call stopPacketFiltering only when + // Note that we could call stopFilteringMulticastV4Packets only when // our new size == 1 (first call), but this function won't // be called often and by making the stopPacket call each // time we're less fragile and self-healing. - mWifiStateMachine.stopPacketFiltering(); + mWifiStateMachine.stopFilteringMulticastV4Packets(); } int uid = Binder.getCallingUid(); @@ -1513,7 +1513,7 @@ public class WifiService extends IWifiManager.Stub { removed.unlinkDeathRecipient(); } if (mMulticasters.size() == 0) { - mWifiStateMachine.startPacketFiltering(); + mWifiStateMachine.startFilteringMulticastV4Packets(); } Long ident = Binder.clearCallingIdentity(); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 0924b862c251..3389f339a7f2 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -3628,6 +3628,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.setSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; app.forcingToForeground = null; app.foregroundServices = false; + app.hasShownUi = false; app.debugging = false; mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); @@ -9218,6 +9219,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.forcingToForeground = null; app.foregroundServices = false; app.foregroundActivities = false; + app.hasShownUi = false; killServicesLocked(app, true); @@ -9331,8 +9333,6 @@ public final class ActivityManagerService extends ActivityManagerNative // This app is persistent, so we need to keep its record around. // If it is not already on the pending app list, add it there // and start a new process for it. - app.forcingToForeground = null; - app.foregroundServices = false; if (mPersistentStartingProcesses.indexOf(app) < 0) { mPersistentStartingProcesses.add(app); restart = true; @@ -12728,21 +12728,31 @@ public final class ActivityManagerService extends ActivityManagerNative while (jt.hasNext() && adj > FOREGROUND_APP_ADJ) { ServiceRecord s = jt.next(); if (s.startRequested) { - if (now < (s.lastActivity+MAX_SERVICE_INACTIVITY)) { - // This service has seen some activity within - // recent memory, so we will keep its process ahead - // of the background processes. + if (app.hasShownUi) { + // If this process has shown some UI, let it immediately + // go to the LRU list because it may be pretty heavy with + // UI stuff. We'll tag it with a label just to help + // debug and understand what is going on. if (adj > SECONDARY_SERVER_ADJ) { - adj = SECONDARY_SERVER_ADJ; - app.adjType = "started-services"; - app.hidden = false; + app.adjType = "started-bg-ui-services"; + } + } else { + if (now < (s.lastActivity+MAX_SERVICE_INACTIVITY)) { + // This service has seen some activity within + // recent memory, so we will keep its process ahead + // of the background processes. + if (adj > SECONDARY_SERVER_ADJ) { + adj = SECONDARY_SERVER_ADJ; + app.adjType = "started-services"; + app.hidden = false; + } + } + // If we have let the service slide into the background + // state, still have some text describing what it is doing + // even though the service no longer has an impact. + if (adj > SECONDARY_SERVER_ADJ) { + app.adjType = "started-bg-services"; } - } - // If we have let the service slide into the background - // state, still have some text describing what it is doing - // even though the service no longer has an impact. - if (adj > SECONDARY_SERVER_ADJ) { - app.adjType = "started-bg-services"; } // Don't kill this process because it is doing work; it // has said it is doing work. @@ -13351,15 +13361,15 @@ public final class ActivityManagerService extends ActivityManagerNative break; } } - } else if (app.curAdj >= PERCEPTIBLE_APP_ADJ) { - if (app.trimMemoryLevel < ComponentCallbacks.TRIM_MEMORY_INVISIBLE + } else if (app.curAdj == HEAVY_WEIGHT_APP_ADJ) { + if (app.trimMemoryLevel < ComponentCallbacks.TRIM_MEMORY_BACKGROUND && app.thread != null) { try { - app.thread.scheduleTrimMemory(ComponentCallbacks.TRIM_MEMORY_INVISIBLE); + app.thread.scheduleTrimMemory(ComponentCallbacks.TRIM_MEMORY_BACKGROUND); } catch (RemoteException e) { } } - app.trimMemoryLevel = ComponentCallbacks.TRIM_MEMORY_INVISIBLE; + app.trimMemoryLevel = ComponentCallbacks.TRIM_MEMORY_BACKGROUND; } else { app.trimMemoryLevel = 0; } @@ -13442,7 +13452,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public boolean profileControl(String process, boolean start, - String path, ParcelFileDescriptor fd) throws RemoteException { + String path, ParcelFileDescriptor fd, int profileType) throws RemoteException { try { synchronized (this) { @@ -13487,7 +13497,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - proc.thread.profilerControl(start, path, fd); + proc.thread.profilerControl(start, path, fd, profileType); fd = null; return true; } diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 0d89081b0813..cc58eafe8872 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -559,6 +559,7 @@ final class ActivityStack { r.forceNewConfig = false; showAskCompatModeDialogLocked(r); r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo); + app.hasShownUi = true; app.thread.scheduleLaunchActivity(new Intent(r.intent), r, System.identityHashCode(r), r.info, r.compat, r.icicle, results, newIntents, !andResume, diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index 9e597aa93d18..5b593638f31a 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -66,6 +66,7 @@ class ProcessRecord { boolean setIsForeground; // Running foreground UI when last set? boolean foregroundServices; // Running any services that are foreground? boolean foregroundActivities; // Running any activities that are foreground? + boolean hasShownUi; // Has UI been shown in this process since it was started? boolean bad; // True if disabled in the bad process list boolean killedBackground; // True when proc has been killed due to too many bg String waitingToKill; // Process is waiting to be killed when in the bg; reason @@ -185,6 +186,7 @@ class ProcessRecord { pw.print(prefix); pw.print("curSchedGroup="); pw.print(curSchedGroup); pw.print(" setSchedGroup="); pw.print(setSchedGroup); pw.print(" trimMemoryLevel="); pw.println(trimMemoryLevel); + pw.print(" hasShownUi="); pw.println(hasShownUi); pw.print(prefix); pw.print("setIsForeground="); pw.print(setIsForeground); pw.print(" foregroundServices="); pw.print(foregroundServices); pw.print(" forcingToForeground="); pw.println(forcingToForeground); diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index d7d4b0346795..a5a6d8e50b39 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -36,7 +36,6 @@ import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkUtils; import android.os.Binder; -import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -76,10 +75,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private final static String TAG = "Tethering"; private final static boolean DEBUG = true; - private boolean mBooted = false; - //used to remember if we got connected before boot finished - private boolean mDeferedUsbConnection = false; - // TODO - remove both of these - should be part of interface inspection/selection stuff private String[] mTetherableUsbRegexs; private String[] mTetherableWifiRegexs; @@ -126,10 +121,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private Notification mTetheredNotification; - // whether we can tether is the && of these two - they come in as separate - // broadcasts so track them so we can decide what to do when either changes - private boolean mUsbMassStorageOff; // track the status of USB Mass Storage - private boolean mUsbConnected; // track the status of USB connection + private boolean mRndisEnabled; // track the RNDIS function enabled state + private boolean mUsbTetherRequested; // true if USB tethering should be started + // when RNDIS is enabled public Tethering(Context context, INetworkManagementService nmService, Looper looper) { mContext = context; @@ -149,7 +143,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - filter.addAction(Intent.ACTION_BOOT_COMPLETED); mContext.registerReceiver(mStateReceiver, filter); filter = new IntentFilter(); @@ -158,9 +151,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { filter.addDataScheme("file"); mContext.registerReceiver(mStateReceiver, filter); - mUsbMassStorageOff = !Environment.MEDIA_SHARED.equals( - Environment.getExternalStorageState()); - mDhcpRange = context.getResources().getStringArray( com.android.internal.R.array.config_tether_dhcp_range); if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) { @@ -221,6 +211,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } public void interfaceLinkStateChanged(String iface, boolean up) { + if (DEBUG) Log.d(TAG, "interfaceLinkStateChanged " + iface + ", " + up); + interfaceStatusChanged(iface, up); } private boolean isUsb(String iface) { @@ -243,6 +235,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } return false; } + public void interfaceAdded(String iface) { boolean found = false; boolean usb = false; @@ -288,6 +281,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } + public void limitReached(String limitName, String iface) {} + public int tether(String iface) { Log.d(TAG, "Tethering " + iface); TetherInterfaceSM sm = null; @@ -454,47 +449,28 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - private void updateUsbStatus() { - boolean enable = mUsbConnected && mUsbMassStorageOff; - - if (mBooted) { - enableUsbIfaces(enable); - } - } - private class StateReceiver extends BroadcastReceiver { public void onReceive(Context content, Intent intent) { String action = intent.getAction(); if (action.equals(UsbManager.ACTION_USB_STATE)) { - mUsbConnected = intent.getExtras().getBoolean(UsbManager.USB_CONNECTED); - updateUsbStatus(); - } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { - mUsbMassStorageOff = false; - updateUsbStatus(); - } - else if (action.equals(Intent.ACTION_MEDIA_UNSHARED)) { - mUsbMassStorageOff = true; - updateUsbStatus(); + synchronized (Tethering.this) { + boolean usbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); + mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false); + // start tethering if we have a request pending + if (usbConnected && mRndisEnabled && mUsbTetherRequested) { + tetherUsb(true); + } + mUsbTetherRequested = false; + } } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { if (DEBUG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION"); mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); - } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - mBooted = true; - updateUsbStatus(); } } } - // used on cable insert/remove - private void enableUsbIfaces(boolean enable) { - // add/remove USB interfaces when USB is connected/disconnected - for (String intf : mTetherableUsbRegexs) { - if (enable) { - interfaceAdded(intf); - } else { - interfaceRemoved(intf); - } - } + private void tetherUsb(boolean enable) { + if (DEBUG) Log.d(TAG, "tetherUsb " + enable); String[] ifaces = new String[0]; try { @@ -505,83 +481,50 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } for (String iface : ifaces) { if (isUsb(iface)) { - if (enable) { - interfaceAdded(iface); - } else { - interfaceRemoved(iface); + int result = (enable ? tether(iface) : untether(iface)); + if (result == ConnectivityManager.TETHER_ERROR_NO_ERROR) { + return; } } } - } - - // toggled when we enter/leave the fully tethered state - private boolean enableUsbRndis(boolean enabled) { - if (DEBUG) Log.d(TAG, "enableUsbRndis(" + enabled + ")"); - - UsbManager usbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); - if (usbManager == null) { - Log.d(TAG, "could not get UsbManager"); - return false; - } - try { - if (enabled) { - usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false); - } else { - usbManager.setCurrentFunction(null, false); - } - } catch (Exception e) { - Log.e(TAG, "Error toggling usb RNDIS", e); - return false; - } - return true; + Log.e(TAG, "unable start or stop USB tethering"); } // configured when we start tethering and unconfig'd on error or conclusion private boolean configureUsbIface(boolean enabled) { if (DEBUG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); - if (enabled) { - // must enable RNDIS first to create the interface - enableUsbRndis(enabled); - } - + // toggle the USB interfaces + String[] ifaces = new String[0]; try { - // bring toggle the interfaces - String[] ifaces = new String[0]; - try { - ifaces = mNMService.listInterfaces(); - } catch (Exception e) { - Log.e(TAG, "Error listing Interfaces", e); - return false; - } - for (String iface : ifaces) { - if (isUsb(iface)) { - InterfaceConfiguration ifcg = null; - try { - ifcg = mNMService.getInterfaceConfig(iface); - if (ifcg != null) { - InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); - ifcg.addr = new LinkAddress(addr, USB_PREFIX_LENGTH); - if (enabled) { - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); - } else { - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down"); - } - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", ""); - ifcg.interfaceFlags = ifcg.interfaceFlags.replace(" "," "); - mNMService.setInterfaceConfig(iface, ifcg); + ifaces = mNMService.listInterfaces(); + } catch (Exception e) { + Log.e(TAG, "Error listing Interfaces", e); + return false; + } + for (String iface : ifaces) { + if (isUsb(iface)) { + InterfaceConfiguration ifcg = null; + try { + ifcg = mNMService.getInterfaceConfig(iface); + if (ifcg != null) { + InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); + ifcg.addr = new LinkAddress(addr, USB_PREFIX_LENGTH); + if (enabled) { + ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); + } else { + ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down"); } - } catch (Exception e) { - Log.e(TAG, "Error configuring interface " + iface, e); - return false; + ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", ""); + ifcg.interfaceFlags = ifcg.interfaceFlags.replace(" "," "); + mNMService.setInterfaceConfig(iface, ifcg); } + } catch (Exception e) { + Log.e(TAG, "Error configuring interface " + iface, e); + return false; } - } - } finally { - if (!enabled) { - enableUsbRndis(false); } - } + } return true; } @@ -598,6 +541,28 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return mTetherableBluetoothRegexs; } + public int setUsbTethering(boolean enable) { + UsbManager usbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); + + synchronized (this) { + if (enable) { + if (mRndisEnabled) { + tetherUsb(true); + } else { + mUsbTetherRequested = true; + usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false); + } + } else { + tetherUsb(false); + if (mRndisEnabled) { + usbManager.setCurrentFunction(null, false); + } + mUsbTetherRequested = false; + } + } + return ConnectivityManager.TETHER_ERROR_NO_ERROR; + } + public int[] getUpstreamIfaceTypes() { int values[] = new int[mUpstreamIfaceTypes.size()]; Iterator<Integer> iterator = mUpstreamIfaceTypes.iterator(); diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index 05e95a7d3d89..9cb772eb315a 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -248,6 +248,8 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } } + public void limitReached(String limitName, String iface) {} + private void showNotification(VpnConfig config, String label, Bitmap icon) { NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -394,7 +396,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { if (mTimer == -1) { mTimer = now; Thread.sleep(1); - } else if (now - mTimer <= 30000) { + } else if (now - mTimer <= 60000) { Thread.sleep(yield ? 200 : 1); } else { mInfo.state = LegacyVpnInfo.STATE_TIMEOUT; diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index c80cd0a87115..f183f83cfba0 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -19,6 +19,7 @@ package com.android.server.usb; import android.app.PendingIntent; import android.app.Notification; import android.app.NotificationManager; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -80,6 +81,7 @@ public class UsbDeviceManager { private static final int MSG_ENABLE_ADB = 1; private static final int MSG_SET_CURRENT_FUNCTION = 2; private static final int MSG_SYSTEM_READY = 3; + private static final int MSG_BOOT_COMPLETED = 4; // Delay for debouncing USB disconnects. // We often get rapid connect/disconnect events when enabling USB functions, @@ -87,7 +89,7 @@ public class UsbDeviceManager { private static final int UPDATE_DELAY = 1000; private UsbHandler mHandler; - private boolean mSystemReady; + private boolean mBootCompleted; private final Context mContext; private final ContentResolver mContentResolver; @@ -141,10 +143,15 @@ public class UsbDeviceManager { Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mHandler = new UsbHandler(thread.getLooper()); + + if (nativeIsStartRequested()) { + if (DEBUG) Slog.d(TAG, "accessory attached at boot"); + setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY, false); + } } public void systemReady() { - mSystemReady = true; + if (DEBUG) Slog.d(TAG, "systemReady"); mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -236,15 +243,22 @@ public class UsbDeviceManager { private String mCurrentFunctions; private String mDefaultFunctions; private UsbAccessory mCurrentAccessory; - private boolean mDeferAccessoryAttached; private int mUsbNotificationId; private boolean mAdbNotificationShown; + private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (DEBUG) Slog.d(TAG, "boot completed"); + mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED); + } + }; + private static final int NOTIFICATION_NONE = 0; private static final int NOTIFICATION_MTP = 1; private static final int NOTIFICATION_PTP = 2; private static final int NOTIFICATION_INSTALLER = 3; - private static final int NOTIFICATION_ADB = 4; + private static final int NOTIFICATION_ACCESSORY = 4; + private static final int NOTIFICATION_ADB = 5; public UsbHandler(Looper looper) { super(looper); @@ -285,6 +299,9 @@ public class UsbDeviceManager { // Watch for USB configuration changes mUEventObserver.startObserving(USB_STATE_MATCH); mUEventObserver.startObserving(ACCESSORY_START_MATCH); + + mContext.registerReceiver(mBootCompletedReceiver, + new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); } catch (Exception e) { Slog.e(TAG, "Error initializing UsbHandler", e); } @@ -406,11 +423,9 @@ public class UsbDeviceManager { mCurrentAccessory = new UsbAccessory(strings); Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); // defer accessoryAttached if system is not ready - if (mSystemReady) { + if (mBootCompleted) { mSettingsManager.accessoryAttached(mCurrentAccessory); - } else { - mDeferAccessoryAttached = true; - } + } // else handle in mBootCompletedReceiver } else { Slog.e(TAG, "nativeGetAccessoryStrings failed"); } @@ -421,7 +436,7 @@ public class UsbDeviceManager { setEnabledFunctions(mDefaultFunctions); if (mCurrentAccessory != null) { - if (mSystemReady) { + if (mBootCompleted) { mSettingsManager.accessoryDetached(mCurrentAccessory); } mCurrentAccessory = null; @@ -463,7 +478,7 @@ public class UsbDeviceManager { // restore defaults when USB is disconnected doSetCurrentFunctions(mDefaultFunctions); } - if (mSystemReady) { + if (mBootCompleted) { updateUsbState(); } break; @@ -497,7 +512,10 @@ public class UsbDeviceManager { updateUsbNotification(); updateAdbNotification(); updateUsbState(); - if (mCurrentAccessory != null && mDeferAccessoryAttached) { + break; + case MSG_BOOT_COMPLETED: + mBootCompleted = true; + if (mCurrentAccessory != null) { mSettingsManager.accessoryAttached(mCurrentAccessory); } break; @@ -527,6 +545,10 @@ public class UsbDeviceManager { title = r.getText( com.android.internal.R.string.usb_cd_installer_notification_title); id = NOTIFICATION_INSTALLER; + } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ACCESSORY)) { + title = r.getText( + com.android.internal.R.string.usb_accessory_notification_title); + id = NOTIFICATION_ACCESSORY; } else { Slog.e(TAG, "No known USB function in updateUsbNotification"); } @@ -671,4 +693,5 @@ public class UsbDeviceManager { private native String[] nativeGetAccessoryStrings(); private native ParcelFileDescriptor nativeOpenAccessory(); + private native boolean nativeIsStartRequested(); } diff --git a/services/java/com/android/server/wm/BlackFrame.java b/services/java/com/android/server/wm/BlackFrame.java index d8fd7fe84a73..36f5dcb97056 100644 --- a/services/java/com/android/server/wm/BlackFrame.java +++ b/services/java/com/android/server/wm/BlackFrame.java @@ -32,10 +32,12 @@ public class BlackFrame { final int top; final Surface surface; - BlackSurface(SurfaceSession session, int layer, int l, int t, int w, int h) + BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b) throws Surface.OutOfResourcesException { left = l; top = t; + int w = r-l; + int h = b-t; surface = new Surface(session, 0, "BlackSurface", -1, w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM); if (WindowManagerService.SHOW_TRANSACTIONS || diff --git a/services/jni/com_android_server_UsbDeviceManager.cpp b/services/jni/com_android_server_UsbDeviceManager.cpp index 69541714260e..40f0dbd81430 100644 --- a/services/jni/com_android_server_UsbDeviceManager.cpp +++ b/services/jni/com_android_server_UsbDeviceManager.cpp @@ -99,11 +99,26 @@ static jobject android_server_UsbDeviceManager_openAccessory(JNIEnv *env, jobjec gParcelFileDescriptorOffsets.mConstructor, fileDescriptor); } +static jboolean android_server_UsbDeviceManager_isStartRequested(JNIEnv *env, jobject thiz) +{ + int fd = open(DRIVER_NAME, O_RDWR); + if (fd < 0) { + LOGE("could not open %s", DRIVER_NAME); + return false; + } + int result = ioctl(fd, ACCESSORY_IS_START_REQUESTED); + close(fd); + return (result == 1); +} + + static JNINativeMethod method_table[] = { { "nativeGetAccessoryStrings", "()[Ljava/lang/String;", (void*)android_server_UsbDeviceManager_getAccessoryStrings }, { "nativeOpenAccessory", "()Landroid/os/ParcelFileDescriptor;", (void*)android_server_UsbDeviceManager_openAccessory }, + { "nativeIsStartRequested", "()Z", + (void*)android_server_UsbDeviceManager_isStartRequested }, }; int register_android_server_UsbDeviceManager(JNIEnv *env) diff --git a/services/surfaceflinger/SurfaceTextureLayer.cpp b/services/surfaceflinger/SurfaceTextureLayer.cpp index 11f61ccb1ada..91e010f90fe8 100644 --- a/services/surfaceflinger/SurfaceTextureLayer.cpp +++ b/services/surfaceflinger/SurfaceTextureLayer.cpp @@ -60,7 +60,11 @@ status_t SurfaceTextureLayer::queueBuffer(int buf, int64_t timestamp, sp<Layer> layer(mLayer.promote()); if (layer != NULL) { - *outTransform = layer->getOrientation(); + uint32_t orientation = layer->getOrientation(); + if (orientation & Transform::ROT_INVALID) { + orientation = 0; + } + *outTransform = orientation; } return res; diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java index 4f8b5256ee82..3bd78e043255 100644 --- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java @@ -36,6 +36,7 @@ import android.os.Messenger; import android.os.SystemProperties; import android.preference.PreferenceManager; import android.provider.Settings; +import android.telephony.ServiceState; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.util.Log; @@ -1031,6 +1032,27 @@ public abstract class DataConnectionTracker extends Handler { protected void onSetDependencyMet(String apnType, boolean met) { } + protected String getReryConfig(boolean forDefault) { + int rt = mPhone.getServiceState().getRadioTechnology(); + + if ((rt == ServiceState.RADIO_TECHNOLOGY_IS95A) || + (rt == ServiceState.RADIO_TECHNOLOGY_IS95B) || + (rt == ServiceState.RADIO_TECHNOLOGY_1xRTT) || + (rt == ServiceState.RADIO_TECHNOLOGY_EVDO_0) || + (rt == ServiceState.RADIO_TECHNOLOGY_EVDO_A) || + (rt == ServiceState.RADIO_TECHNOLOGY_EVDO_B) || + (rt == ServiceState.RADIO_TECHNOLOGY_EHRPD)) { + // CDMA variant + return SystemProperties.get("ro.cdma.data_retry_config"); + } else { + // Use GSM varient for all others. + if (forDefault) { + return SystemProperties.get("ro.gsm.data_retry_config"); + } else { + return SystemProperties.get("ro.gsm.2nd_data_retry_config"); + } + } + } protected void resetAllRetryCounts() { for (DataConnection dc : mDataConnections.values()) { diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java index 40a70a806ce1..a0961caaa40d 100644 --- a/telephony/java/com/android/internal/telephony/PhoneBase.java +++ b/telephony/java/com/android/internal/telephony/PhoneBase.java @@ -806,10 +806,10 @@ public abstract class PhoneBase extends Handler implements Phone { mNotifier.notifyDataConnection(this, reason, apnType, getDataConnectionState(apnType)); } - public void notifyDataConnection() { + public void notifyDataConnection(String reason) { String types[] = getActiveApnTypes(); for (String apnType : types) { - mNotifier.notifyDataConnection(this, null, apnType, getDataConnectionState(apnType)); + mNotifier.notifyDataConnection(this, reason, apnType, getDataConnectionState(apnType)); } } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 93fc9cebd81e..facee5f67bdc 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -31,6 +31,8 @@ public interface RILConstants { // From the top of ril.cpp int RIL_ERRNO_INVALID_RESPONSE = -1; + int MAX_INT = 0x7FFFFFFF; + // from RIL_Errno int SUCCESS = 0; int RADIO_NOT_AVAILABLE = 1; /* If radio did not start or is resetting */ diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java index 0d551aade502..d3bb6a546d94 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java @@ -134,8 +134,6 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { } } - // Not sure if this is needed in CDMALTE phone. - // mDataRoaming = regCodeIsRoaming(regState); mLteSS.setRadioTechnology(type); mLteSS.setState(regCodeToServiceState(regState)); } else { @@ -249,10 +247,11 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { boolean hasLocationChanged = !newCellLoc.equals(cellLoc); boolean has4gHandoff = - ((networkType == ServiceState.RADIO_TECHNOLOGY_LTE) && - (newNetworkType == ServiceState.RADIO_TECHNOLOGY_EHRPD)) || - ((networkType == ServiceState.RADIO_TECHNOLOGY_EHRPD) && - (newNetworkType == ServiceState.RADIO_TECHNOLOGY_LTE)); + mNewDataConnectionState == ServiceState.STATE_IN_SERVICE && + (((networkType == ServiceState.RADIO_TECHNOLOGY_LTE) && + (newNetworkType == ServiceState.RADIO_TECHNOLOGY_EHRPD)) || + ((networkType == ServiceState.RADIO_TECHNOLOGY_EHRPD) && + (newNetworkType == ServiceState.RADIO_TECHNOLOGY_LTE))); boolean hasMultiApnSupport = (((newNetworkType == ServiceState.RADIO_TECHNOLOGY_LTE) || @@ -345,13 +344,14 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { } if (cm.getSimState().isSIMReady()) { - // SIM is found on the device. If ERI roaming is OFF, use operator name - // from CSIM record. + // SIM is found on the device. If ERI roaming is OFF and SID/NID matches + // one configfured in SIM, use operator name from CSIM record. boolean showSpn = ((CdmaLteUiccRecords)phone.mIccRecords).getCsimSpnDisplayCondition(); int iconIndex = ss.getCdmaEriIconIndex(); - if (showSpn && (iconIndex == EriInfo.ROAMING_INDICATOR_OFF)) { + if (showSpn && (iconIndex == EriInfo.ROAMING_INDICATOR_OFF) && + isInHomeSidNid(ss.getSystemId(), ss.getNetworkId())) { ss.setOperatorAlphaLong(phone.mIccRecords.getServiceProviderName()); } } @@ -392,7 +392,7 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { phone.notifyServiceStateChanged(ss); } - if (hasCdmaDataConnectionAttached) { + if (hasCdmaDataConnectionAttached || has4gHandoff) { mAttachedRegistrants.notifyRegistrants(); } @@ -401,7 +401,7 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { } if ((hasCdmaDataConnectionChanged || hasNetworkTypeChanged)) { - phone.notifyDataConnection(); + phone.notifyDataConnection(null); } if (hasRoamingOn) { @@ -469,6 +469,34 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { } /** + * Check whether the specified SID and NID pair appears in the HOME SID/NID list + * read from NV or SIM. + * + * @return true if provided sid/nid pair belongs to operator's home network. + */ + private boolean isInHomeSidNid(int sid, int nid) { + // if SID/NID is not available, assume this is home network. + if (isSidsAllZeros()) return true; + + // length of SID/NID shold be same + if (mHomeSystemId.length != mHomeNetworkId.length) return true; + + if (sid == 0) return true; + + for (int i = 0; i < mHomeSystemId.length; i++) { + // Use SID only if NID is a reserved value. + // SID 0 and NID 0 and 65535 are reserved. (C.0005 2.6.5.2) + if ((mHomeSystemId[i] == sid) && + ((mHomeNetworkId[i] == 0) || (mHomeNetworkId[i] == 65535) || + (nid == 0) || (nid == 65535) || (mHomeNetworkId[i] == nid))) { + return true; + } + } + // SID/NID are not in the list. So device is not in home network + return false; + } + + /** * Returns OTASP_NOT_NEEDED as its not needed for LTE */ @Override diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java index 24a468a7d2c1..2cf4b8812a37 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java @@ -130,8 +130,8 @@ public class CdmaServiceStateTracker extends ServiceStateTracker { protected String mCurPlmn = null; protected String mMdn; - private int mHomeSystemId[] = null; - private int mHomeNetworkId[] = null; + protected int mHomeSystemId[] = null; + protected int mHomeNetworkId[] = null; protected String mMin; protected String mPrlVersion; protected boolean mIsMinInfoReady = false; @@ -999,7 +999,7 @@ public class CdmaServiceStateTracker extends ServiceStateTracker { } if (hasCdmaDataConnectionChanged || hasNetworkTypeChanged) { - phone.notifyDataConnection(); + phone.notifyDataConnection(null); } if (hasRoamingOn) { @@ -1481,7 +1481,7 @@ public class CdmaServiceStateTracker extends ServiceStateTracker { } } - private boolean isSidsAllZeros() { + protected boolean isSidsAllZeros() { if (mHomeSystemId != null) { for (int i=0; i < mHomeSystemId.length; i++) { if (mHomeSystemId[i] != 0) { diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index ccdb0bf69a5b..be129d56346a 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -609,9 +609,20 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) { if (dcac.getReconnectIntentSync() != null) { cancelReconnectAlarm(dcac); - if (dcac.dataConnection != null) { - dcac.dataConnection.resetRetryCount(); + } + // update retry config for existing calls to match up + // ones for the new RAT. + if (dcac.dataConnection != null) { + Collection<ApnContext> apns = dcac.getApnListSync(); + + boolean hasDefault = false; + for (ApnContext apnContext : apns) { + if (apnContext.getApnType().equals(Phone.APN_TYPE_DEFAULT)) { + hasDefault = true; + break; + } } + configureRetry(dcac.dataConnection, hasDefault); } } @@ -973,7 +984,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { // configure retry count if no other Apn is using the same connection. if (refCount == 0) { - configureRetry(dc, apnContext.getApnType()); + configureRetry(dc, apn.canHandleType(Phone.APN_TYPE_DEFAULT)); } apnContext.setDataConnectionAc(dcac); apnContext.setDataConnection(dc); @@ -1834,7 +1845,11 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { retryOverride = ((DataConnection.CallSetupException)ar.exception).getRetryOverride(); } - startDelayedRetry(cause, apnContext, retryOverride); + if (retryOverride == RILConstants.MAX_INT) { + if (DBG) log("No retry is suggested."); + } else { + startDelayedRetry(cause, apnContext, retryOverride); + } } } else { if (DBG) log("onDataSetupComplete: Try next APN"); @@ -2022,20 +2037,18 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { return conn; } - private void configureRetry(DataConnection dc, String apnType) { - if ((dc == null) || (apnType == null)) return; + private void configureRetry(DataConnection dc, boolean forDefault) { + if (dc == null) return; - if (apnType.equals(Phone.APN_TYPE_DEFAULT)) { - if (!dc.configureRetry(SystemProperties.get("ro.gsm.data_retry_config"))) { + if (!dc.configureRetry(getReryConfig(forDefault))) { + if (forDefault) { if (!dc.configureRetry(DEFAULT_DATA_RETRY_CONFIG)) { // Should never happen, log an error and default to a simple linear sequence. loge("configureRetry: Could not configure using " + "DEFAULT_DATA_RETRY_CONFIG=" + DEFAULT_DATA_RETRY_CONFIG); dc.configureRetry(20, 2000, 1000); } - } - } else { - if (!dc.configureRetry(SystemProperties.get("ro.gsm.2nd_data_retry_config"))) { + } else { if (!dc.configureRetry(SECONDARY_DATA_RETRY_CONFIG)) { // Should never happen, log an error and default to a simple sequence. loge("configureRetry: Could note configure using " + diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java index 93f4b4ea579a..d3645faba86f 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java @@ -915,7 +915,7 @@ final class GsmServiceStateTracker extends ServiceStateTracker { } if (hasRadioTechnologyChanged) { - phone.notifyDataConnection(Phone.REASON_NW_TYPE_CHANGED, Phone.APN_TYPE_ALL); + phone.notifyDataConnection(Phone.REASON_NW_TYPE_CHANGED); } if (hasRoamingOn) { diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java index 1493ab9ffcd8..13b6129ab1ac 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java @@ -93,12 +93,9 @@ public class ListActivity extends Activity { } public void startProfiling(View v) { - ViewDebug.startLooperProfiling(new File(Environment.getExternalStorageDirectory(), - "looper.trace")); } public void stopProfiling(View v) { - ViewDebug.stopLooperProfiling(); } @Override diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java index e77646397f9e..9aa70b0e0b60 100644 --- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java +++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java @@ -87,8 +87,6 @@ public class ImageProcessingActivity extends Activity mIsProcessing = false; } - // This is a hack to work around an invalidation bug - mBitmapOut.setPixel(0, 0, 0); mOutPixelsAllocation.copyTo(mBitmapOut); mDisplayView.invalidate(); } diff --git a/tests/TileBenchmark/AndroidManifest.xml b/tests/TileBenchmark/AndroidManifest.xml index 663cc0dbea6f..ab61a9e25bb7 100644 --- a/tests/TileBenchmark/AndroidManifest.xml +++ b/tests/TileBenchmark/AndroidManifest.xml @@ -7,14 +7,16 @@ android:label="@string/app_name" android:hardwareAccelerated="true"> <activity android:name=".ProfileActivity" - android:label="@string/profile_activity"> + android:label="@string/profile_activity" + android:theme="@android:style/Theme.Holo.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".PlaybackActivity" - android:label="@string/playback_activity"> + android:label="@string/playback_activity" + android:theme="@android:style/Theme.Holo.NoActionBar"> </activity> </application> </manifest> diff --git a/tests/TileBenchmark/res/layout/main.xml b/tests/TileBenchmark/res/layout/main.xml index 4a81da65edb9..577c466e3847 100644 --- a/tests/TileBenchmark/res/layout/main.xml +++ b/tests/TileBenchmark/res/layout/main.xml @@ -23,11 +23,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" > - <Button - android:id="@+id/inspect" + <Spinner + android:id="@+id/movement" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/inspect_log" + android:prompt="@string/movement_method" /> <Spinner android:id="@+id/velocity" @@ -36,6 +36,13 @@ android:gravity="center_horizontal" android:prompt="@string/desired_scroll_velocity" /> + <ToggleButton + android:id="@+id/capture" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textOn="@string/capture_stop" + android:textOff="@string/capture_start" + /> <EditText android:id="@+id/url" android:layout_width="0dip" @@ -44,6 +51,12 @@ android:imeOptions="actionGo" android:layout_weight="1" /> + <Button + android:id="@+id/inspect" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/inspect_log" + /> </LinearLayout> <com.test.tilebenchmark.ProfiledWebView android:id="@+id/web" diff --git a/tests/TileBenchmark/res/values/colors.xml b/tests/TileBenchmark/res/values/colors.xml index 3958083febd5..dbb8e7295b8b 100644 --- a/tests/TileBenchmark/res/values/colors.xml +++ b/tests/TileBenchmark/res/values/colors.xml @@ -18,8 +18,17 @@ <color name="ready_tile">#ff4ac230</color> <!-- The color of tiles with stale / invalid textures --> <color name="unready_tile">#ff744400</color> - <!-- Background color for logged URLs --> - <color name="finished_url">#ff004000</color> - <!-- Background color for URLs with logging in progress --> - <color name="unfinished_url">#ff400000</color> + <!-- Viewport overlay in playback --> + <color name="view">#50000050</color> + <!-- Invalidated region overlay in playback - start color --> + <color name="inval_region_start">#80ff0000</color> + <!-- Invalidated region overlay in playback - stop color--> + <color name="inval_region_stop">#80ffffff</color> + + <!-- Background color for not testing --> + <color name="background_not_testing">#ff000000</color> + <!-- Background color for during testing --> + <color name="background_start_testing">#ff400000</color> + <!-- Background color for testing complete --> + <color name="background_stop_testing">#ff004000</color> </resources> diff --git a/tests/TileBenchmark/res/values/strings.xml b/tests/TileBenchmark/res/values/strings.xml index f70ee2c04a3f..66972ac26bab 100644 --- a/tests/TileBenchmark/res/values/strings.xml +++ b/tests/TileBenchmark/res/values/strings.xml @@ -28,6 +28,10 @@ <string name="loadbutton">Load</string> <!-- Button, opens the playback activity [CHAR LIMIT=20] --> <string name="inspect_log">Inspect Log</string> + <!-- ToggleButton label when pressing starts capture [CHAR LIMIT=15] --> + <string name="capture_start">Start Capture</string> + <!-- ToggleButton label when pressing stops capture [CHAR LIMIT=15] --> + <string name="capture_stop">Stop Capture</string> <!-- The speed of auto-scrolling [CHAR LIMIT=30] --> <string name="desired_scroll_velocity">Choose Scroll Velocity</string> <!-- Pixels moved per frame [CHAR LIMIT=10] --> @@ -39,6 +43,21 @@ <item>200</item> <item>400</item> </string-array> + <!-- Drop down menu for selecting scrolling vs manual navigation for + capturing [CHAR LIMIT=15] --> + <string name="movement_method">Movement Method</string> + <!-- Drop down menu entry - automatically scroll to the end of the page + with scrollBy() [CHAR LIMIT=15] --> + <string name="movement_auto_scroll">Auto-scroll</string> + <!-- Drop down menu entry - [CHAR LIMIT=15] --> + <string name="movement_auto_fling">Auto-fling</string> + <!-- Drop down menu entry - manually navigate the page(s), hit 'capture' + button [CHAR LIMIT=15] --> + <string name="movement_manual">Manual</string> + + <!-- Error popup indicating log data couldn't be loaded [CHAR LIMIT=60] --> + <string name="error_no_data">Error: log data could not be loaded.</string> + <!-- 25th percentile - 25% of frames fall below this value [CHAR LIMIT=12] --> <string name="percentile_25">25%ile</string> @@ -56,7 +75,7 @@ <string name="format_stat">%4.4f</string> <!-- Format string for displaying aggregate stats+values (nr of valid tiles, etc.) [CHAR LIMIT=20] --> - <string name="format_stat_name">%1$9s %2$3d</string> + <string name="format_stat_name">%1$-20s %2$3d</string> <!-- Text hovering over canvas, number of tiles ready [CHAR LIMIT=15] --> <string name="ready_tiles">Ready Tiles</string> <!-- Text hovering over canvas, number tiles not ready [CHAR LIMIT=15] --> @@ -64,4 +83,7 @@ <!-- Text hovering over canvas, number of tiles that haven't been allocated to a place on the page [CHAR LIMIT=15] --> <string name="unplaced_tiles">Unplaced Tiles</string> + <!-- Text hovering over canvas, number of invalidated regions this frame + [CHAR LIMIT=15] --> + <string name="number_invalidates">Invalidates</string> </resources> diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java index 5130f5d3ab3f..36694a7476f4 100644 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackActivity.java @@ -27,6 +27,7 @@ import android.widget.Button; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; +import android.widget.Toast; import java.io.FileInputStream; import java.io.IOException; @@ -102,7 +103,10 @@ public class PlaybackActivity extends Activity { @Override protected void onPostExecute(TileData data[][]) { if (data == null) { - data = genTestPattern(); + Toast.makeText(getApplicationContext(), + getResources().getString(R.string.error_no_data), + Toast.LENGTH_LONG).show(); + return; } mPlaybackView.setData(data); @@ -166,23 +170,4 @@ public class PlaybackActivity extends Activity { new LoadFileTask().execute(ProfileActivity.TEMP_FILENAME); } - - private TileData[][] genTestPattern() { - final int XMAX = 5; - final int FRAMEMAX = 99; - - TileData example[][] = new TileData[FRAMEMAX][]; - for (int frame = 0; frame < FRAMEMAX; frame++) { - int numTiles = frame + 10; - - example[frame] = new TileData[numTiles]; - for (int t = 0; t < numTiles; t++) { - int x = t % XMAX; - int y = t / XMAX; - boolean isReady = y * 10 < frame; - example[frame][t] = new TileData(x, y, isReady, 0); - } - } - return example; - } } diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java index db4a34182bac..35b1563ed895 100644 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java @@ -28,19 +28,17 @@ import java.util.ArrayList; import java.util.Arrays; public class PlaybackGraphs { - private static final int BAR_WIDTH = PlaybackView.TILEX * 3; + private static final int BAR_WIDTH = PlaybackView.TILE_SCALE * 3; private static final float CANVAS_SCALE = 0.2f; private static final double IDEAL_FRAMES = 60; private static final int LABELOFFSET = 100; private static Paint whiteLabels; - private static double viewportCoverage(int l, int b, int r, int t, - int tileIndexX, - int tileIndexY) { - if (tileIndexX * PlaybackView.TILEX < r - && (tileIndexX + 1) * PlaybackView.TILEX >= l - && tileIndexY * PlaybackView.TILEY < t - && (tileIndexY + 1) * PlaybackView.TILEY >= b) { + private static double viewportCoverage(TileData view, TileData tile) { + if (tile.left < view.right + && tile.right >= view.left + && tile.top < view.bottom + && tile.bottom >= view.top) { return 1.0f; } return 0.0f; @@ -76,13 +74,10 @@ public class PlaybackGraphs { // coverage graph @Override public double getValue(TileData[] frame) { - int l = frame[0].x, b = frame[0].y; - int r = frame[1].x, t = frame[1].y; double total = 0, totalCount = 0; - for (int tileID = 2; tileID < frame.length; tileID++) { + for (int tileID = 1; tileID < frame.length; tileID++) { TileData data = frame[tileID]; - double coverage = viewportCoverage(l, b, r, t, data.x, - data.y); + double coverage = viewportCoverage(frame[0], data); total += coverage * (data.isReady ? 1 : 0); totalCount += coverage; } @@ -158,7 +153,7 @@ public class PlaybackGraphs { public PlaybackGraphs() { whiteLabels = new Paint(); whiteLabels.setColor(Color.WHITE); - whiteLabels.setTextSize(PlaybackView.TILEY / 3); + whiteLabels.setTextSize(PlaybackView.TILE_SCALE / 3); } private ArrayList<ShapeDrawable> mShapes = new ArrayList<ShapeDrawable>(); @@ -177,11 +172,13 @@ public class PlaybackGraphs { int lastBar = 0; for (int frameIndex = 0; frameIndex < tileProfilingData.length; frameIndex++) { TileData frame[] = tileProfilingData[frameIndex]; - int newBar = (frame[0].y + frame[1].y) / 2; + int newBar = (frame[0].top + frame[0].bottom) / 2; MetricGen s = Metrics[metricIndex]; double absoluteValue = s.getValue(frame); double relativeValue = absoluteValue / s.getMax(); + relativeValue = Math.min(1,relativeValue); + relativeValue = Math.max(0,relativeValue); int rightPos = (int) (-BAR_WIDTH * metricIndex); int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue)); @@ -207,7 +204,7 @@ public class PlaybackGraphs { ArrayList<ShapeDrawable> shapes) { // Shapes drawn here are drawn relative to the viewRect Rect viewRect = shapes.get(shapes.size() - 1).getBounds(); - canvas.translate(0, 5 * PlaybackView.TILEY - viewRect.top); + canvas.translate(0, 5 * PlaybackView.TILE_SCALE - viewRect.top); for (ShapeDrawable shape : mShapes) { shape.draw(canvas); @@ -234,13 +231,15 @@ public class PlaybackGraphs { int yPos = LABELOFFSET; canvas.drawText(label, xPos, yPos, whiteLabels); for (int statIndex = 0; statIndex < Stats.length; statIndex++) { - label = resources.getString(R.string.format_stat, mStats[metricIndex][statIndex]); - yPos = LABELOFFSET + (1 + statIndex) * PlaybackView.TILEY / 2; + label = resources.getString(R.string.format_stat, + mStats[metricIndex][statIndex]); + yPos = LABELOFFSET + (1 + statIndex) * PlaybackView.TILE_SCALE + / 2; canvas.drawText(label, xPos, yPos, whiteLabels); } } for (int stringIndex = 0; stringIndex < strings.length; stringIndex++) { - int yPos = LABELOFFSET + stringIndex * PlaybackView.TILEY / 2; + int yPos = LABELOFFSET + stringIndex * PlaybackView.TILE_SCALE / 2; canvas.drawText(strings[stringIndex], 0, yPos, whiteLabels); } } diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java index f104eac21b89..edc8643e5b24 100644 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackView.java @@ -16,6 +16,9 @@ package com.test.tilebenchmark; +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -30,8 +33,9 @@ import android.view.View; import java.util.ArrayList; public class PlaybackView extends View { - public static final int TILEX = 300; - public static final int TILEY = 300; + public static final int TILE_SCALE = 300; + private static final int INVAL_FLAG = -2; + private static final int INVAL_CYCLE = 250; private Paint levelPaint = null, coordPaint = null, goldPaint = null; private PlaybackGraphs mGraphs; @@ -39,28 +43,46 @@ public class PlaybackView extends View { private ArrayList<ShapeDrawable> mTempShapes = new ArrayList<ShapeDrawable>(); private TileData mProfData[][] = null; private GestureDetector mGestureDetector = null; - private String mRenderStrings[] = new String[3]; + private String mRenderStrings[] = new String[4]; private class TileDrawable extends ShapeDrawable { TileData tile; + String label; - public TileDrawable(TileData t) { - int tileColorId = t.isReady ? R.color.ready_tile - : R.color.unready_tile; - getPaint().setColor(getResources().getColor(tileColorId)); - - setBounds(t.x * TILEX, t.y * TILEY, (t.x + 1) * TILEX, (t.y + 1) - * TILEY); + public TileDrawable(TileData t, int colorId) { this.tile = t; + getPaint().setColor(getResources().getColor(colorId)); + if (colorId == R.color.ready_tile + || colorId == R.color.unready_tile) { + + label = (int) (t.left / TILE_SCALE) + ", " + + (int) (t.top / TILE_SCALE); + // ignore scale value for tiles + setBounds(t.left, t.top, + t.right, t.bottom); + } else { + setBounds((int) (t.left * t.scale), + (int) (t.top * t.scale), + (int) (t.right * t.scale), + (int) (t.bottom * t.scale)); + } + } + + @SuppressWarnings("unused") + public void setColor(int color) { + getPaint().setColor(color); } @Override public void draw(Canvas canvas) { super.draw(canvas); - canvas.drawText(Integer.toString(tile.level), getBounds().left, - getBounds().bottom, levelPaint); - canvas.drawText(tile.x + "," + tile.y, getBounds().left, - ((getBounds().bottom + getBounds().top) / 2), coordPaint); + if (label != null) { + canvas.drawText(Integer.toString(tile.level), getBounds().left, + getBounds().bottom, levelPaint); + canvas.drawText(label, getBounds().left, + ((getBounds().bottom + getBounds().top) / 2), + coordPaint); + } } } @@ -92,10 +114,10 @@ public class PlaybackView extends View { private void init() { levelPaint = new Paint(); levelPaint.setColor(Color.WHITE); - levelPaint.setTextSize(TILEY / 2); + levelPaint.setTextSize(TILE_SCALE / 2); coordPaint = new Paint(); coordPaint.setColor(Color.BLACK); - coordPaint.setTextSize(TILEY / 3); + coordPaint.setTextSize(TILE_SCALE / 3); goldPaint = new Paint(); goldPaint.setColor(0xffa0e010); mGraphs = new PlaybackGraphs(); @@ -110,6 +132,7 @@ public class PlaybackView extends View { } mGraphs.draw(canvas, mTempShapes, mRenderStrings, getResources()); + invalidate(); // may have animations, force redraw } public int setFrame(int frame) { @@ -117,35 +140,66 @@ public class PlaybackView extends View { return 0; } - int readyTiles = 0, unreadyTiles = 0, unplacedTiles = 0; + int readyTiles = 0, unreadyTiles = 0, unplacedTiles = 0, numInvals = 0; mTempShapes.clear(); - // draw actual tiles - for (int tileID = 2; tileID < mProfData[frame].length; tileID++) { - TileData t = mProfData[frame][tileID]; - mTempShapes.add(new TileDrawable(t)); - if (t.isReady) { - readyTiles++; + // create tile shapes (as they're drawn on bottom) + for (TileData t : mProfData[frame]) { + if (t.level != INVAL_FLAG && t != mProfData[frame][0]) { + int colorId; + if (t.isReady) { + readyTiles++; + colorId = R.color.ready_tile; + } else { + unreadyTiles++; + colorId = R.color.unready_tile; + } + if (t.left < 0 || t.top < 0) { + unplacedTiles++; + } + mTempShapes.add(new TileDrawable(t, colorId)); } else { - unreadyTiles++; + numInvals++; } - if (t.x < 0 || t.y < 0) { - unplacedTiles++; + } + + // create invalidate shapes (drawn above tiles) + int invalId = 0; + for (TileData t : mProfData[frame]) { + if (t.level == INVAL_FLAG && t != mProfData[frame][0]) { + TileDrawable invalShape = new TileDrawable(t, + R.color.inval_region_start); + ValueAnimator tileAnimator = ObjectAnimator.ofInt(invalShape, + "color", + getResources().getColor(R.color.inval_region_start), + getResources().getColor(R.color.inval_region_stop)); + tileAnimator.setDuration(numInvals * INVAL_CYCLE); + tileAnimator.setEvaluator(new ArgbEvaluator()); + tileAnimator.setRepeatCount(ValueAnimator.INFINITE); + tileAnimator.setRepeatMode(ValueAnimator.RESTART); + float delay = (float) (invalId) * INVAL_CYCLE; + tileAnimator.setStartDelay((int) delay); + invalId++; + tileAnimator.start(); + + mTempShapes.add(invalShape); } } + mRenderStrings[0] = getResources().getString(R.string.format_stat_name, getResources().getString(R.string.ready_tiles), readyTiles); mRenderStrings[1] = getResources().getString(R.string.format_stat_name, getResources().getString(R.string.unready_tiles), unreadyTiles); mRenderStrings[2] = getResources().getString(R.string.format_stat_name, - getResources().getString(R.string.unplaced_tiles), unplacedTiles); - - // draw view rect (using first two TileData objects) - ShapeDrawable viewShape = new ShapeDrawable(); - viewShape.getPaint().setColor(0xff0000ff); - viewShape.setAlpha(64); - viewShape.setBounds(mProfData[frame][0].x, mProfData[frame][0].y, - mProfData[frame][1].x, mProfData[frame][1].y); + getResources().getString(R.string.unplaced_tiles), + unplacedTiles); + mRenderStrings[3] = getResources().getString(R.string.format_stat_name, + getResources().getString(R.string.number_invalidates), + numInvals); + + // draw view rect (using first TileData object, on top) + TileDrawable viewShape = new TileDrawable(mProfData[frame][0], + R.color.view); mTempShapes.add(viewShape); this.invalidate(); return frame; diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java index 23b62751b931..1521807eb1dc 100644 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java @@ -38,6 +38,7 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; +import android.widget.ToggleButton; import java.io.FileOutputStream; import java.io.IOException; @@ -58,11 +59,24 @@ public class ProfileActivity extends Activity { // before test Button mInspectButton; + ToggleButton mCaptureButton; Spinner mVelocitySpinner; + Spinner mMovementSpinner; EditText mUrl; ProfiledWebView mWeb; ProfileCallback mCallback; + LoggingWebViewClient mLoggingWebViewClient = new LoggingWebViewClient(); + AutoLoggingWebViewClient mAutoLoggingWebViewClient = new AutoLoggingWebViewClient(); + + private enum TestingState { + NOT_TESTING, + PRE_TESTING, + START_TESTING, + STOP_TESTING, + SAVED_TESTING + }; + private class VelocitySelectedListener implements OnItemSelectedListener { @Override public void onItemSelected(AdapterView<?> parent, View view, @@ -77,6 +91,31 @@ public class ProfileActivity extends Activity { } } + private class MovementSelectedListener implements OnItemSelectedListener { + @Override + public void onItemSelected(AdapterView<?> parent, View view, + int position, long id) { + String movementStr = parent.getItemAtPosition(position).toString(); + if (movementStr == getResources().getString( + R.string.movement_auto_scroll) + || movementStr == getResources().getString( + R.string.movement_auto_fling)) { + mWeb.setWebViewClient(mAutoLoggingWebViewClient); + mCaptureButton.setEnabled(false); + mVelocitySpinner.setEnabled(true); + } else if (movementStr == getResources().getString( + R.string.movement_manual)) { + mWeb.setWebViewClient(mLoggingWebViewClient); + mCaptureButton.setEnabled(true); + mVelocitySpinner.setEnabled(false); + } + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + } + } + private class LoggingWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { @@ -88,6 +127,9 @@ public class ProfileActivity extends Activity { super.onPageStarted(view, url, favicon); mUrl.setText(url); } + } + + private class AutoLoggingWebViewClient extends LoggingWebViewClient { @Override public void onPageFinished(WebView view, String url) { @@ -100,10 +142,16 @@ public class ProfileActivity extends Activity { @Override public void onFinish() { - mWeb.startScrollTest(mCallback); + startViewProfiling(true); } }.start(); } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + setTestingState(TestingState.PRE_TESTING); + } } private class StoreFileTask extends @@ -125,24 +173,65 @@ public class ProfileActivity extends Activity { @Override protected void onPostExecute(Void v) { - mUrl.setBackgroundResource(R.color.finished_url); + setTestingState(TestingState.SAVED_TESTING); } } + public void setTestingState(TestingState state) { + switch (state) { + case NOT_TESTING: + mUrl.setBackgroundResource(R.color.background_not_testing); + mInspectButton.setEnabled(true); + mMovementSpinner.setEnabled(true); + break; + case PRE_TESTING: + mInspectButton.setEnabled(false); + mMovementSpinner.setEnabled(false); + break; + case START_TESTING: + mUrl.setBackgroundResource(R.color.background_start_testing); + mInspectButton.setEnabled(false); + mMovementSpinner.setEnabled(false); + break; + case STOP_TESTING: + mUrl.setBackgroundResource(R.color.background_stop_testing); + break; + case SAVED_TESTING: + mInspectButton.setEnabled(true); + mMovementSpinner.setEnabled(true); + break; + } + } + + /** auto - automatically scroll. */ + private void startViewProfiling(boolean auto) { + if (!auto) { + // manual, toggle capture button to indicate capture state to user + mCaptureButton.setChecked(true); + } + mWeb.startScrollTest(mCallback, auto); + setTestingState(TestingState.START_TESTING); + } + /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mInspectButton = (Button) findViewById(R.id.inspect); + mCaptureButton = (ToggleButton) findViewById(R.id.capture); mVelocitySpinner = (Spinner) findViewById(R.id.velocity); + mMovementSpinner = (Spinner) findViewById(R.id.movement); mUrl = (EditText) findViewById(R.id.url); mWeb = (ProfiledWebView) findViewById(R.id.web); mCallback = new ProfileCallback() { @SuppressWarnings("unchecked") @Override public void profileCallback(TileData[][] data) { - new StoreFileTask().execute(new Pair<String, TileData[][]>(TEMP_FILENAME, data)); + new StoreFileTask().execute(new Pair<String, TileData[][]>( + TEMP_FILENAME, data)); + mCaptureButton.setChecked(false); + setTestingState(TestingState.STOP_TESTING); } }; @@ -166,6 +255,33 @@ public class ProfileActivity extends Activity { new VelocitySelectedListener()); mVelocitySpinner.setSelection(3); + // Movement spinner + String content[] = { + getResources().getString(R.string.movement_auto_scroll), + getResources().getString(R.string.movement_auto_fling), + getResources().getString(R.string.movement_manual) + }; + adapter = new ArrayAdapter<CharSequence>(this, + android.R.layout.simple_spinner_item, content); + adapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item); + mMovementSpinner.setAdapter(adapter); + mMovementSpinner.setOnItemSelectedListener( + new MovementSelectedListener()); + mMovementSpinner.setSelection(0); + + // Capture toggle button + mCaptureButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mCaptureButton.isChecked()) { + startViewProfiling(false); + } else { + mWeb.stopScrollTest(); + } + } + }); + // Custom profiling WebView WebSettings settings = mWeb.getSettings(); settings.setJavaScriptEnabled(true); @@ -180,12 +296,13 @@ public class ProfileActivity extends Activity { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { String url = mUrl.getText().toString(); - mUrl.setBackgroundResource(R.color.unfinished_url); mWeb.loadUrl(url); mWeb.requestFocus(); return true; } }); + + setTestingState(TestingState.NOT_TESTING); } public void setCallback(ProfileCallback callback) { diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java index 656062421b97..d3941be41302 100644 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java @@ -59,12 +59,13 @@ public class ProfiledWebView extends WebView { } /* - * Called once the page is loaded to start scrolling for evaluating tiles + * Called once the page is loaded to start scrolling for evaluating tiles. + * If autoScrolling isn't set, stop must be called manually. */ - public void startScrollTest(ProfileCallback callback) { - isScrolling = true; + public void startScrollTest(ProfileCallback callback, boolean autoScrolling) { + isScrolling = autoScrolling; mCallback = callback; - super.tileProfilingStart(); + tileProfilingStart(); invalidate(); } @@ -72,19 +73,31 @@ public class ProfiledWebView extends WebView { * Called once the page has stopped scrolling */ public void stopScrollTest() { - float testRatio = super.tileProfilingStop(); + super.tileProfilingStop(); + + if (mCallback == null) { + tileProfilingClear(); + return; + } TileData data[][] = new TileData[super.tileProfilingNumFrames()][]; for (int frame = 0; frame < data.length; frame++) { data[frame] = new TileData[ - super.tileProfilingNumTilesInFrame(frame)]; + tileProfilingNumTilesInFrame(frame)]; for (int tile = 0; tile < data[frame].length; tile++) { - int x = super.tileProfilingGetX(frame, tile); - int y = super.tileProfilingGetY(frame, tile); - boolean isReady = super.tileProfilingGetReady(frame, tile); - int level = super.tileProfilingGetLevel(frame, tile); + int left = tileProfilingGetInt(frame, tile, "left"); + int top = tileProfilingGetInt(frame, tile, "top"); + int right = tileProfilingGetInt(frame, tile, "right"); + int bottom = tileProfilingGetInt(frame, tile, "bottom"); + + boolean isReady = super.tileProfilingGetInt( + frame, tile, "isReady") == 1; + int level = tileProfilingGetInt(frame, tile, "level"); + + float scale = tileProfilingGetFloat(frame, tile, "scale"); - data[frame][tile] = new TileData(x, y, isReady, level); + data[frame][tile] = new TileData(left, top, right, bottom, + isReady, level, scale); } } super.tileProfilingClear(); diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java b/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java index 7d4bb9f5217f..3e729a6282be 100644 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/TileData.java @@ -19,14 +19,24 @@ package com.test.tilebenchmark; import java.io.Serializable; public class TileData implements Serializable { - public int x, y; + int left, top, right, bottom; public boolean isReady; public int level; + public float scale; - public TileData(int x, int y, boolean isReady, int level) { - this.x = x; - this.y = y; + public TileData(int left, int top, int right, int bottom, boolean isReady, + int level, float scale) { + this.left = left; + this.right = right; + this.top = top; + this.bottom = bottom; this.isReady = isReady; this.level = level; + this.scale = scale; + } + + public String toString() { + return "Tile (" + left + "," + top + ")->(" + + right + "," + bottom + ")"; } } diff --git a/wifi/java/android/net/wifi/StateChangeResult.java b/wifi/java/android/net/wifi/StateChangeResult.java new file mode 100644 index 000000000000..8ab5982c2116 --- /dev/null +++ b/wifi/java/android/net/wifi/StateChangeResult.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2011 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.wifi; + +/** + * Stores supplicant state change information passed from WifiMonitor to + * a state machine. WifiStateMachine, SupplicantStateTracker and WpsStateMachine + * are example state machines that handle it. + * @hide + */ +public class StateChangeResult { + StateChangeResult(int networkId, String BSSID, SupplicantState state) { + this.state = state; + this.BSSID = BSSID; + this.networkId = networkId; + } + int networkId; + String BSSID; + SupplicantState state; +} diff --git a/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java index 0c4f9f6d49b0..9168e62489bd 100644 --- a/wifi/java/android/net/wifi/SupplicantStateTracker.java +++ b/wifi/java/android/net/wifi/SupplicantStateTracker.java @@ -19,7 +19,7 @@ package android.net.wifi; import com.android.internal.util.State; import com.android.internal.util.StateMachine; -import android.net.wifi.WifiStateMachine.StateChangeResult; +import android.net.wifi.StateChangeResult; import android.content.Context; import android.content.Intent; import android.os.Handler; @@ -159,11 +159,11 @@ class SupplicantStateTracker extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch (message.what) { - case WifiStateMachine.AUTHENTICATION_FAILURE_EVENT: + case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: mAuthenticationFailuresCount++; mAuthFailureInSupplicantBroadcast = true; break; - case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: StateChangeResult stateChangeResult = (StateChangeResult) message.obj; SupplicantState state = stateChangeResult.state; sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast); @@ -251,7 +251,7 @@ class SupplicantStateTracker extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch (message.what) { - case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: StateChangeResult stateChangeResult = (StateChangeResult) message.obj; SupplicantState state = stateChangeResult.state; if (SupplicantState.isHandshakeState(state)) { @@ -293,7 +293,7 @@ class SupplicantStateTracker extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch(message.what) { - case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: StateChangeResult stateChangeResult = (StateChangeResult) message.obj; SupplicantState state = stateChangeResult.state; sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast); diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java index 4ec4cfcd34d9..2ccc8a2c56fe 100644 --- a/wifi/java/android/net/wifi/WifiMonitor.java +++ b/wifi/java/android/net/wifi/WifiMonitor.java @@ -16,15 +16,24 @@ package android.net.wifi; -import android.util.Log; import android.net.NetworkInfo; +import android.net.wifi.p2p.WifiP2pConfig; +import android.net.wifi.p2p.WifiP2pDevice; +import android.net.wifi.p2p.WifiP2pGroup; +import android.net.wifi.StateChangeResult; +import android.os.Message; +import android.util.Log; + + +import com.android.internal.util.Protocol; +import com.android.internal.util.StateMachine; import java.util.regex.Pattern; import java.util.regex.Matcher; /** * Listens for events from the wpa_supplicant server, and passes them on - * to the {@link WifiStateMachine} for handling. Runs in its own thread. + * to the {@link StateMachine} for handling. Runs in its own thread. * * @hide */ @@ -45,16 +54,16 @@ public class WifiMonitor { private static final int UNKNOWN = 9; /** All events coming from the supplicant start with this prefix */ - private static final String eventPrefix = "CTRL-EVENT-"; - private static final int eventPrefixLen = eventPrefix.length(); + private static final String EVENT_PREFIX_STR = "CTRL-EVENT-"; + private static final int EVENT_PREFIX_LEN_STR = EVENT_PREFIX_STR.length(); /** All WPA events coming from the supplicant start with this prefix */ - private static final String wpaEventPrefix = "WPA:"; - private static final String passwordKeyMayBeIncorrectEvent = + private static final String WPA_EVENT_PREFIX_STR = "WPA:"; + private static final String PASSWORD_MAY_BE_INCORRECT_STR = "pre-shared key may be incorrect"; /* WPS events */ - private static final String wpsOverlapEvent = "WPS-OVERLAP-DETECTED"; + private static final String WPS_OVERLAP_STR = "WPS-OVERLAP-DETECTED"; /** * Names of events from wpa_supplicant (minus the prefix). In the @@ -68,26 +77,26 @@ public class WifiMonitor { * </pre> * <code>xx:xx:xx:xx:xx:xx</code> is the BSSID of the associated access point */ - private static final String connectedEvent = "CONNECTED"; + private static final String CONNECTED_STR = "CONNECTED"; /** * <pre> * CTRL-EVENT-DISCONNECTED - Disconnect event - remove keys * </pre> */ - private static final String disconnectedEvent = "DISCONNECTED"; + private static final String DISCONNECTED_STR = "DISCONNECTED"; /** * <pre> * CTRL-EVENT-STATE-CHANGE x * </pre> * <code>x</code> is the numerical value of the new state. */ - private static final String stateChangeEvent = "STATE-CHANGE"; + private static final String STATE_CHANGE_STR = "STATE-CHANGE"; /** * <pre> * CTRL-EVENT-SCAN-RESULTS ready * </pre> */ - private static final String scanResultsEvent = "SCAN-RESULTS"; + private static final String SCAN_RESULTS_STR = "SCAN-RESULTS"; /** * <pre> @@ -95,32 +104,32 @@ public class WifiMonitor { * </pre> * {@code x} is the link speed in Mb/sec. */ - private static final String linkSpeedEvent = "LINK-SPEED"; + private static final String LINK_SPEED_STR = "LINK-SPEED"; /** * <pre> * CTRL-EVENT-TERMINATING - signal x * </pre> * <code>x</code> is the signal that caused termination. */ - private static final String terminatingEvent = "TERMINATING"; + private static final String TERMINATING_STR = "TERMINATING"; /** * <pre> * CTRL-EVENT-DRIVER-STATE state * </pre> * <code>state</code> can be HANGED */ - private static final String driverStateEvent = "DRIVER-STATE"; + private static final String DRIVER_STATE_STR = "DRIVER-STATE"; /** * <pre> * CTRL-EVENT-EAP-FAILURE EAP authentication failed * </pre> */ - private static final String eapFailureEvent = "EAP-FAILURE"; + private static final String EAP_FAILURE_STR = "EAP-FAILURE"; /** * This indicates an authentication failure on EAP FAILURE event */ - private static final String eapAuthFailure = "EAP authentication failed"; + private static final String EAP_AUTH_FAILURE_STR = "EAP authentication failed"; /** * Regex pattern for extracting an Ethernet-style MAC address from a string. @@ -130,17 +139,116 @@ public class WifiMonitor { private static Pattern mConnectedEventPattern = Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) "); - private final WifiStateMachine mWifiStateMachine; + /** P2P events */ + private static final String P2P_EVENT_PREFIX_STR = "P2P"; + + /* P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13 pri_dev_type=1-0050F204-1 + name='p2p-TEST1' config_methods=0x188 dev_capab=0x27 group_capab=0x0 */ + private static final String P2P_DEVICE_FOUND_STR = "P2P-DEVICE-FOUND"; + + /* P2P-DEVICE-LOST p2p_dev_addr=42:fc:89:e1:e2:27 */ + private static final String P2P_DEVICE_LOST_STR = "P2P-DEVICE-LOST"; + + /* P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 */ + private static final String P2P_GO_NEG_REQUEST_STR = "P2P-GO-NEG-REQUEST"; + + private static final String P2P_GO_NEG_SUCCESS_STR = "P2P-GO-NEG-SUCCESS"; + + private static final String P2P_GO_NEG_FAILURE_STR = "P2P-GO-NEG-FAILURE"; + + private static final String P2P_GROUP_FORMATION_SUCCESS_STR = + "P2P-GROUP-FORMATION-SUCCESS"; + + private static final String P2P_GROUP_FORMATION_FAILURE_STR = + "P2P-GROUP-FORMATION-FAILURE"; + + /* P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437 + [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc|passphrase="fKG4jMe3"] + go_dev_addr=fa:7b:7a:42:02:13 */ + private static final String P2P_GROUP_STARTED_STR = "P2P-GROUP-STARTED"; + + /* P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED */ + private static final String P2P_GROUP_REMOVED_STR = "P2P-GROUP-REMOVED"; + + /* P2P-INVITATION-RECEIVED sa=fa:7b:7a:42:02:13 go_dev_addr=f8:7b:7a:42:02:13 + bssid=fa:7b:7a:42:82:13 unknown-network */ + private static final String P2P_INVITATION_RECEIVED_STR = "P2P-INVITATION-RECEIVED"; + + /* P2P-INVITATION-RESULT status=1 */ + private static final String P2P_INVITATION_RESULT_STR = "P2P-INVITATION-RESULT"; + + /* P2P-PROV-DISC-PBC-REQ 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27 + pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 + group_capab=0x0 */ + private static final String P2P_PROV_DISC_PBC_REQ_STR = "P2P-PROV-DISC-PBC-REQ"; + /* P2P-PROV-DISC-ENTER-PIN 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27 + pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 + group_capab=0x0 */ + private static final String P2P_PROV_DISC_ENTER_PIN_STR = "P2P-PROV-DISC-ENTER-PIN"; + /* P2P-PROV-DISC-SHOW-PIN 42:fc:89:e1:e2:27 44490607 p2p_dev_addr=42:fc:89:e1:e2:27 + pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 + group_capab=0x0 */ + private static final String P2P_PROV_DISC_SHOW_PIN_STR = "P2P-PROV-DISC-SHOW-PIN"; + + private static final String HOST_AP_EVENT_PREFIX_STR = "AP"; + /* AP-STA-CONNECTED 42:fc:89:a8:96:09 */ + private static final String AP_STA_CONNECTED_STR = "AP-STA-CONNECTED"; + /* AP-STA-DISCONNECTED 42:fc:89:a8:96:09 */ + private static final String AP_STA_DISCONNECTED_STR = "AP-STA-DISCONNECTED"; + + private final StateMachine mStateMachine; + + /* Supplicant events reported to a state machine */ + private static final int BASE = Protocol.BASE_WIFI_MONITOR; + + /* Connection to supplicant established */ + public static final int SUP_CONNECTION_EVENT = BASE + 1; + /* Connection to supplicant lost */ + public static final int SUP_DISCONNECTION_EVENT = BASE + 2; + /* Network connection completed */ + public static final int NETWORK_CONNECTION_EVENT = BASE + 3; + /* Network disconnection completed */ + public static final int NETWORK_DISCONNECTION_EVENT = BASE + 4; + /* Scan results are available */ + public static final int SCAN_RESULTS_EVENT = BASE + 5; + /* Supplicate state changed */ + public static final int SUPPLICANT_STATE_CHANGE_EVENT = BASE + 6; + /* Password failure and EAP authentication failure */ + public static final int AUTHENTICATION_FAILURE_EVENT = BASE + 7; + /* WPS overlap detected */ + public static final int WPS_OVERLAP_EVENT = BASE + 8; + /* Driver was hung */ + public static final int DRIVER_HUNG_EVENT = BASE + 9; + + /* P2P events */ + public static final int P2P_DEVICE_FOUND_EVENT = BASE + 21; + public static final int P2P_DEVICE_LOST_EVENT = BASE + 22; + public static final int P2P_GO_NEGOTIATION_REQUEST_EVENT = BASE + 23; + public static final int P2P_GO_NEGOTIATION_SUCCESS_EVENT = BASE + 25; + public static final int P2P_GO_NEGOTIATION_FAILURE_EVENT = BASE + 26; + public static final int P2P_GROUP_FORMATION_SUCCESS_EVENT = BASE + 27; + public static final int P2P_GROUP_FORMATION_FAILURE_EVENT = BASE + 28; + public static final int P2P_GROUP_STARTED_EVENT = BASE + 29; + public static final int P2P_GROUP_REMOVED_EVENT = BASE + 30; + public static final int P2P_INVITATION_RECEIVED_EVENT = BASE + 31; + public static final int P2P_INVITATION_RESULT_EVENT = BASE + 32; + public static final int P2P_PROV_DISC_PBC_REQ_EVENT = BASE + 33; + public static final int P2P_PROV_DISC_ENTER_PIN_EVENT = BASE + 34; + public static final int P2P_PROV_DISC_SHOW_PIN_EVENT = BASE + 35; + + /* hostap events */ + public static final int AP_STA_DISCONNECTED_EVENT = BASE + 41; + public static final int AP_STA_CONNECTED_EVENT = BASE + 42; /** * This indicates the supplicant connection for the monitor is closed */ - private static final String monitorSocketClosed = "connection closed"; + private static final String MONITOR_SOCKET_CLOSED_STR = "connection closed"; /** * This indicates a read error on the monitor socket conenction */ - private static final String wpaRecvError = "recv error"; + private static final String WPA_RECV_ERROR_STR = "recv error"; /** * Tracks consecutive receive errors @@ -152,8 +260,8 @@ public class WifiMonitor { */ private static final int MAX_RECV_ERRORS = 10; - public WifiMonitor(WifiStateMachine wifiStateMachine) { - mWifiStateMachine = wifiStateMachine; + public WifiMonitor(StateMachine wifiStateMachine) { + mStateMachine = wifiStateMachine; } public void startMonitoring() { @@ -170,9 +278,9 @@ public class WifiMonitor { if (connectToSupplicant()) { // Send a message indicating that it is now possible to send commands // to the supplicant - mWifiStateMachine.notifySupplicantConnection(); + mStateMachine.sendMessage(SUP_CONNECTION_EVENT); } else { - mWifiStateMachine.notifySupplicantLost(); + mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT); return; } @@ -181,20 +289,24 @@ public class WifiMonitor { String eventStr = WifiNative.waitForEvent(); // Skip logging the common but mostly uninteresting scan-results event - if (false && eventStr.indexOf(scanResultsEvent) == -1) { - Log.v(TAG, "Event [" + eventStr + "]"); + if (false && eventStr.indexOf(SCAN_RESULTS_STR) == -1) { + Log.d(TAG, "Event [" + eventStr + "]"); } - if (!eventStr.startsWith(eventPrefix)) { - if (eventStr.startsWith(wpaEventPrefix) && - 0 < eventStr.indexOf(passwordKeyMayBeIncorrectEvent)) { - mWifiStateMachine.notifyAuthenticationFailure(); - } else if (eventStr.startsWith(wpsOverlapEvent)) { - mWifiStateMachine.notifyWpsOverlap(); + if (!eventStr.startsWith(EVENT_PREFIX_STR)) { + if (eventStr.startsWith(WPA_EVENT_PREFIX_STR) && + 0 < eventStr.indexOf(PASSWORD_MAY_BE_INCORRECT_STR)) { + mStateMachine.sendMessage(AUTHENTICATION_FAILURE_EVENT); + } else if (eventStr.startsWith(WPS_OVERLAP_STR)) { + mStateMachine.sendMessage(WPS_OVERLAP_EVENT); + } else if (eventStr.startsWith(P2P_EVENT_PREFIX_STR)) { + handleP2pEvents(eventStr); + } else if (eventStr.startsWith(HOST_AP_EVENT_PREFIX_STR)) { + handleHostApEvents(eventStr); } continue; } - String eventName = eventStr.substring(eventPrefixLen); + String eventName = eventStr.substring(EVENT_PREFIX_LEN_STR); int nameEnd = eventName.indexOf(' '); if (nameEnd != -1) eventName = eventName.substring(0, nameEnd); @@ -206,21 +318,21 @@ public class WifiMonitor { * Map event name into event enum */ int event; - if (eventName.equals(connectedEvent)) + if (eventName.equals(CONNECTED_STR)) event = CONNECTED; - else if (eventName.equals(disconnectedEvent)) + else if (eventName.equals(DISCONNECTED_STR)) event = DISCONNECTED; - else if (eventName.equals(stateChangeEvent)) + else if (eventName.equals(STATE_CHANGE_STR)) event = STATE_CHANGE; - else if (eventName.equals(scanResultsEvent)) + else if (eventName.equals(SCAN_RESULTS_STR)) event = SCAN_RESULTS; - else if (eventName.equals(linkSpeedEvent)) + else if (eventName.equals(LINK_SPEED_STR)) event = LINK_SPEED; - else if (eventName.equals(terminatingEvent)) + else if (eventName.equals(TERMINATING_STR)) event = TERMINATING; - else if (eventName.equals(driverStateEvent)) + else if (eventName.equals(DRIVER_STATE_STR)) event = DRIVER_STATE; - else if (eventName.equals(eapFailureEvent)) + else if (eventName.equals(EAP_FAILURE_STR)) event = EAP_FAILURE; else event = UNKNOWN; @@ -249,7 +361,7 @@ public class WifiMonitor { * If monitor socket is closed, we have already * stopped the supplicant, simply exit the monitor thread */ - if (eventData.startsWith(monitorSocketClosed)) { + if (eventData.startsWith(MONITOR_SOCKET_CLOSED_STR)) { if (false) { Log.d(TAG, "Monitor socket is closed, exiting thread"); } @@ -260,7 +372,7 @@ public class WifiMonitor { * Close the supplicant connection if we see * too many recv errors */ - if (eventData.startsWith(wpaRecvError)) { + if (eventData.startsWith(WPA_RECV_ERROR_STR)) { if (++mRecvErrors > MAX_RECV_ERRORS) { if (false) { Log.d(TAG, "too many recv errors, closing connection"); @@ -271,11 +383,11 @@ public class WifiMonitor { } // notify and exit - mWifiStateMachine.notifySupplicantLost(); + mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT); break; } else if (event == EAP_FAILURE) { - if (eventData.startsWith(eapAuthFailure)) { - mWifiStateMachine.notifyAuthenticationFailure(); + if (eventData.startsWith(EAP_AUTH_FAILURE_STR)) { + mStateMachine.sendMessage(AUTHENTICATION_FAILURE_EVENT); } } else { handleEvent(event, eventData); @@ -305,7 +417,7 @@ public class WifiMonitor { return; } if (state.equals("HANGED")) { - mWifiStateMachine.notifyDriverHung(); + mStateMachine.sendMessage(DRIVER_HUNG_EVENT); } } @@ -326,7 +438,7 @@ public class WifiMonitor { break; case SCAN_RESULTS: - mWifiStateMachine.notifyScanResultsAvailable(); + mStateMachine.sendMessage(SCAN_RESULTS_EVENT); break; case UNKNOWN: @@ -335,6 +447,59 @@ public class WifiMonitor { } /** + * Handle p2p events + */ + private void handleP2pEvents(String dataString) { + if (dataString.startsWith(P2P_DEVICE_FOUND_STR)) { + mStateMachine.sendMessage(P2P_DEVICE_FOUND_EVENT, new WifiP2pDevice(dataString)); + } else if (dataString.startsWith(P2P_DEVICE_LOST_STR)) { + mStateMachine.sendMessage(P2P_DEVICE_LOST_EVENT, new WifiP2pDevice(dataString)); + } else if (dataString.startsWith(P2P_GO_NEG_REQUEST_STR)) { + mStateMachine.sendMessage(P2P_GO_NEGOTIATION_REQUEST_EVENT, + new WifiP2pConfig(dataString)); + } else if (dataString.startsWith(P2P_GO_NEG_SUCCESS_STR)) { + mStateMachine.sendMessage(P2P_GO_NEGOTIATION_SUCCESS_EVENT); + } else if (dataString.startsWith(P2P_GO_NEG_FAILURE_STR)) { + mStateMachine.sendMessage(P2P_GO_NEGOTIATION_FAILURE_EVENT); + } else if (dataString.startsWith(P2P_GROUP_FORMATION_SUCCESS_STR)) { + mStateMachine.sendMessage(P2P_GROUP_FORMATION_SUCCESS_EVENT); + } else if (dataString.startsWith(P2P_GROUP_FORMATION_FAILURE_STR)) { + mStateMachine.sendMessage(P2P_GROUP_FORMATION_FAILURE_EVENT); + } else if (dataString.startsWith(P2P_GROUP_STARTED_STR)) { + mStateMachine.sendMessage(P2P_GROUP_STARTED_EVENT, new WifiP2pGroup(dataString)); + } else if (dataString.startsWith(P2P_GROUP_REMOVED_STR)) { + mStateMachine.sendMessage(P2P_GROUP_REMOVED_EVENT, new WifiP2pGroup(dataString)); + } else if (dataString.startsWith(P2P_INVITATION_RECEIVED_STR)) { + mStateMachine.sendMessage(P2P_INVITATION_RECEIVED_EVENT, + new WifiP2pGroup(dataString)); + } else if (dataString.startsWith(P2P_INVITATION_RESULT_STR)) { + String[] tokens = dataString.split(" "); + if (tokens.length != 2) return; + String[] nameValue = tokens[1].split("="); + if (nameValue.length != 2) return; + mStateMachine.sendMessage(P2P_INVITATION_RESULT_EVENT, nameValue[1]); + } else if (dataString.startsWith(P2P_PROV_DISC_PBC_REQ_STR)) { + mStateMachine.sendMessage(P2P_PROV_DISC_PBC_REQ_EVENT, + new WifiP2pDevice(dataString)); + } else if (dataString.startsWith(P2P_PROV_DISC_ENTER_PIN_STR)) { + mStateMachine.sendMessage(P2P_PROV_DISC_ENTER_PIN_EVENT, + new WifiP2pDevice(dataString)); + } + } + + /** + * Handle hostap events + */ + private void handleHostApEvents(String dataString) { + String[] tokens = dataString.split(" "); + if (tokens[0].equals(AP_STA_CONNECTED_STR)) { + mStateMachine.sendMessage(AP_STA_CONNECTED_EVENT, tokens[1]); + } else if (tokens[0].equals(AP_STA_DISCONNECTED_STR)) { + mStateMachine.sendMessage(AP_STA_DISCONNECTED_EVENT, tokens[1]); + } + } + + /** * Handle the supplicant STATE-CHANGE event * @param dataString New supplicant state string in the format: * id=network-id state=new-state @@ -383,7 +548,7 @@ public class WifiMonitor { if (newSupplicantState == SupplicantState.INVALID) { Log.w(TAG, "Invalid supplicant state: " + newState); } - mWifiStateMachine.notifySupplicantStateChange(networkId, BSSID, newSupplicantState); + notifySupplicantStateChange(networkId, BSSID, newSupplicantState); } } @@ -403,7 +568,40 @@ public class WifiMonitor { } } } - mWifiStateMachine.notifyNetworkStateChange(newState, BSSID, networkId); + notifyNetworkStateChange(newState, BSSID, networkId); + } + + /** + * Send the state machine a notification that the state of Wifi connectivity + * has changed. + * @param networkId the configured network on which the state change occurred + * @param newState the new network state + * @param BSSID when the new state is {@link DetailedState#CONNECTED + * NetworkInfo.DetailedState.CONNECTED}, + * this is the MAC address of the access point. Otherwise, it + * is {@code null}. + */ + void notifyNetworkStateChange(NetworkInfo.DetailedState newState, String BSSID, int netId) { + if (newState == NetworkInfo.DetailedState.CONNECTED) { + Message m = mStateMachine.obtainMessage(NETWORK_CONNECTION_EVENT, + netId, 0, BSSID); + mStateMachine.sendMessage(m); + } else { + Message m = mStateMachine.obtainMessage(NETWORK_DISCONNECTION_EVENT, + netId, 0, BSSID); + mStateMachine.sendMessage(m); + } + } + + /** + * Send the state machine a notification that the state of the supplicant + * has changed. + * @param networkId the configured network on which the state change occurred + * @param newState the new {@code SupplicantState} + */ + void notifySupplicantStateChange(int networkId, String BSSID, SupplicantState newState) { + mStateMachine.sendMessage(mStateMachine.obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT, + new StateChangeResult(networkId, BSSID, newState))); } /** diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 6e13d0fb349c..3b043b3fa495 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -16,6 +16,16 @@ package android.net.wifi; +import android.net.wifi.p2p.WifiP2pConfig; +import android.net.wifi.p2p.WifiP2pGroup; +import android.net.wifi.p2p.WifiP2pDevice; +import android.util.Log; + +import java.io.InputStream; +import java.lang.Process; +import java.util.ArrayList; +import java.util.List; + /** * Native calls for sending requests to the supplicant daemon, and for * receiving asynchronous events. All methods of the form "xxxxCommand()" @@ -28,6 +38,10 @@ package android.net.wifi; * WifiStateTracker class except for waitForEvent() call which is * on a separate monitor channel for WifiMonitor * + * TODO: clean up the API and move the functionality from JNI to here. We should + * be able to get everything done with doBooleanCommand, doIntCommand and + * doStringCommand native commands + * * {@hide} */ public class WifiNative { @@ -104,18 +118,30 @@ public class WifiNative { public native static boolean stopDriverCommand(); + + /** + * Start filtering out Multicast V4 packets + * @return {@code true} if the operation succeeded, {@code false} otherwise + */ + public native static boolean startFilteringMulticastV4Packets(); + /** - * Start filtering out multicast packets, to reduce battery consumption - * that would result from processing them, only to discard them. + * Stop filtering out Multicast V4 packets. * @return {@code true} if the operation succeeded, {@code false} otherwise */ - public native static boolean startPacketFiltering(); + public native static boolean stopFilteringMulticastV4Packets(); /** - * Stop filtering out multicast packets. + * Start filtering out Multicast V6 packets * @return {@code true} if the operation succeeded, {@code false} otherwise */ - public native static boolean stopPacketFiltering(); + public native static boolean startFilteringMulticastV6Packets(); + + /** + * Stop filtering out Multicast V6 packets. + * @return {@code true} if the operation succeeded, {@code false} otherwise + */ + public native static boolean stopFilteringMulticastV6Packets(); public native static boolean setPowerModeCommand(int mode); @@ -174,4 +200,141 @@ public class WifiNative { public native static void enableBackgroundScanCommand(boolean enable); public native static void setScanIntervalCommand(int scanInterval); + + private native static boolean doBooleanCommand(String command); + + //STOPSHIP: remove this after native interface works and replace all + //calls to doBooleanTempCommand() with doBooleanCommand() + private static boolean doBooleanTempCommand(String command) { + try { + String str = "/system/bin/wpa_cli " + command; + Log.e("WifiNative", "===> " + str); + Runtime.getRuntime() + .exec(str).waitFor(); + } catch (Exception e) { + Log.e("WifiNative", "exception with doBooleanTempCommand"); + return false; + } + return true; + } + + private static String doStringTempCommand(String command) { + String lines[] = null; + try { + String str = "/system/bin/wpa_cli " + command; + Log.e("WifiNative", "===> " + str); + Process p = Runtime.getRuntime() + .exec(str); + InputStream in = p.getInputStream(); + p.waitFor(); + byte[] bytes=new byte[in.available()]; + in.read(bytes); + String s = new String(bytes); + Log.e("WifiNative", "====> doString: " + s); + lines = s.split("\\r?\\n"); + } catch (Exception e) { + Log.e("WifiNative", "exception with doBooleanTempCommand"); + return null; + } + return lines[1]; + } + + private native static int doIntCommand(String command); + + private native static String doStringCommand(String command); + + public static boolean p2pFind() { + return doBooleanTempCommand("p2p_find"); + } + + public static boolean p2pFind(int timeout) { + if (timeout <= 0) { + return p2pFind(); + } + return doBooleanTempCommand("p2p_find " + timeout); + } + + public static boolean p2pListen() { + return doBooleanTempCommand("p2p_listen"); + } + + public static boolean p2pListen(int timeout) { + if (timeout <= 0) { + return p2pListen(); + } + return doBooleanTempCommand("p2p_listen " + timeout); + } + + public static boolean p2pFlush() { + return doBooleanTempCommand("p2p_flush"); + } + + /* p2p_connect <peer device address> <pbc|pin|PIN#> [label|display|keypad] + [persistent] [join|auth] [go_intent=<0..15>] [freq=<in MHz>] */ + public static String p2pConnect(WifiP2pConfig config) { + if (config == null) return null; + List<String> args = new ArrayList<String>(); + WpsConfiguration wpsConfig = config.wpsConfig; + args.add(config.deviceAddress); + + switch (wpsConfig.setup) { + case PBC: + args.add("pbc"); + break; + case DISPLAY: + //TODO: pass the pin back for display + args.add("pin"); + args.add("display"); + break; + case KEYPAD: + args.add(wpsConfig.pin); + args.add("keypad"); + break; + case LABEL: + args.add(wpsConfig.pin); + args.add("label"); + default: + break; + } + + if (config.isPersistent) args.add("persistent"); + if (config.joinExistingGroup) args.add("join"); + + args.add("go_intent=" + config.groupOwnerIntent); + if (config.channel > 0) args.add("freq=" + config.channel); + + String command = "p2p_connect "; + for (String s : args) command += s + " "; + + return doStringTempCommand(command); + } + + public static boolean p2pGroupAdd() { + return doBooleanTempCommand("p2p_group_add"); + } + + public static boolean p2pGroupRemove(String iface) { + if (iface == null) return false; + return doBooleanTempCommand("p2p_group_remove " + iface); + } + + public static boolean p2pReject(String deviceAddress) { + return doBooleanTempCommand("p2p_reject " + deviceAddress); + } + + /* Invite a peer to a group */ + public static boolean p2pInvite(WifiP2pGroup group, String deviceAddress) { + if (group == null || deviceAddress == null) return false; + return doBooleanTempCommand("p2p_invite group=" + group.getInterface() + + " peer=" + deviceAddress + " go_dev_addr=" + group.getOwner().deviceAddress); + } + + public static boolean p2pWpsPbc() { + return doBooleanTempCommand("wps_pbc"); + } + + public static boolean p2pWpsPin(String pin) { + return doBooleanTempCommand("wps_pin any " + pin); + } + } diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 12efeb1e59f1..74c6b4947a10 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -56,6 +56,8 @@ import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkUtils; import android.net.wifi.WpsResult.Status; +import android.net.wifi.p2p.WifiP2pManager; +import android.net.wifi.StateChangeResult; import android.os.Binder; import android.os.IBinder; import android.os.INetworkManagementService; @@ -67,6 +69,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.WorkSource; +import android.server.WifiP2pService; import android.provider.Settings; import android.util.EventLog; import android.util.Log; @@ -82,12 +85,21 @@ import java.net.InetAddress; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; /** * Track the state of Wifi connectivity. All event handling is done here, * and all changes in connectivity state are initiated here. * + * Wi-Fi now supports three modes of operation: Client, Soft Ap and Direct + * In the current implementation, we do not support any concurrency and thus only + * one of Client, Soft Ap or Direct operation is supported at any time. + * + * The WifiStateMachine supports Soft Ap and Client operations while WifiP2pService + * handles Direct. WifiP2pService and WifiStateMachine co-ordinate to ensure only + * one exists at a certain time. + * * @hide */ public class WifiStateMachine extends StateMachine { @@ -96,7 +108,7 @@ public class WifiStateMachine extends StateMachine { private static final String NETWORKTYPE = "WIFI"; private static final boolean DBG = false; - /* TODO: fetch a configurable interface */ + /* TODO: This is no more used with the hostapd code. Clean up */ private static final String SOFTAP_IFACE = "wl0.1"; private WifiMonitor mWifiMonitor; @@ -160,9 +172,16 @@ public class WifiStateMachine extends StateMachine { /* Tracks current frequency mode */ private AtomicInteger mFrequencyBand = new AtomicInteger(WifiManager.WIFI_FREQUENCY_BAND_AUTO); + /* Tracks if we are filtering Multicast v4 packets. Default is to filter. */ + private AtomicBoolean mFilteringMulticastV4Packets = new AtomicBoolean(true); + // Channel for sending replies. private AsyncChannel mReplyChannel = new AsyncChannel(); + private WifiP2pManager mWifiP2pManager; + //Used to initiate a connection with WifiP2pService + private AsyncChannel mWifiP2pChannel = new AsyncChannel(); + // Event log tags (must be in sync with event-log-tags) private static final int EVENTLOG_WIFI_STATE_CHANGED = 50021; private static final int EVENTLOG_WIFI_EVENT_HANDLED = 50022; @@ -204,27 +223,10 @@ public class WifiStateMachine extends StateMachine { static final int CMD_SET_AP_CONFIG = BASE + 23; /* Get the soft access point configuration */ static final int CMD_GET_AP_CONFIG = BASE + 24; + /* Set configuration on tether interface */ + static final int CMD_TETHER_INTERFACE = BASE + 25; - static final int CMD_BLUETOOTH_ADAPTER_STATE_CHANGE = BASE + 25; - - /* Supplicant events */ - /* Connection to supplicant established */ - static final int SUP_CONNECTION_EVENT = BASE + 31; - /* Connection to supplicant lost */ - static final int SUP_DISCONNECTION_EVENT = BASE + 32; - /* Network connection completed */ - static final int NETWORK_CONNECTION_EVENT = BASE + 33; - /* Network disconnection completed */ - static final int NETWORK_DISCONNECTION_EVENT = BASE + 34; - /* Scan results are available */ - static final int SCAN_RESULTS_EVENT = BASE + 35; - /* Supplicate state changed */ - static final int SUPPLICANT_STATE_CHANGE_EVENT = BASE + 36; - /* Password failure and EAP authentication failure */ - static final int AUTHENTICATION_FAILURE_EVENT = BASE + 37; - /* WPS overlap detected */ - static final int WPS_OVERLAP_EVENT = BASE + 38; - + static final int CMD_BLUETOOTH_ADAPTER_STATE_CHANGE = BASE + 26; /* Supplicant commands */ /* Is supplicant alive ? */ @@ -285,6 +287,11 @@ public class WifiStateMachine extends StateMachine { static final int CMD_START_PACKET_FILTERING = BASE + 84; /* Clear packet filter */ static final int CMD_STOP_PACKET_FILTERING = BASE + 85; + + /* arg1 values to CMD_STOP_PACKET_FILTERING and CMD_START_PACKET_FILTERING */ + static final int MULTICAST_V6 = 1; + static final int MULTICAST_V4 = 0; + /* Connect to a specified network (network id * or WifiConfiguration) This involves increasing * the priority of the network, enabling the network @@ -321,6 +328,10 @@ public class WifiStateMachine extends StateMachine { /* Reset the WPS state machine */ static final int CMD_RESET_WPS_STATE = BASE + 122; + /* Interaction with WifiP2pService */ + public static final int WIFI_ENABLE_PENDING = BASE + 131; + public static final int P2P_ENABLE_PROCEED = BASE + 132; + private static final int CONNECT_MODE = 1; private static final int SCAN_ONLY_MODE = 2; @@ -412,8 +423,13 @@ public class WifiStateMachine extends StateMachine { /* Waiting for WPS to be completed*/ private State mWaitForWpsCompletionState = new WaitForWpsCompletionState(); - /* Soft Ap is running */ + /* Soft ap is running */ private State mSoftApStartedState = new SoftApStartedState(); + /* Soft ap is running and we are tethered through connectivity service */ + private State mTetheredState = new TetheredState(); + + /* Wait till p2p is disabled */ + private State mWaitForP2pDisableState = new WaitForP2pDisableState(); /** @@ -504,13 +520,9 @@ public class WifiStateMachine extends StateMachine { new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - ArrayList<String> available = intent.getStringArrayListExtra( ConnectivityManager.EXTRA_AVAILABLE_TETHER); - ArrayList<String> active = intent.getStringArrayListExtra( - ConnectivityManager.EXTRA_ACTIVE_TETHER); - updateTetherState(available, active); - + sendMessage(CMD_TETHER_INTERFACE, available); } },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); @@ -550,6 +562,8 @@ public class WifiStateMachine extends StateMachine { addState(mDriverStoppedState, mSupplicantStartedState); addState(mSupplicantStoppingState, mDefaultState); addState(mSoftApStartedState, mDefaultState); + addState(mTetheredState, mSoftApStartedState); + addState(mWaitForP2pDisableState, mDefaultState); setInitialState(mInitialState); @@ -868,17 +882,33 @@ public class WifiStateMachine extends StateMachine { } /** - * Start packet filtering + * Start filtering Multicast v4 packets */ - public void startPacketFiltering() { - sendMessage(CMD_START_PACKET_FILTERING); + public void startFilteringMulticastV4Packets() { + mFilteringMulticastV4Packets.set(true); + sendMessage(obtainMessage(CMD_START_PACKET_FILTERING, MULTICAST_V4, 0)); } /** - * Stop packet filtering + * Stop filtering Multicast v4 packets */ - public void stopPacketFiltering() { - sendMessage(CMD_STOP_PACKET_FILTERING); + public void stopFilteringMulticastV4Packets() { + mFilteringMulticastV4Packets.set(false); + sendMessage(obtainMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V4, 0)); + } + + /** + * Start filtering Multicast v4 packets + */ + public void startFilteringMulticastV6Packets() { + sendMessage(obtainMessage(CMD_START_PACKET_FILTERING, MULTICAST_V6, 0)); + } + + /** + * Stop filtering Multicast v4 packets + */ + public void stopFilteringMulticastV6Packets() { + sendMessage(obtainMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V6, 0)); } /** @@ -1023,14 +1053,17 @@ public class WifiStateMachine extends StateMachine { * Internal private functions ********************************************************/ - private void updateTetherState(ArrayList<String> available, ArrayList<String> tethered) { - - boolean wifiTethered = false; - boolean wifiAvailable = false; - + private void checkAndSetConnectivityInstance() { if (mCm == null) { mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); } + } + + private boolean startTethering(ArrayList<String> available) { + + boolean wifiAvailable = false; + + checkAndSetConnectivityInstance(); String[] wifiRegexs = mCm.getTetherableWifiRegexs(); @@ -1052,18 +1085,43 @@ public class WifiStateMachine extends StateMachine { } catch (Exception e) { Log.e(TAG, "Error configuring interface " + intf + ", :" + e); setWifiApEnabled(null, false); - return; + return false; } if(mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { Log.e(TAG, "Error tethering on " + intf); setWifiApEnabled(null, false); - return; + return false; } - break; + return true; } } } + // We found no interfaces to tether + return false; + } + + private void stopTethering() { + + checkAndSetConnectivityInstance(); + + /* Clear the interface config to allow dhcp correctly configure new + ip settings */ + InterfaceConfiguration ifcg = null; + try { + ifcg = nwService.getInterfaceConfig(mInterfaceName); + if (ifcg != null) { + ifcg.addr = new LinkAddress(NetworkUtils.numericToInetAddress( + "0.0.0.0"), 0); + nwService.setInterfaceConfig(mInterfaceName, ifcg); + } + } catch (Exception e) { + Log.e(TAG, "Error resetting interface " + mInterfaceName + ", :" + e); + } + + if (mCm.untether(mInterfaceName) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { + Log.e(TAG, "Untether initiate failed!"); + } } /** @@ -1591,12 +1649,15 @@ public class WifiStateMachine extends StateMachine { if (currentStatus == SOFT_AP_STOPPED) { nwService.startAccessPoint(config, mInterfaceName, SOFTAP_IFACE); } else if (currentStatus == SOFT_AP_RUNNING) { - nwService.setAccessPoint(config, mInterfaceName, SOFTAP_IFACE); + //nwService.setAccessPoint(config, mInterfaceName, SOFTAP_IFACE); + //TODO: when we have a control channel to hostapd, we should not need to do this + nwService.stopAccessPoint(mInterfaceName); + nwService.startAccessPoint(config, mInterfaceName, SOFTAP_IFACE); } } catch (Exception e) { Log.e(TAG, "Exception in softap start " + e); try { - nwService.stopAccessPoint(); + nwService.stopAccessPoint(mInterfaceName); nwService.startAccessPoint(config, mInterfaceName, SOFTAP_IFACE); } catch (Exception ee) { Log.e(TAG, "Exception during softap restart : " + ee); @@ -1606,105 +1667,6 @@ public class WifiStateMachine extends StateMachine { return true; } - - /********************************************************* - * Notifications from WifiMonitor - ********************************************************/ - - /** - * Stores supplicant state change information passed from WifiMonitor - */ - static class StateChangeResult { - StateChangeResult(int networkId, String BSSID, SupplicantState state) { - this.state = state; - this.BSSID = BSSID; - this.networkId = networkId; - } - int networkId; - String BSSID; - SupplicantState state; - } - - /** - * Send the tracker a notification that a user provided - * configuration caused authentication failure - this could - * be a password failure or a EAP authentication failure - */ - void notifyAuthenticationFailure() { - sendMessage(AUTHENTICATION_FAILURE_EVENT); - } - - /** - * Send a notification that the supplicant has detected overlapped - * WPS sessions - */ - void notifyWpsOverlap() { - sendMessage(WPS_OVERLAP_EVENT); - } - - /** - * Send the tracker a notification that a connection to the supplicant - * daemon has been established. - */ - void notifySupplicantConnection() { - sendMessage(SUP_CONNECTION_EVENT); - } - - /** - * Send the tracker a notification that connection to the supplicant - * daemon is lost - */ - void notifySupplicantLost() { - sendMessage(SUP_DISCONNECTION_EVENT); - } - - /** - * Send the tracker a notification that the state of Wifi connectivity - * has changed. - * @param networkId the configured network on which the state change occurred - * @param newState the new network state - * @param BSSID when the new state is {@link DetailedState#CONNECTED - * NetworkInfo.DetailedState.CONNECTED}, - * this is the MAC address of the access point. Otherwise, it - * is {@code null}. - */ - void notifyNetworkStateChange(DetailedState newState, String BSSID, int networkId) { - if (newState == NetworkInfo.DetailedState.CONNECTED) { - sendMessage(obtainMessage(NETWORK_CONNECTION_EVENT, networkId, 0, BSSID)); - } else { - sendMessage(obtainMessage(NETWORK_DISCONNECTION_EVENT, networkId, 0, BSSID)); - } - } - - /** - * Send the tracker a notification that the state of the supplicant - * has changed. - * @param networkId the configured network on which the state change occurred - * @param newState the new {@code SupplicantState} - */ - void notifySupplicantStateChange(int networkId, String BSSID, SupplicantState newState) { - sendMessage(obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT, - new StateChangeResult(networkId, BSSID, newState))); - } - - /** - * Send the tracker a notification that a scan has completed, and results - * are available. - */ - void notifyScanResultsAvailable() { - /** - * Switch scan mode over to passive. - * Turning off scan-only mode happens only in "Connect" mode - */ - setScanType(false); - sendMessage(SCAN_RESULTS_EVENT); - } - - void notifyDriverHung() { - setWifiEnabled(false); - setWifiEnabled(true); - } - /******************************************************** * HSM states *******************************************************/ @@ -1714,6 +1676,18 @@ public class WifiStateMachine extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch (message.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + mWifiP2pChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + } else { + Log.e(TAG, "WifiP2pService connection failure, error=" + message.arg1); + } + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + Log.e(TAG, "WifiP2pService channel lost, message.arg1 =" + message.arg1); + //TODO: Re-establish connection to state machine after a delay + //mWifiP2pChannel.connect(mContext, getHandler(), mWifiP2pManager.getMessenger()); + break; case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE: mBluetoothConnectionActive = (message.arg1 != BluetoothAdapter.STATE_DISCONNECTED); @@ -1749,18 +1723,19 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_DRIVER: case CMD_START_AP: case CMD_STOP_AP: + case CMD_TETHER_INTERFACE: case CMD_START_SCAN: case CMD_DISCONNECT: case CMD_RECONNECT: case CMD_REASSOCIATE: - case SUP_CONNECTION_EVENT: - case SUP_DISCONNECTION_EVENT: - case NETWORK_CONNECTION_EVENT: - case NETWORK_DISCONNECTION_EVENT: - case SCAN_RESULTS_EVENT: - case SUPPLICANT_STATE_CHANGE_EVENT: - case AUTHENTICATION_FAILURE_EVENT: - case WPS_OVERLAP_EVENT: + case WifiMonitor.SUP_CONNECTION_EVENT: + case WifiMonitor.SUP_DISCONNECTION_EVENT: + case WifiMonitor.NETWORK_CONNECTION_EVENT: + case WifiMonitor.NETWORK_DISCONNECTION_EVENT: + case WifiMonitor.SCAN_RESULTS_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: + case WifiMonitor.WPS_OVERLAP_EVENT: case CMD_BLACKLIST_NETWORK: case CMD_CLEAR_BLACKLIST: case CMD_SET_SCAN_MODE: @@ -1777,11 +1752,20 @@ public class WifiStateMachine extends StateMachine { case DhcpStateMachine.CMD_PRE_DHCP_ACTION: case DhcpStateMachine.CMD_POST_DHCP_ACTION: break; + case WifiMonitor.DRIVER_HUNG_EVENT: + setWifiEnabled(false); + setWifiEnabled(true); + break; case CMD_START_WPS: /* Return failure when the state machine cannot handle WPS initiation*/ mReplyChannel.replyToMessage(message, WifiManager.CMD_WPS_COMPLETED, new WpsResult(Status.FAILURE)); break; + case WifiP2pService.P2P_ENABLE_PENDING: + // turn off wifi and defer to be handled in DriverUnloadedState + setWifiEnabled(false); + deferMessage(message); + break; default: Log.e(TAG, "Error! unhandled message" + message); break; @@ -1808,6 +1792,11 @@ public class WifiStateMachine extends StateMachine { else { transitionTo(mDriverUnloadedState); } + + //Connect to WifiP2pService + mWifiP2pManager = (WifiP2pManager) mContext.getSystemService(Context.WIFI_P2P_SERVICE); + mWifiP2pChannel.connect(mContext, getHandler(), mWifiP2pManager.getMessenger()); + } } @@ -2023,7 +2012,11 @@ public class WifiStateMachine extends StateMachine { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch (message.what) { case CMD_LOAD_DRIVER: - transitionTo(mDriverLoadingState); + mWifiP2pChannel.sendMessage(WIFI_ENABLE_PENDING); + transitionTo(mWaitForP2pDisableState); + break; + case WifiP2pService.P2P_ENABLE_PENDING: + mReplyChannel.replyToMessage(message, P2P_ENABLE_PROCEED); break; default: return NOT_HANDLED; @@ -2057,7 +2050,7 @@ public class WifiStateMachine extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch(message.what) { - case SUP_CONNECTION_EVENT: + case WifiMonitor.SUP_CONNECTION_EVENT: Log.d(TAG, "Supplicant connection established"); setWifiState(WIFI_STATE_ENABLED); mSupplicantRestartCount = 0; @@ -2074,13 +2067,10 @@ public class WifiStateMachine extends StateMachine { WifiConfigStore.initialize(mContext); - //TODO: initialize and fix multicast filtering - //mWM.initializeMulticastFiltering(); - sendSupplicantConnectionChangedBroadcast(true); transitionTo(mDriverStartedState); break; - case SUP_DISCONNECTION_EVENT: + case WifiMonitor.SUP_DISCONNECTION_EVENT: if (++mSupplicantRestartCount <= SUPPLICANT_RESTART_TRIES) { Log.e(TAG, "Failed to setup control channel, restart supplicant"); WifiNative.killSupplicant(); @@ -2143,7 +2133,7 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_SUPPLICANT: /* Supplicant stopped by user */ transitionTo(mSupplicantStoppingState); break; - case SUP_DISCONNECTION_EVENT: /* Supplicant connection lost */ + case WifiMonitor.SUP_DISCONNECTION_EVENT: /* Supplicant connection lost */ Log.e(TAG, "Connection lost, restart supplicant"); WifiNative.killSupplicant(); WifiNative.closeSupplicantConnection(); @@ -2155,7 +2145,7 @@ public class WifiStateMachine extends StateMachine { transitionTo(mDriverLoadedState); sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS); break; - case SCAN_RESULTS_EVENT: + case WifiMonitor.SCAN_RESULTS_EVENT: eventLoggingEnabled = false; setScanResults(WifiNative.scanResultsCommand()); sendScanResultsAvailableBroadcast(); @@ -2257,10 +2247,10 @@ public class WifiStateMachine extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch(message.what) { - case SUP_CONNECTION_EVENT: + case WifiMonitor.SUP_CONNECTION_EVENT: Log.e(TAG, "Supplicant connection received while stopping"); break; - case SUP_DISCONNECTION_EVENT: + case WifiMonitor.SUP_DISCONNECTION_EVENT: Log.d(TAG, "Supplicant connection lost"); WifiNative.closeSupplicantConnection(); transitionTo(mDriverLoadedState); @@ -2300,7 +2290,7 @@ public class WifiStateMachine extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch(message.what) { - case SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: SupplicantState state = handleSupplicantStateChange(message); /* If suplicant is exiting out of INTERFACE_DISABLED state into * a state that indicates driver has started, it is ready to @@ -2313,10 +2303,10 @@ public class WifiStateMachine extends StateMachine { /* Queue driver commands & connection events */ case CMD_START_DRIVER: case CMD_STOP_DRIVER: - case NETWORK_CONNECTION_EVENT: - case NETWORK_DISCONNECTION_EVENT: - case AUTHENTICATION_FAILURE_EVENT: - case WPS_OVERLAP_EVENT: + case WifiMonitor.NETWORK_CONNECTION_EVENT: + case WifiMonitor.NETWORK_DISCONNECTION_EVENT: + case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: + case WifiMonitor.WPS_OVERLAP_EVENT: case CMD_SET_SCAN_TYPE: case CMD_SET_HIGH_PERF_MODE: case CMD_SET_COUNTRY_CODE: @@ -2359,6 +2349,16 @@ public class WifiStateMachine extends StateMachine { /* initialize network state */ setNetworkDetailedState(DetailedState.DISCONNECTED); + /* Remove any filtering on Multicast v6 at start */ + WifiNative.stopFilteringMulticastV6Packets(); + + /* Reset Multicast v4 filtering state */ + if (mFilteringMulticastV4Packets.get()) { + WifiNative.startFilteringMulticastV4Packets(); + } else { + WifiNative.stopFilteringMulticastV4Packets(); + } + if (mIsScanMode) { WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE); WifiNative.disconnectCommand(); @@ -2419,10 +2419,22 @@ public class WifiStateMachine extends StateMachine { mWakeLock.release(); break; case CMD_START_PACKET_FILTERING: - WifiNative.startPacketFiltering(); + if (message.arg1 == MULTICAST_V6) { + WifiNative.startFilteringMulticastV6Packets(); + } else if (message.arg1 == MULTICAST_V4) { + WifiNative.startFilteringMulticastV4Packets(); + } else { + Log.e(TAG, "Illegal arugments to CMD_START_PACKET_FILTERING"); + } break; case CMD_STOP_PACKET_FILTERING: - WifiNative.stopPacketFiltering(); + if (message.arg1 == MULTICAST_V6) { + WifiNative.stopFilteringMulticastV6Packets(); + } else if (message.arg1 == MULTICAST_V4) { + WifiNative.stopFilteringMulticastV4Packets(); + } else { + Log.e(TAG, "Illegal arugments to CMD_STOP_PACKET_FILTERING"); + } break; default: return NOT_HANDLED; @@ -2451,7 +2463,7 @@ public class WifiStateMachine extends StateMachine { public boolean processMessage(Message message) { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch(message.what) { - case SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: SupplicantState state = handleSupplicantStateChange(message); if (state == SupplicantState.INTERFACE_DISABLED) { transitionTo(mDriverStoppedState); @@ -2495,7 +2507,7 @@ public class WifiStateMachine extends StateMachine { WifiNative.startDriverCommand(); mWakeLock.release(); break; - case SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: SupplicantState state = handleSupplicantStateChange(message); /* A driver start causes supplicant to first report an INTERFACE_DISABLED * state before transitioning out of it for connection. Stay in @@ -2541,9 +2553,9 @@ public class WifiStateMachine extends StateMachine { case CMD_DISCONNECT: case CMD_RECONNECT: case CMD_REASSOCIATE: - case SUPPLICANT_STATE_CHANGE_EVENT: - case NETWORK_CONNECTION_EVENT: - case NETWORK_DISCONNECTION_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.NETWORK_CONNECTION_EVENT: + case WifiMonitor.NETWORK_DISCONNECTION_EVENT: break; default: return NOT_HANDLED; @@ -2564,14 +2576,14 @@ public class WifiStateMachine extends StateMachine { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); StateChangeResult stateChangeResult; switch(message.what) { - case AUTHENTICATION_FAILURE_EVENT: - mSupplicantStateTracker.sendMessage(AUTHENTICATION_FAILURE_EVENT); + case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: + mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT); break; - case WPS_OVERLAP_EVENT: + case WifiMonitor.WPS_OVERLAP_EVENT: /* We just need to broadcast the error */ sendErrorBroadcast(WifiManager.WPS_OVERLAP_ERROR); break; - case SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: handleSupplicantStateChange(message); break; /* Do a redundant disconnect without transition */ @@ -2613,12 +2625,12 @@ public class WifiStateMachine extends StateMachine { mWpsStateMachine.sendMessage(Message.obtain(message)); transitionTo(mWaitForWpsCompletionState); break; - case SCAN_RESULTS_EVENT: + case WifiMonitor.SCAN_RESULTS_EVENT: /* Set the scan setting back to "connect" mode */ WifiNative.setScanResultHandlingCommand(CONNECT_MODE); /* Handle scan results */ return NOT_HANDLED; - case NETWORK_CONNECTION_EVENT: + case WifiMonitor.NETWORK_CONNECTION_EVENT: Log.d(TAG,"Network connection established"); mLastNetworkId = message.arg1; mLastBssid = (String) message.obj; @@ -2632,7 +2644,7 @@ public class WifiStateMachine extends StateMachine { sendNetworkStateChangeBroadcast(mLastBssid); transitionTo(mConnectingState); break; - case NETWORK_DISCONNECTION_EVENT: + case WifiMonitor.NETWORK_DISCONNECTION_EVENT: Log.d(TAG,"Network connection lost"); handleNetworkDisconnect(); transitionTo(mDisconnectedState); @@ -2719,7 +2731,7 @@ public class WifiStateMachine extends StateMachine { deferMessage(message); break; /* Ignore */ - case NETWORK_CONNECTION_EVENT: + case WifiMonitor.NETWORK_CONNECTION_EVENT: break; case CMD_STOP_DRIVER: sendMessage(CMD_DISCONNECT); @@ -2784,10 +2796,7 @@ public class WifiStateMachine extends StateMachine { deferMessage(message); break; case CMD_REQUEST_CM_WAKELOCK: - if (mCm == null) { - mCm = (ConnectivityManager)mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - } + checkAndSetConnectivityInstance(); mCm.requestNetworkTransitionWakelock(TAG); break; case CMD_SET_SCAN_MODE: @@ -2829,7 +2838,7 @@ public class WifiStateMachine extends StateMachine { } break; /* Ignore */ - case NETWORK_CONNECTION_EVENT: + case WifiMonitor.NETWORK_CONNECTION_EVENT: break; case CMD_RSSI_POLL: eventLoggingEnabled = false; @@ -2890,7 +2899,7 @@ public class WifiStateMachine extends StateMachine { } break; /* Handle in DisconnectedState */ - case SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: deferMessage(message); break; default: @@ -2975,9 +2984,9 @@ public class WifiStateMachine extends StateMachine { } break; /* Ignore network disconnect */ - case NETWORK_DISCONNECTION_EVENT: + case WifiMonitor.NETWORK_DISCONNECTION_EVENT: break; - case SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: StateChangeResult stateChangeResult = (StateChangeResult) message.obj; setNetworkDetailedState(WifiInfo.getDetailedStateOf(stateChangeResult.state)); /* ConnectModeState does the rest of the handling */ @@ -2989,7 +2998,7 @@ public class WifiStateMachine extends StateMachine { } /* Handled in parent state */ return NOT_HANDLED; - case SCAN_RESULTS_EVENT: + case WifiMonitor.SCAN_RESULTS_EVENT: /* Re-enable background scan when a pending scan result is received */ if (mEnableBackgroundScan && mScanResultIsPending) { WifiNative.enableBackgroundScanCommand(true); @@ -3032,10 +3041,10 @@ public class WifiStateMachine extends StateMachine { case CMD_ENABLE_NETWORK: case CMD_RECONNECT: case CMD_REASSOCIATE: - case NETWORK_CONNECTION_EVENT: /* Handled after IP & proxy update */ + case WifiMonitor.NETWORK_CONNECTION_EVENT: /* Handled after IP & proxy update */ deferMessage(message); break; - case NETWORK_DISCONNECTION_EVENT: + case WifiMonitor.NETWORK_DISCONNECTION_EVENT: Log.d(TAG,"Network connection lost"); handleNetworkDisconnect(); break; @@ -3065,16 +3074,9 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_AP: Log.d(TAG,"Stopping Soft AP"); setWifiApState(WIFI_AP_STATE_DISABLING); - - if (mCm == null) { - mCm = (ConnectivityManager) mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - } - if (mCm.untether(SOFTAP_IFACE) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { - Log.e(TAG, "Untether initiate failed!"); - } + stopTethering(); try { - nwService.stopAccessPoint(); + nwService.stopAccessPoint(mInterfaceName); } catch(Exception e) { Log.e(TAG, "Exception in stopAccessPoint()"); } @@ -3096,6 +3098,17 @@ public class WifiStateMachine extends StateMachine { Log.e(TAG,"Cannot start supplicant with a running soft AP"); setWifiState(WIFI_STATE_UNKNOWN); break; + case CMD_TETHER_INTERFACE: + ArrayList<String> available = (ArrayList<String>) message.obj; + if (startTethering(available)) { + transitionTo(mTetheredState); + } + break; + case WifiP2pService.P2P_ENABLE_PENDING: + // turn of soft Ap and defer to be handled in DriverUnloadedState + setWifiApEnabled(null, false); + deferMessage(message); + break; default: return NOT_HANDLED; } @@ -3103,4 +3116,68 @@ public class WifiStateMachine extends StateMachine { return HANDLED; } } + + class WaitForP2pDisableState extends State { + private int mSavedArg; + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + + //Preserve the argument arg1 that has information used in DriverLoadingState + mSavedArg = getCurrentMessage().arg1; + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch(message.what) { + case WifiP2pService.WIFI_ENABLE_PROCEED: + //restore argument from original message (CMD_LOAD_DRIVER) + message.arg1 = mSavedArg; + transitionTo(mDriverLoadingState); + break; + case CMD_LOAD_DRIVER: + case CMD_UNLOAD_DRIVER: + case CMD_START_SUPPLICANT: + case CMD_STOP_SUPPLICANT: + case CMD_START_AP: + case CMD_STOP_AP: + case CMD_START_DRIVER: + case CMD_STOP_DRIVER: + case CMD_SET_SCAN_MODE: + case CMD_SET_SCAN_TYPE: + case CMD_SET_HIGH_PERF_MODE: + case CMD_SET_COUNTRY_CODE: + case CMD_SET_FREQUENCY_BAND: + case CMD_START_PACKET_FILTERING: + case CMD_STOP_PACKET_FILTERING: + deferMessage(message); + break; + default: + return NOT_HANDLED; + } + EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what); + return HANDLED; + } + } + + class TetheredState extends State { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch(message.what) { + case CMD_TETHER_INTERFACE: + // Ignore any duplicate interface available notifications + // when in tethered state + return HANDLED; + default: + return NOT_HANDLED; + } + } + } } diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java index b09e04b04c5f..fa7cf21627bf 100644 --- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java +++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java @@ -68,7 +68,7 @@ import java.util.regex.Pattern; public class WifiWatchdogStateMachine extends StateMachine { - private static final boolean VDBG = true; //TODO : Remove this before merge + private static final boolean VDBG = false; private static final boolean DBG = true; private static final String WWSM_TAG = "WifiWatchdogStateMachine"; diff --git a/wifi/java/android/net/wifi/WpsConfiguration.java b/wifi/java/android/net/wifi/WpsConfiguration.java index 12d951fcea05..2e7689a5676d 100644 --- a/wifi/java/android/net/wifi/WpsConfiguration.java +++ b/wifi/java/android/net/wifi/WpsConfiguration.java @@ -30,13 +30,16 @@ import java.util.BitSet; */ public class WpsConfiguration implements Parcelable { + /* Wi-Fi Protected Setup. www.wi-fi.org/wifi-protected-setup has details */ public enum Setup { - /* Wi-Fi protected setup push button configuration */ + /* Push button configuration */ PBC, - /* Wi-Fi protected setup pin method configuration with pin obtained from access point */ - PIN_FROM_ACCESS_POINT, - /* Wi-Fi protected setup pin method configuration with pin obtained from device */ - PIN_FROM_DEVICE, + /* Display pin method configuration - pin is generated and displayed on device */ + DISPLAY, + /* Keypad pin method configuration - pin is entered on device */ + KEYPAD, + /* Label pin method configuration - pin is obtained from a printed label */ + LABEL, /* Invalid config */ INVALID } diff --git a/wifi/java/android/net/wifi/WpsStateMachine.java b/wifi/java/android/net/wifi/WpsStateMachine.java index 120b22886de7..af089ab91115 100644 --- a/wifi/java/android/net/wifi/WpsStateMachine.java +++ b/wifi/java/android/net/wifi/WpsStateMachine.java @@ -22,7 +22,7 @@ import com.android.internal.util.StateMachine; import android.content.Context; import android.content.Intent; -import android.net.wifi.WifiStateMachine.StateChangeResult; +import android.net.wifi.StateChangeResult; import android.net.wifi.WpsResult.Status; import android.os.Handler; import android.os.Message; @@ -99,10 +99,10 @@ class WpsStateMachine extends StateMachine { case PBC: result = WifiConfigStore.startWpsPbc(mWpsConfig); break; - case PIN_FROM_ACCESS_POINT: + case KEYPAD: result = WifiConfigStore.startWpsWithPinFromAccessPoint(mWpsConfig); break; - case PIN_FROM_DEVICE: + case DISPLAY: result = WifiConfigStore.startWpsWithPinFromDevice(mWpsConfig); break; default: @@ -139,7 +139,7 @@ class WpsStateMachine extends StateMachine { boolean retValue = HANDLED; if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch (message.what) { - case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: StateChangeResult stateChangeResult = (StateChangeResult) message.obj; SupplicantState supState = (SupplicantState) stateChangeResult.state; switch (supState) { @@ -194,7 +194,7 @@ class WpsStateMachine extends StateMachine { if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); switch (message.what) { //Ignore supplicant state changes - case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: + case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: break; default: retValue = NOT_HANDLED; diff --git a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl new file mode 100644 index 000000000000..a0c7dd145051 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2008, 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.wifi.p2p; + +import android.os.Messenger; + +/** + * Interface that WifiP2pService implements + * + * {@hide} + */ +interface IWifiP2pManager +{ + Messenger getMessenger(); + boolean isP2pSupported(); +} + diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.aidl b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.aidl new file mode 100644 index 000000000000..ea3b2803d514 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2011, 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.wifi.p2p; + +parcelable WifiP2pConfig; diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java new file mode 100644 index 000000000000..fff5ee38305a --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2011 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.wifi.p2p; + +import android.net.wifi.WpsConfiguration; +import android.net.wifi.WpsConfiguration.Setup; +import android.os.Parcelable; +import android.os.Parcel; + +/** + * A class representing a Wi-Fi P2p configuration + * @hide + */ +public class WifiP2pConfig implements Parcelable { + + /** + * Device name + */ + public String deviceName; + + /** + * Device address + */ + public String deviceAddress; + + /** + * WPS configuration + */ + public WpsConfiguration wpsConfig; + + /** + * This is an integer value between 0 and 15 where 0 indicates the least + * inclination to be a group owner and 15 indicates the highest inclination + * to be a group owner. + */ + public int groupOwnerIntent; + + public boolean isPersistent; + + public boolean joinExistingGroup; + + /** + * Channel frequency in MHz + */ + public int channel; + + public WifiP2pConfig() { + //set defaults + wpsConfig = new WpsConfiguration(); + wpsConfig.setup = Setup.PBC; + } + + /* P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 */ + public WifiP2pConfig(String supplicantEvent) throws IllegalArgumentException { + String[] tokens = supplicantEvent.split(" "); + + if (tokens.length < 2 || !tokens[0].equals("P2P-GO-NEG-REQUEST")) { + throw new IllegalArgumentException("Malformed supplicant event"); + } + + deviceAddress = tokens[1]; + wpsConfig = new WpsConfiguration(); + + if (tokens.length > 2) { + String[] nameVal = tokens[2].split("="); + int devPasswdId; + try { + devPasswdId = Integer.parseInt(nameVal[1]); + } catch (NumberFormatException e) { + devPasswdId = 0; + } + //As defined in wps/wps_defs.h + switch (devPasswdId) { + case 0x00: + wpsConfig.setup = Setup.LABEL; + break; + case 0x01: + wpsConfig.setup = Setup.KEYPAD; + break; + case 0x04: + wpsConfig.setup = Setup.PBC; + break; + case 0x05: + wpsConfig.setup = Setup.DISPLAY; + break; + default: + wpsConfig.setup = Setup.PBC; + break; + } + } + } + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append("Device: ").append(deviceName); + sbuf.append("\n address: ").append(deviceAddress); + sbuf.append("\n wps: ").append(wpsConfig); + sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent); + sbuf.append("\n isPersistent: ").append(isPersistent); + sbuf.append("\n joinExistingGroup: ").append(joinExistingGroup); + sbuf.append("\n channel: ").append(channel); + return sbuf.toString(); + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** copy constructor {@hide} */ + public WifiP2pConfig(WifiP2pConfig source) { + if (source != null) { + //TODO: implement + } + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(deviceName); + dest.writeString(deviceAddress); + dest.writeParcelable(wpsConfig, flags); + dest.writeInt(groupOwnerIntent); + dest.writeInt(isPersistent ? 1 : 0); + dest.writeInt(joinExistingGroup ? 1 : 0); + dest.writeInt(channel); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiP2pConfig> CREATOR = + new Creator<WifiP2pConfig>() { + public WifiP2pConfig createFromParcel(Parcel in) { + WifiP2pConfig config = new WifiP2pConfig(); + config.deviceName = in.readString(); + config.deviceAddress = in.readString(); + config.wpsConfig = (WpsConfiguration) in.readParcelable(null); + config.groupOwnerIntent = in.readInt(); + config.isPersistent = (in.readInt() == 1); + config.joinExistingGroup = (in.readInt() == 1); + config.channel = in.readInt(); + return config; + } + + public WifiP2pConfig[] newArray(int size) { + return new WifiP2pConfig[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.aidl b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.aidl new file mode 100644 index 000000000000..8790c6ffe903 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2011, 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.wifi.p2p; + +parcelable WifiP2pDevice; diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java new file mode 100644 index 000000000000..83dc2856f4a4 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2011 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.wifi.p2p; + +import android.os.Parcelable; +import android.os.Parcel; +import android.util.Log; + +import java.util.regex.Pattern; + +/** + * A class representing a Wi-Fi p2p device + * @hide + */ +public class WifiP2pDevice implements Parcelable { + + private static final String TAG = "WifiP2pDevice"; + /** + * Device name + */ + public String deviceName; + + /** + * Device MAC address + */ + public String deviceAddress; + + /** + * interfaceAddress + * + * This address is used during group owner negotiation as the Intended + * P2P Interface Address and the group interface will be created with + * address as the local address in case of successfully completed + * negotiation. + */ + public String interfaceAddress; + + /** + * Primary device type + */ + public String primaryDeviceType; + + /** + * Secondary device type + */ + public String secondaryDeviceType; + + + // These definitions match the ones in wpa_supplicant + /* WPS config methods supported */ + private static final int WPS_CONFIG_USBA = 0x0001; + private static final int WPS_CONFIG_ETHERNET = 0x0002; + private static final int WPS_CONFIG_LABEL = 0x0004; + private static final int WPS_CONFIG_DISPLAY = 0x0008; + private static final int WPS_CONFIG_EXT_NFC_TOKEN = 0x0010; + private static final int WPS_CONFIG_INT_NFC_TOKEN = 0x0020; + private static final int WPS_CONFIG_NFC_INTERFACE = 0x0040; + private static final int WPS_CONFIG_PUSHBUTTON = 0x0080; + private static final int WPS_CONFIG_KEYPAD = 0x0100; + private static final int WPS_CONFIG_VIRT_PUSHBUTTON = 0x0280; + private static final int WPS_CONFIG_PHY_PUSHBUTTON = 0x0480; + private static final int WPS_CONFIG_VIRT_DISPLAY = 0x2008; + private static final int WPS_CONFIG_PHY_DISPLAY = 0x4008; + + /* Device Capability bitmap */ + private static final int DEVICE_CAPAB_SERVICE_DISCOVERY = 1; + private static final int DEVICE_CAPAB_CLIENT_DISCOVERABILITY = 1<<1; + private static final int DEVICE_CAPAB_CONCURRENT_OPER = 1<<2; + private static final int DEVICE_CAPAB_INFRA_MANAGED = 1<<3; + private static final int DEVICE_CAPAB_DEVICE_LIMIT = 1<<4; + private static final int DEVICE_CAPAB_INVITATION_PROCEDURE = 1<<5; + + /* Group Capability bitmap */ + private static final int GROUP_CAPAB_GROUP_OWNER = 1; + private static final int GROUP_CAPAB_PERSISTENT_GROUP = 1<<1; + private static final int GROUP_CAPAB_GROUP_LIMIT = 1<<2; + private static final int GROUP_CAPAB_INTRA_BSS_DIST = 1<<3; + private static final int GROUP_CAPAB_CROSS_CONN = 1<<4; + private static final int GROUP_CAPAB_PERSISTENT_RECONN = 1<<5; + private static final int GROUP_CAPAB_GROUP_FORMATION = 1<<6; + + /** + * WPS config methods supported + */ + public int wpsConfigMethodsSupported; + + /** + * Device capability + */ + public int deviceCapability; + + /** + * Group capability + */ + public int groupCapability; + + public enum Status { + CONNECTED, + INVITED, + FAILED, + AVAILABLE, + UNAVAILABLE, + } + + public Status status = Status.UNAVAILABLE; + + public WifiP2pDevice() { + } + + /** + * @param string formats supported include + * P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13 + * pri_dev_type=1-0050F204-1 name='p2p-TEST1' config_methods=0x188 dev_capab=0x27 + * group_capab=0x0 + * + * P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13 + * + * fa:7b:7a:42:02:13 + * + * P2P-PROV-DISC-PBC-REQ 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27 + * pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 + * group_capab=0x0 + * + * P2P-PROV-DISC-ENTER-PIN 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27 + * pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 + * group_capab=0x0 + * + * P2P-PROV-DISC-SHOW-PIN 42:fc:89:e1:e2:27 44490607 p2p_dev_addr=42:fc:89:e1:e2:27 + * pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27 + * group_capab=0x0 + * + * Note: The events formats can be looked up in the wpa_supplicant code + */ + public WifiP2pDevice(String string) throws IllegalArgumentException { + String[] tokens = string.split(" "); + + if (tokens.length < 1) { + throw new IllegalArgumentException("Malformed supplicant event"); + } + + /* Just a device address */ + if (tokens.length == 1) { + deviceAddress = string; + return; + } + + Pattern p = Pattern.compile("(?:[0-9a-f]{2}:){5}[0-9a-f]{2}", Pattern.CASE_INSENSITIVE); + if (p.matcher(tokens[1]).matches()) interfaceAddress = tokens[1]; + + for (String token : tokens) { + String[] nameValue = token.split("="); + if (nameValue.length != 2) continue; + + if (nameValue[0].equals("p2p_dev_addr")) { + deviceAddress = nameValue[1]; + continue; + } + + if (nameValue[0].equals("pri_dev_type")) { + primaryDeviceType = nameValue[1]; + continue; + } + + if (nameValue[0].equals("name")) { + deviceName = trimQuotes(nameValue[1]); + } + + if (nameValue[0].equals("config_methods")) { + wpsConfigMethodsSupported = parseHex(nameValue[1]); + continue; + } + + if (nameValue[0].equals("dev_capab")) { + deviceCapability = parseHex(nameValue[1]); + continue; + } + + if (nameValue[0].equals("group_capab")) { + groupCapability = parseHex(nameValue[1]); + continue; + } + } + + if (tokens[0].startsWith("P2P-DEVICE-FOUND")) { + status = Status.AVAILABLE; + } + } + + public boolean isGroupOwner() { + return (groupCapability & GROUP_CAPAB_GROUP_OWNER) != 0; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof WifiP2pDevice)) return false; + + WifiP2pDevice other = (WifiP2pDevice) obj; + if (other == null || other.deviceAddress == null) { + return (deviceAddress == null); + } + //STOPSHIP: fix later + //return other.deviceAddress.equals(deviceAddress); + return other.deviceAddress.startsWith(deviceAddress.substring(0,8)); + } + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append("Device: ").append(deviceName); + sbuf.append("\n deviceAddress: ").append(deviceAddress); + sbuf.append("\n interfaceAddress: ").append(interfaceAddress); + sbuf.append("\n primary type: ").append(primaryDeviceType); + sbuf.append("\n secondary type: ").append(secondaryDeviceType); + sbuf.append("\n wps: ").append(wpsConfigMethodsSupported); + sbuf.append("\n grpcapab: ").append(groupCapability); + sbuf.append("\n devcapab: ").append(deviceCapability); + sbuf.append("\n status: ").append(status); + return sbuf.toString(); + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** copy constructor {@hide} */ + public WifiP2pDevice(WifiP2pDevice source) { + if (source != null) { + deviceName = source.deviceName; + deviceAddress = source.deviceAddress; + interfaceAddress = source.interfaceAddress; + primaryDeviceType = source.primaryDeviceType; + secondaryDeviceType = source.secondaryDeviceType; + wpsConfigMethodsSupported = source.wpsConfigMethodsSupported; + deviceCapability = source.deviceCapability; + groupCapability = source.groupCapability; + status = source.status; + } + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(deviceName); + dest.writeString(deviceAddress); + dest.writeString(interfaceAddress); + dest.writeString(primaryDeviceType); + dest.writeString(secondaryDeviceType); + dest.writeInt(wpsConfigMethodsSupported); + dest.writeInt(deviceCapability); + dest.writeInt(groupCapability); + dest.writeString(status.name()); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiP2pDevice> CREATOR = + new Creator<WifiP2pDevice>() { + public WifiP2pDevice createFromParcel(Parcel in) { + WifiP2pDevice device = new WifiP2pDevice(); + device.deviceName = in.readString(); + device.deviceAddress = in.readString(); + device.interfaceAddress = in.readString(); + device.primaryDeviceType = in.readString(); + device.secondaryDeviceType = in.readString(); + device.wpsConfigMethodsSupported = in.readInt(); + device.deviceCapability = in.readInt(); + device.groupCapability = in.readInt(); + device.status = Status.valueOf(in.readString()); + return device; + } + + public WifiP2pDevice[] newArray(int size) { + return new WifiP2pDevice[size]; + } + }; + + private String trimQuotes(String str) { + str = str.trim(); + if (str.startsWith("'") && str.endsWith("'")) { + return str.substring(1, str.length()-1); + } + return str; + } + + //supported formats: 0x1abc, 0X1abc, 1abc + private int parseHex(String hexString) { + int num = 0; + if (hexString.startsWith("0x") || hexString.startsWith("0X")) { + hexString = hexString.substring(2); + } + + try { + num = Integer.parseInt(hexString, 16); + } catch(NumberFormatException e) { + Log.e(TAG, "Failed to parse hex string " + hexString); + } + return num; + } +} diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.aidl b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.aidl new file mode 100644 index 000000000000..6c79009315b5 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2011, 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.wifi.p2p; + +parcelable WifiP2pDeviceList; diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java new file mode 100644 index 000000000000..4ec23b8c17a6 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2011 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.wifi.p2p; + +import android.os.Parcelable; +import android.os.Parcel; +import android.net.wifi.p2p.WifiP2pDevice; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +/** + * A class representing a Wi-Fi P2p device list + * @hide + */ +public class WifiP2pDeviceList implements Parcelable { + + private Collection<WifiP2pDevice> mDevices; + + public WifiP2pDeviceList() { + mDevices = new ArrayList<WifiP2pDevice>(); + } + + //copy constructor + public WifiP2pDeviceList(WifiP2pDeviceList source) { + if (source != null) { + mDevices = source.getDeviceList(); + } + } + + public WifiP2pDeviceList(ArrayList<WifiP2pDevice> devices) { + mDevices = new ArrayList<WifiP2pDevice>(); + for (WifiP2pDevice device : devices) { + mDevices.add(device); + } + } + + public void clear() { + mDevices.clear(); + } + + public void add(WifiP2pDevice device) { + if (device == null) return; + if (mDevices.contains(device)) return; + mDevices.add(device); + } + + public boolean remove(WifiP2pDevice device) { + if (device == null) return false; + return mDevices.remove(device); + } + + public Collection<WifiP2pDevice> getDeviceList() { + return Collections.unmodifiableCollection(mDevices); + } + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + for (WifiP2pDevice device : mDevices) { + sbuf.append("\n").append(device); + } + return sbuf.toString(); + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDevices.size()); + for(WifiP2pDevice device : mDevices) { + dest.writeParcelable(device, flags); + } + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiP2pDeviceList> CREATOR = + new Creator<WifiP2pDeviceList>() { + public WifiP2pDeviceList createFromParcel(Parcel in) { + WifiP2pDeviceList deviceList = new WifiP2pDeviceList(); + + int deviceCount = in.readInt(); + for (int i = 0; i < deviceCount; i++) { + deviceList.add((WifiP2pDevice)in.readParcelable(null)); + } + return deviceList; + } + + public WifiP2pDeviceList[] newArray(int size) { + return new WifiP2pDeviceList[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.aidl b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.aidl new file mode 100644 index 000000000000..403f2b19bfda --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2011, 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.wifi.p2p; + +parcelable WifiP2pGroup; diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java new file mode 100644 index 000000000000..ca6e4d5122d6 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2011 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.wifi.p2p; + +import android.os.Parcelable; +import android.os.Parcel; + +import java.util.ArrayList; +import java.util.List; +import java.util.Collection; +import java.util.Collections; + +/** + * A class representing a Wi-Fi P2p group + * @hide + */ +public class WifiP2pGroup implements Parcelable { + + /** The network name */ + private String mNetworkName; + + /** The network bssid */ + private String mNetworkBssid; + + /** Group owner */ + private WifiP2pDevice mOwner; + + /** Device is group owner */ + private boolean mIsGroupOwner; + + /** Group clients */ + private List<WifiP2pDevice> mClients = new ArrayList<WifiP2pDevice>(); + + private int mChannel; + + /** + * The network passphrase + * <p/> + * The passphrase used for WPA2-PSK + */ + private String mPassphrase; + + /** + * TODO: fix + * Sometimes supplicant sends a psk + */ + private String mPsk; + + /** Indicates that the group is persistent */ + private boolean mIsPersistent; + + private String mInterface; + + public WifiP2pGroup() { + } + + /** + * @param string formats supported include + * + * P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437 + * [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc| + * passphrase="fKG4jMe3"] go_dev_addr=fa:7b:7a:42:02:13 + * + * P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED + * + * P2P-INVITATION-RECEIVED sa=fa:7b:7a:42:02:13 go_dev_addr=f8:7b:7a:42:02:13 + * bssid=fa:7b:7a:42:82:13 unknown-network + * + * Note: The events formats can be looked up in the wpa_supplicant code + */ + public WifiP2pGroup(String supplicantEvent) throws IllegalArgumentException { + + String[] tokens = supplicantEvent.split(" "); + + if (tokens.length < 3) { + throw new IllegalArgumentException("Malformed supplicant event"); + } + + if (tokens[0].startsWith("P2P-GROUP")) { + mInterface = tokens[1]; + mIsGroupOwner = tokens[2].equals("GO"); + + for (String token : tokens) { + String[] nameValue = token.split("="); + if (nameValue.length != 2) continue; + + if (nameValue[0].equals("ssid")) { + mNetworkName = nameValue[1]; + continue; + } + + if (nameValue[0].equals("freq")) { + try { + mChannel = Integer.parseInt(nameValue[1]); + } catch (NumberFormatException e) { + mChannel = 0; //invalid + } + continue; + } + + if (nameValue[0].equals("psk")) { + mPsk = nameValue[1]; + continue; + } + + if (nameValue[0].equals("passphrase")) { + mPassphrase = nameValue[1]; + continue; + } + + if (nameValue[0].equals("go_dev_addr")) { + mOwner = new WifiP2pDevice(nameValue[1]); + } + } + } else if (tokens[0].equals("P2P-INVITATION-RECEIVED")) { + for (String token : tokens) { + String[] nameValue = token.split("="); + if (nameValue.length != 2) continue; + + if (nameValue[0].equals("go_dev_addr")) { + mOwner = new WifiP2pDevice(nameValue[1]); + continue; + } + + if (nameValue[0].equals("bssid")) { + mNetworkBssid = nameValue[1]; + } + } + } else { + throw new IllegalArgumentException("Malformed supplicant event"); + } + } + + public boolean isGroupOwner() { + return mIsGroupOwner; + } + + public WifiP2pDevice getOwner() { + return mOwner; + } + + public void addClient(String address) { + addClient(new WifiP2pDevice(address)); + } + + public void addClient(WifiP2pDevice device) { + for (WifiP2pDevice client : mClients) { + if (client.equals(device)) return; + } + mClients.add(device); + } + + public boolean removeClient(String address) { + return mClients.remove(new WifiP2pDevice(address)); + } + + public boolean removeClient(WifiP2pDevice device) { + return mClients.remove(device); + } + + public boolean isClientListEmpty() { + return mClients.size() == 0; + } + + public Collection<WifiP2pDevice> getClientList() { + return Collections.unmodifiableCollection(mClients); + } + + public String getInterface() { + return mInterface; + } + + // TODO: implement + public String toString() { + StringBuffer sbuf = new StringBuffer(); + //sbuf.append("SSID: ").append(SSID); + //sbuf.append("\n passphrase: ").append(passphrase); + return sbuf.toString(); + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** copy constructor {@hide} */ + // TODO: implement + public WifiP2pGroup(WifiP2pGroup source) { + if (source != null) { + } + } + + /** Implement the Parcelable interface {@hide} */ + // STOPSHIP: implement + public void writeToParcel(Parcel dest, int flags) { + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiP2pGroup> CREATOR = + new Creator<WifiP2pGroup>() { + public WifiP2pGroup createFromParcel(Parcel in) { + WifiP2pGroup group = new WifiP2pGroup(); + return group; + } + + public WifiP2pGroup[] newArray(int size) { + return new WifiP2pGroup[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java new file mode 100644 index 000000000000..ea212accfd10 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2011 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.wifi.p2p; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.WorkSource; +import android.os.Messenger; + +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; + +/** + * This class provides the API for managing Wi-Fi p2p + * connectivity. Get an instance of this class by calling + * {@link android.content.Context#getSystemService(String) + * Context.getSystemService(Context.WIFI_P2P_SERVICE)}. + * + * It deals with the following: + * <ul> + * <li>Wi-Fi peer discovery and connection setup. Allows applications to initiate a discovery to + * find available peers and then setup a connection </li> + * <li>Configuration and status query. Allows applications to fetch the current list + * of available and connected peers and query connection status </li> + * <li>Intent actions that are broadcast to track operations + * on a p2p connection</li> + * </ul> + * @hide + */ +public class WifiP2pManager { + /** + * Broadcast intent action to indicate whether Wi-Fi p2p is enabled or disabled. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String WIFI_P2P_STATE_CHANGED_ACTION = + "android.net.wifi.P2P_STATE_CHANGED"; + + /** + * The lookup key for an int that indicates whether Wi-Fi p2p is enabled or disabled. + * Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}. + * + * @see #WIFI_P2P_STATE_DISABLED + * @see #WIFI_P2P_STATE_ENABLED + */ + public static final String EXTRA_WIFI_STATE = "wifi_p2p_state"; + + /** + * Wi-Fi p2p is disabled. + * + * @see #WIFI_P2P_STATE_CHANGED_ACTION + * @see #getWifiP2pState() + */ + public static final int WIFI_P2P_STATE_DISABLED = 1; + + /** + * Wi-Fi p2p is enabled. + * + * @see #WIFI_P2P_STATE_CHANGED_ACTION + * @see #getWifiP2pState() + */ + public static final int WIFI_P2P_STATE_ENABLED = 2; + + /** + * Broadcast intent action indicating that the state of Wi-Fi p2p connectivity + * has changed. One extra provides the new state + * in the form of a {@link android.net.NetworkInfo} object. + * @see #EXTRA_NETWORK_INFO + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String WIFI_P2P_CONNECTION_CHANGED_ACTION = + "android.net.wifi.CONNECTION_STATE_CHANGE"; + + /** + * The lookup key for a {@link android.net.NetworkInfo} object associated with the + * Wi-Fi network. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_NETWORK_INFO = "networkInfo"; + + /** + * Broadcast intent action indicating that the available peer list has changed + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String WIFI_P2P_PEERS_CHANGED_ACTION = + "android.net.wifi.PEERS_CHANGED"; + + /** + * Activity Action: Pick a Wi-Fi p2p network to connect to. + * <p>Input: Nothing. + * <p>Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PICK_WIFI_P2P_NETWORK = + "android.net.wifi.PICK_WIFI_P2P_NETWORK"; + + IWifiP2pManager mService; + + /* For communication with WifiP2pService */ + private AsyncChannel mAsyncChannel = new AsyncChannel(); + + /* AsyncChannel notifications to apps */ + public static final int HANDLER_CONNECTION = AsyncChannel.CMD_CHANNEL_HALF_CONNECTED; + public static final int HANDLER_DISCONNECTION = AsyncChannel.CMD_CHANNEL_DISCONNECTED; + + private static final int BASE = Protocol.BASE_WIFI_P2P_MANAGER; + + public static final int ENABLE_P2P = BASE + 1; + public static final int ENABLE_P2P_FAILED = BASE + 2; + public static final int ENABLE_P2P_SUCCEEDED = BASE + 3; + + /* arg1 on ENABLE_P2P_FAILED indicates a reason for failure */ + public static final int P2P_UNSUPPORTED = 1; + + public static final int DISABLE_P2P = BASE + 5; + public static final int DISABLE_P2P_FAILED = BASE + 6; + public static final int DISABLE_P2P_SUCCEEDED = BASE + 7; + + public static final int START_LISTEN_MODE = BASE + 9; + public static final int START_LISTEN_FAILED = BASE + 10; + public static final int START_LISTEN_SUCCEEDED = BASE + 11; + + public static final int DISCOVER_PEERS = BASE + 13; + public static final int DISCOVER_PEERS_FAILED = BASE + 14; + public static final int DISCOVER_PEERS_SUCCEDED = BASE + 15; + + public static final int CANCEL_DISCOVER_PEERS = BASE + 17; + public static final int CANCEL_DISCOVER_PEERS_FAILED = BASE + 18; + public static final int CANCEL_DISCOVER_PEERS_SUCCEDED = BASE + 19; + + public static final int CONNECT = BASE + 21; + public static final int CONNECT_FAILED = BASE + 22; + public static final int CONNECT_SUCCEEDED = BASE + 23; + + public static final int CANCEL_CONNECT = BASE + 25; + public static final int CANCEL_CONNECT_FAILED = BASE + 26; + public static final int CANCEL_CONNECT_SUCCEDED = BASE + 27; + + public static final int REJECT = BASE + 28; + public static final int REJECT_FAILED = BASE + 29; + public static final int REJECT_SUCCEEDED = BASE + 30; + + public static final int CREATE_GROUP = BASE + 31; + public static final int CREATE_GROUP_FAILED = BASE + 32; + public static final int CREATE_GROUP_SUCCEEDED = BASE + 33; + + public static final int REMOVE_GROUP = BASE + 34; + public static final int REMOVE_GROUP_FAILED = BASE + 35; + public static final int REMOVE_GROUP_SUCCEEDED = BASE + 36; + + public static final int REQUEST_SETTINGS = BASE + 37; + public static final int RESPONSE_SETTINGS = BASE + 38; + + public static final int REQUEST_PEERS = BASE + 39; + public static final int RESPONSE_PEERS = BASE + 40; + + public static final int REQUEST_CONNECTION_STATUS = BASE + 41; + public static final int RESPONSE_CONNECTION_STATUS = BASE + 42; + + public static final int WPS_PBC = BASE + 43; + public static final int WPS_PIN = BASE + 44; + public static final int WPS_PIN_AVAILABLE = BASE + 45; + + /** + * Create a new WifiP2pManager instance. Applications use + * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve + * the standard {@link android.content.Context#WIFI_P2P_SERVICE Context.WIFI_P2P_SERVICE}. + * @param service the Binder interface + * @param handler target for messages + * @hide - hide this because it takes in a parameter of type IWifiP2pManager, which + * is a system private class. + */ + public WifiP2pManager(IWifiP2pManager service) { + mService = service; + } + + /** + * Registers the application handler with the Wi-Fi framework. + * This function must be the first to be called before any p2p control + * or query operations can be performed. + * @param srcContext is the context of the source + * @param srcHandler is the handler on which the source receives messages + * @return {@code true} if the operation succeeded + */ + public boolean connectHandler(Context srcContext, Handler srcHandler) { + Messenger messenger = getMessenger(); + if (messenger == null) return false; + return mAsyncChannel.connectSync(srcContext, srcHandler, messenger) + == AsyncChannel.STATUS_SUCCESSFUL; + } + + public boolean isP2pSupported() { + try { + return mService.isP2pSupported(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Sends in a request to the system to enable p2p. This will pop up a dialog + * to the user and upon authorization will enable p2p. + */ + public void enableP2p() { + mAsyncChannel.sendMessage(ENABLE_P2P); + } + + /** + * Sends in a request to the system to disable p2p. This will pop up a dialog + * to the user and upon authorization will enable p2p. + */ + public void disableP2p() { + mAsyncChannel.sendMessage(DISABLE_P2P); + } + + /** + * Set device in listen mode. This will make the device discoverable by + * another peer. + * A dialog to the user is thrown to request his permission since it can + * have a significant impact on power consumption + */ + public void setListenState(int timeout) { + mAsyncChannel.sendMessage(START_LISTEN_MODE, timeout); + } + + /** + * Initiates peer discovery + */ + public void discoverPeers() { + mAsyncChannel.sendMessage(DISCOVER_PEERS); + } + + /** + * Initiates peer discovery with a timeout + */ + public void discoverPeers(int timeout) { + mAsyncChannel.sendMessage(DISCOVER_PEERS, timeout); + } + + /** + * Cancel any existing peer discovery operation + */ + public void cancelPeerDiscovery() { + mAsyncChannel.sendMessage(CANCEL_DISCOVER_PEERS); + } + + /** + * Start a p2p connection + * + * @param peer Configuration described in a {@link WifiP2pConfig} object. + */ + public void connect(WifiP2pConfig config) { + mAsyncChannel.sendMessage(CONNECT, config); + } + + /** + * Cancel any ongoing negotiation or disconnect from an existing group + */ + public void disconnect() { + mAsyncChannel.sendMessage(CANCEL_CONNECT); + } + + /** + * Create a p2p group. This is essentially an access point that can accept + * client connections. + */ + public void createGroup() { + mAsyncChannel.sendMessage(CREATE_GROUP); + } + + /** + * Remove the current group. This also removes the p2p interface created + * during group formation. + */ + public void removeGroup() { + mAsyncChannel.sendMessage(REMOVE_GROUP); + } + + /** + * Request current p2p settings. This returns a RESPONSE_SETTINGS on the source + * handler. + */ + public void requestP2pSettings() { + mAsyncChannel.sendMessage(REQUEST_SETTINGS); + } + + /** + * Request the list of peers. This returns a RESPONSE_PEERS on the source + * handler. + */ + public void requestPeers() { + mAsyncChannel.sendMessage(REQUEST_PEERS); + } + + /** + * Fetch device list from a RESPONSE_PEERS message + */ + public WifiP2pDeviceList peersInResponse(Message msg) { + return (WifiP2pDeviceList) msg.obj; + } + + /** + * Request device connection status. This returns a RESPONSE_CONNECTION_STATUS on + * the source handler. + */ + public void requestConnectionStatus() { + mAsyncChannel.sendMessage(REQUEST_CONNECTION_STATUS); + } + + + /** + * Get a reference to WifiP2pService handler. This is used to establish + * an AsyncChannel communication with WifiService + * + * @return Messenger pointing to the WifiP2pService handler + * @hide + */ + public Messenger getMessenger() { + try { + return mService.getMessenger(); + } catch (RemoteException e) { + return null; + } + } +} diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java new file mode 100644 index 000000000000..4988f0b8573e --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java @@ -0,0 +1,880 @@ +/* + * Copyright (C) 2011 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.server; + +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiMonitor; +import android.net.wifi.WifiNative; +import android.net.wifi.WifiStateMachine; +import android.net.wifi.WpsConfiguration; +import android.net.wifi.WpsConfiguration.Setup; +import android.net.wifi.p2p.IWifiP2pManager; +import android.net.wifi.p2p.WifiP2pConfig; +import android.net.wifi.p2p.WifiP2pDevice; +import android.net.wifi.p2p.WifiP2pDevice.Status; +import android.net.wifi.p2p.WifiP2pDeviceList; +import android.net.wifi.p2p.WifiP2pGroup; +import android.net.wifi.p2p.WifiP2pManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Messenger; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Message; +import android.util.Slog; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Collection; + +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; +import com.android.internal.R; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +/** + * WifiP2pService inclues a state machine to perform Wi-Fi p2p operations. Applications + * communicate with this service to issue device discovery and connectivity requests + * through the WifiP2pManager interface. The state machine communicates with the wifi + * driver through wpa_supplicant and handles the event responses through WifiMonitor. + * + * Note that the term Wifi when used without a p2p suffix refers to the client mode + * of Wifi operation + * @hide + */ +public class WifiP2pService extends IWifiP2pManager.Stub { + private static final String TAG = "WifiP2pService"; + private static final boolean DBG = true; + + private Context mContext; + + // Tracked to notify the user about wifi client/hotspot being shut down + // during p2p bring up + private int mWifiState = WifiManager.WIFI_STATE_DISABLED; + private int mWifiApState = WifiManager.WIFI_AP_STATE_DISABLED; + + private P2pStateMachine mP2pStateMachine; + private AsyncChannel mReplyChannel = new AsyncChannel();; + private AsyncChannel mWifiChannel; + + private static final int BASE = Protocol.BASE_WIFI_P2P_SERVICE; + + /* Message sent to WifiStateMachine to indicate p2p enable is pending */ + public static final int P2P_ENABLE_PENDING = BASE + 1; + /* Message sent to WifiStateMachine to indicate Wi-Fi client/hotspot operation can proceed */ + public static final int WIFI_ENABLE_PROCEED = BASE + 2; + + /* User accepted to disable Wi-Fi in order to enable p2p */ + private static final int WIFI_DISABLE_USER_ACCEPT = BASE + 11; + + private final boolean mP2pSupported; + + public WifiP2pService(Context context) { + mContext = context; + + mP2pSupported = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_wifi_p2p_support); + + mP2pStateMachine = new P2pStateMachine(TAG, mP2pSupported); + mP2pStateMachine.start(); + + // broadcasts + IntentFilter filter = new IntentFilter(); + filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); + mContext.registerReceiver(new WifiStateReceiver(), filter); + + } + + private class WifiStateReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_DISABLED); + } else if (intent.getAction().equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) { + mWifiApState = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, + WifiManager.WIFI_AP_STATE_DISABLED); + } + } + } + + private void enforceAccessPermission() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, + "WifiP2pService"); + } + + private void enforceChangePermission() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, + "WifiP2pService"); + } + + /** + * Get a reference to handler. This is used by a client to establish + * an AsyncChannel communication with WifiP2pService + */ + public Messenger getMessenger() { + enforceAccessPermission(); + enforceChangePermission(); + return new Messenger(mP2pStateMachine.getHandler()); + } + + /** + * Return if p2p is supported + */ + public boolean isP2pSupported() { + enforceAccessPermission(); + return mP2pSupported; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump WifiP2pService from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + } + + + /** + * Handles interaction with WifiStateMachine + */ + private class P2pStateMachine extends StateMachine { + + private DefaultState mDefaultState = new DefaultState(); + private P2pNotSupportedState mP2pNotSupportedState = new P2pNotSupportedState(); + private P2pDisablingState mP2pDisablingState = new P2pDisablingState(); + private P2pDisabledState mP2pDisabledState = new P2pDisabledState(); + private WaitForWifiDisableState mWaitForWifiDisableState = new WaitForWifiDisableState(); + private P2pEnablingState mP2pEnablingState = new P2pEnablingState(); + private P2pEnabledState mP2pEnabledState = new P2pEnabledState(); + // Inactive is when p2p is enabled with no connectivity + private InactiveState mInactiveState = new InactiveState(); + private GroupNegotiationState mGroupNegotiationState = new GroupNegotiationState(); + private GroupCreatedState mGroupCreatedState = new GroupCreatedState(); + + private WifiMonitor mWifiMonitor = new WifiMonitor(this); + + private WifiP2pDeviceList mPeers = new WifiP2pDeviceList(); + private WifiP2pGroup mGroup; + + // Saved enable request message so the state machine can send an appropriate response + private Message mSavedEnableRequestMessage; + + // Saved WifiP2pConfig from GO negotiation request + private WifiP2pConfig mSavedGoNegotiationConfig; + + // Saved WifiP2pConfig from connect request + private WifiP2pConfig mSavedConnectConfig; + + // Saved WifiP2pGroup from invitation request + private WifiP2pGroup mSavedP2pGroup; + + P2pStateMachine(String name, boolean p2pSupported) { + super(name); + + addState(mDefaultState); + addState(mP2pNotSupportedState, mDefaultState); + addState(mP2pDisablingState, mDefaultState); + addState(mP2pDisabledState, mDefaultState); + addState(mWaitForWifiDisableState, mDefaultState); + addState(mP2pEnablingState, mDefaultState); + addState(mP2pEnabledState, mDefaultState); + addState(mInactiveState, mP2pEnabledState); + addState(mGroupNegotiationState, mP2pEnabledState); + addState(mGroupCreatedState, mP2pEnabledState); + + if (p2pSupported) { + setInitialState(mP2pDisabledState); + } else { + setInitialState(mP2pNotSupportedState); + } + } + + // TODO: Respond to every p2p request with success/failure + class DefaultState extends State { + @Override + public boolean processMessage(Message message) { + if (DBG) Slog.d(TAG, getName() + message.toString()); + switch (message.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + if (DBG) Slog.d(TAG, "Full connection with WifiStateMachine established"); + mWifiChannel = (AsyncChannel) message.obj; + } else { + Slog.e(TAG, "Full connection failure, error = " + message.arg1); + mWifiChannel = null; + } + break; + + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + if (message.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { + Slog.e(TAG, "Send failed, client connection lost"); + } else { + Slog.e(TAG, "Client connection lost with reason: " + message.arg1); + } + mWifiChannel = null; + break; + + case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: + AsyncChannel ac = new AsyncChannel(); + ac.connect(mContext, getHandler(), message.replyTo); + break; + case WifiStateMachine.WIFI_ENABLE_PENDING: + // Disable p2p operation before we can respond + sendMessage(WifiP2pManager.DISABLE_P2P); + deferMessage(message); + break; + case WifiP2pManager.ENABLE_P2P: + mReplyChannel.replyToMessage(message, WifiP2pManager.ENABLE_P2P_FAILED); + break; + case WifiP2pManager.DISABLE_P2P: + mReplyChannel.replyToMessage(message, WifiP2pManager.DISABLE_P2P_FAILED); + break; + case WifiP2pManager.START_LISTEN_MODE: + mReplyChannel.replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED); + break; + case WifiP2pManager.DISCOVER_PEERS: + mReplyChannel.replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED); + break; + case WifiP2pManager.CANCEL_DISCOVER_PEERS: + mReplyChannel.replyToMessage(message, + WifiP2pManager.CANCEL_DISCOVER_PEERS_FAILED); + break; + case WifiP2pManager.CONNECT: + mReplyChannel.replyToMessage(message, WifiP2pManager.CONNECT_FAILED); + break; + case WifiP2pManager.CANCEL_CONNECT: + mReplyChannel.replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED); + break; + case WifiP2pManager.REJECT: + mReplyChannel.replyToMessage(message, WifiP2pManager.REJECT_FAILED); + break; + case WifiP2pManager.CREATE_GROUP: + mReplyChannel.replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED); + break; + case WifiP2pManager.REMOVE_GROUP: + mReplyChannel.replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED); + break; + // TODO: fix + case WifiP2pManager.REQUEST_SETTINGS: + case WifiP2pManager.REQUEST_PEERS: + case WifiP2pManager.REQUEST_CONNECTION_STATUS: + break; + // Ignore + case WIFI_DISABLE_USER_ACCEPT: + break; + default: + Slog.e(TAG, "Unhandled message " + message); + return NOT_HANDLED; + } + return HANDLED; + } + } + + class P2pNotSupportedState extends State { + @Override + public boolean processMessage(Message message) { + switch (message.what) { + // Allow Wi-Fi to proceed + case WifiStateMachine.WIFI_ENABLE_PENDING: + mReplyChannel.replyToMessage(message, WIFI_ENABLE_PROCEED); + break; + case WifiP2pManager.ENABLE_P2P: + mReplyChannel.replyToMessage(message, WifiP2pManager.ENABLE_P2P_FAILED, + WifiP2pManager.P2P_UNSUPPORTED); + break; + case WifiP2pManager.DISABLE_P2P: + mReplyChannel.replyToMessage(message, WifiP2pManager.DISABLE_P2P_FAILED, + WifiP2pManager.P2P_UNSUPPORTED); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class P2pDisablingState extends State { + @Override + public void enter() { + if (DBG) Slog.d(TAG, getName()); + // TODO: fix later + WifiNative.unloadDriver(); + transitionTo(mP2pDisabledState); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) Slog.d(TAG, getName() + message.toString()); + switch (message.what) { + case WifiMonitor.SUP_DISCONNECTION_EVENT: + WifiNative.unloadDriver(); + transitionTo(mP2pDisabledState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + + class P2pDisabledState extends State { + @Override + public void enter() { + if (DBG) Slog.d(TAG, getName()); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) Slog.d(TAG, getName() + message.toString()); + switch (message.what) { + case WifiP2pManager.ENABLE_P2P: + mSavedEnableRequestMessage = Message.obtain(message); + OnClickListener listener = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + sendMessage(WIFI_DISABLE_USER_ACCEPT); + } else { + mReplyChannel.replyToMessage(mSavedEnableRequestMessage, + WifiP2pManager.ENABLE_P2P_FAILED); + } + } + }; + + // Show a user request dialog if we know Wi-Fi client/hotspot is in operation + if (mWifiState != WifiManager.WIFI_STATE_DISABLED || + mWifiApState != WifiManager.WIFI_AP_STATE_DISABLED) { + Resources r = Resources.getSystem(); + AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) + .setMessage(r.getString(R.string.wifi_p2p_turnon_message)) + .setPositiveButton(r.getString(R.string.ok), listener) + .setNegativeButton(r.getString(R.string.cancel), listener) + .create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + dialog.show(); + } else { + mWifiChannel.sendMessage(P2P_ENABLE_PENDING); + transitionTo(mWaitForWifiDisableState); + } + break; + case WIFI_DISABLE_USER_ACCEPT: + mWifiChannel.sendMessage(P2P_ENABLE_PENDING); + transitionTo(mWaitForWifiDisableState); + break; + case WifiStateMachine.WIFI_ENABLE_PENDING: + mReplyChannel.replyToMessage(message, WIFI_ENABLE_PROCEED); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class WaitForWifiDisableState extends State { + @Override + public void enter() { + if (DBG) Slog.d(TAG, getName()); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) Slog.d(TAG, getName() + message.toString()); + switch (message.what) { + case WifiStateMachine.P2P_ENABLE_PROCEED: + // TODO: fix this for p2p + if (WifiNative.loadDriver() && + WifiNative.startSupplicant()) { + Slog.d(TAG, "Wi-fi Direct start successful"); + mWifiMonitor.startMonitoring(); + transitionTo(mP2pEnablingState); + } else { + notifyP2pEnableFailure(); + mReplyChannel.replyToMessage(mSavedEnableRequestMessage, + WifiP2pManager.ENABLE_P2P_FAILED); + transitionTo(mP2pDisabledState); + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class P2pEnablingState extends State { + @Override + public void enter() { + if (DBG) Slog.d(TAG, getName()); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) Slog.d(TAG, getName() + message.toString()); + switch (message.what) { + case WifiMonitor.SUP_CONNECTION_EVENT: + mReplyChannel.replyToMessage(mSavedEnableRequestMessage, + WifiP2pManager.ENABLE_P2P_SUCCEEDED); + transitionTo(mInactiveState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class P2pEnabledState extends State { + @Override + public void enter() { + if (DBG) Slog.d(TAG, getName()); + sendP2pStateChangedBroadcast(true); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) Slog.d(TAG, getName() + message.toString()); + switch (message.what) { + case WifiP2pManager.DISABLE_P2P: + // TODO: use stopSupplicant after control channel fixed + WifiNative.killSupplicant(); + transitionTo(mP2pDisablingState); + break; + case WifiP2pManager.DISCOVER_PEERS: + int timeout = message.arg1; + WifiNative.p2pFlush(); + WifiNative.p2pFind(timeout); + break; + case WifiP2pManager.REQUEST_PEERS: + mReplyChannel.replyToMessage(message, WifiP2pManager.RESPONSE_PEERS, mPeers); + break; + case WifiMonitor.P2P_DEVICE_FOUND_EVENT: + WifiP2pDevice device = (WifiP2pDevice) message.obj; + mPeers.add(device); + sendP2pPeersChangedBroadcast(); + break; + case WifiMonitor.P2P_DEVICE_LOST_EVENT: + device = (WifiP2pDevice) message.obj; + if (mPeers.remove(device)) sendP2pPeersChangedBroadcast(); + break; + case WifiP2pManager.CONNECT: + if (DBG) Slog.d(TAG, getName() + " sending connect"); + mSavedConnectConfig = (WifiP2pConfig) message.obj; + String pin = WifiNative.p2pConnect(mSavedConnectConfig); + try { + Integer.parseInt(pin); + notifyWpsPin(pin, mSavedConnectConfig.deviceAddress); + } catch (NumberFormatException ignore) { + // do nothing if p2pConnect did not return a pin + } + updateDeviceStatus(mSavedConnectConfig.deviceAddress, Status.INVITED); + sendP2pPeersChangedBroadcast(); + transitionTo(mGroupNegotiationState); + break; + case WifiP2pManager.REJECT: + if (DBG) Slog.d(TAG, getName() + " sending reject"); + WifiNative.p2pReject((String) message.obj); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + @Override + public void exit() { + sendP2pStateChangedBroadcast(false); + } + } + + class InactiveState extends State { + @Override public void enter() { + if (DBG) Slog.d(TAG, getName()); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) Slog.d(TAG, getName() + message.toString()); + switch (message.what) { + case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT: + mSavedGoNegotiationConfig = (WifiP2pConfig) message.obj; + notifyP2pGoNegotationRequest(mSavedGoNegotiationConfig); + break; + case WifiP2pManager.CREATE_GROUP: + WifiNative.p2pGroupAdd(); + transitionTo(mGroupNegotiationState); + break; + case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT: + WifiP2pGroup group = (WifiP2pGroup) message.obj; + notifyP2pInvitationReceived(group); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class GroupNegotiationState extends State { + @Override public void enter() { + if (DBG) Slog.d(TAG, getName()); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) Slog.d(TAG, getName() + message.toString()); + switch (message.what) { + // We ignore these right now, since we get a GROUP_STARTED notification + // afterwards + case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT: + case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT: + if (DBG) Slog.d(TAG, getName() + " go success"); + break; + case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT: + case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT: + if (DBG) Slog.d(TAG, getName() + " go failure"); + updateDeviceStatus(mSavedConnectConfig.deviceAddress, Status.FAILED); + mSavedConnectConfig = null; + transitionTo(mInactiveState); + break; + case WifiMonitor.P2P_GROUP_STARTED_EVENT: + mGroup = (WifiP2pGroup) message.obj; + if (DBG) Slog.d(TAG, getName() + " group started"); + // If this device is GO, do nothing since there is a follow up + // AP_STA_CONNECTED event + if (!mGroup.isGroupOwner()) { + WifiP2pDevice groupOwner = mGroup.getOwner(); + updateDeviceStatus(groupOwner.deviceAddress, Status.CONNECTED); + sendP2pPeersChangedBroadcast(); + } + transitionTo(mGroupCreatedState); + break; + case WifiP2pManager.CANCEL_CONNECT: + // TODO: fix + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class GroupCreatedState extends State { + @Override + public void enter() { + if (DBG) Slog.d(TAG, getName()); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) Slog.d(TAG, getName() + message.toString()); + switch (message.what) { + case WifiMonitor.AP_STA_CONNECTED_EVENT: + String address = (String) message.obj; + mGroup.addClient(address); + updateDeviceStatus(address, Status.CONNECTED); + if (DBG) Slog.d(TAG, getName() + " ap sta connected"); + sendP2pPeersChangedBroadcast(); + break; + case WifiMonitor.AP_STA_DISCONNECTED_EVENT: + address = (String) message.obj; + updateDeviceStatus(address, Status.AVAILABLE); + if (mGroup.removeClient(address)) { + if (DBG) Slog.d(TAG, "Removed client " + address); + if (mGroup.isClientListEmpty()) { + Slog.d(TAG, "Client list empty, killing p2p connection"); + sendMessage(WifiP2pManager.REMOVE_GROUP); + } else { + // Just send a notification + sendP2pPeersChangedBroadcast(); + } + } else { + if (DBG) Slog.d(TAG, "Failed to remove client " + address); + for (WifiP2pDevice c : mGroup.getClientList()) { + if (DBG) Slog.d(TAG,"client " + c.deviceAddress); + } + } + if (DBG) Slog.e(TAG, getName() + " ap sta disconnected"); + break; + // Disconnect & remove group have same effect when connected + case WifiP2pManager.CANCEL_CONNECT: + case WifiP2pManager.REMOVE_GROUP: + if (DBG) Slog.e(TAG, getName() + " remove group"); + WifiNative.p2pFlush(); + WifiNative.p2pGroupRemove(mGroup.getInterface()); + break; + case WifiMonitor.P2P_GROUP_REMOVED_EVENT: + if (DBG) Slog.e(TAG, getName() + " group removed"); + Collection <WifiP2pDevice> devices = mGroup.getClientList(); + boolean changed = false; + for (WifiP2pDevice d : mPeers.getDeviceList()) { + if (devices.contains(d) || mGroup.getOwner().equals(d)) { + d.status = Status.AVAILABLE; + changed = true; + } + } + mGroup = null; + if (changed) sendP2pPeersChangedBroadcast(); + transitionTo(mInactiveState); + break; + case WifiMonitor.P2P_DEVICE_LOST_EVENT: + WifiP2pDevice device = (WifiP2pDevice) message.obj; + if (device.equals(mGroup.getOwner())) { + Slog.d(TAG, "Lost the group owner, killing p2p connection"); + sendMessage(WifiP2pManager.REMOVE_GROUP); + } else if (mGroup.removeClient(device) && mGroup.isClientListEmpty()) { + Slog.d(TAG, "Client list empty, killing p2p connection"); + sendMessage(WifiP2pManager.REMOVE_GROUP); + } + return NOT_HANDLED; // Do the regular device lost handling + case WifiP2pManager.DISABLE_P2P: + sendMessage(WifiP2pManager.REMOVE_GROUP); + deferMessage(message); + break; + case WifiP2pManager.DISCOVER_PEERS: + int timeout = message.arg1; + WifiNative.p2pFind(timeout); + break; + case WifiP2pManager.CONNECT: + WifiP2pConfig config = (WifiP2pConfig) message.obj; + Slog.d(TAG, "Inviting device : " + config.deviceAddress); + WifiNative.p2pInvite(mGroup, config.deviceAddress); + updateDeviceStatus(config.deviceAddress, Status.INVITED); + sendP2pPeersChangedBroadcast(); + // TODO: figure out updating the status to declined when invitation is rejected + break; + case WifiMonitor.P2P_INVITATION_RESULT_EVENT: + Slog.d(TAG,"===> INVITATION RESULT EVENT : " + message.obj); + break; + case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT: + notifyP2pProvDiscPbcRequest((WifiP2pDevice) message.obj); + break; + case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT: + notifyP2pProvDiscPinRequest((WifiP2pDevice) message.obj); + break; + case WifiP2pManager.WPS_PBC: + WifiNative.p2pWpsPbc(); + break; + case WifiP2pManager.WPS_PIN: + WifiNative.p2pWpsPin((String) message.obj); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private void sendP2pStateChangedBroadcast(boolean enabled) { + final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + if (enabled) { + intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE, + WifiP2pManager.WIFI_P2P_STATE_ENABLED); + } else { + intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE, + WifiP2pManager.WIFI_P2P_STATE_DISABLED); + } + mContext.sendStickyBroadcast(intent); + } + + private void sendP2pPeersChangedBroadcast() { + final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcast(intent); + } + + private void notifyP2pEnableFailure() { + Resources r = Resources.getSystem(); + AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) + .setMessage(r.getString(R.string.wifi_p2p_failed_message)) + .setPositiveButton(r.getString(R.string.ok), null) + .create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + dialog.show(); + } + + private void notifyWpsPin(String pin, String peerAddress) { + Resources r = Resources.getSystem(); + AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) + .setMessage(r.getString(R.string.wifi_p2p_pin_display_message, pin, peerAddress)) + .setPositiveButton(r.getString(R.string.ok), null) + .create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + dialog.show(); + } + + private void notifyP2pGoNegotationRequest(WifiP2pConfig config) { + Resources r = Resources.getSystem(); + WpsConfiguration wpsConfig = config.wpsConfig; + final View textEntryView = LayoutInflater.from(mContext) + .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null); + final EditText pin = (EditText) textEntryView .findViewById(R.id.wifi_p2p_wps_pin); + + AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) + .setView(textEntryView) + .setPositiveButton(r.getString(R.string.ok), new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (DBG) Slog.d(TAG, getName() + " connect " + pin.getText()); + mSavedGoNegotiationConfig.wpsConfig.setup = Setup.KEYPAD; + mSavedGoNegotiationConfig.wpsConfig.pin = pin.getText().toString(); + sendMessage(WifiP2pManager.CONNECT, mSavedGoNegotiationConfig); + mSavedGoNegotiationConfig = null; + } + }) + .setNegativeButton(r.getString(R.string.cancel), new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (DBG) Slog.d(TAG, getName() + " reject"); + sendMessage(WifiP2pManager.REJECT, + mSavedGoNegotiationConfig.deviceAddress); + mSavedGoNegotiationConfig = null; + } + }) + .create(); + + if (wpsConfig.setup == Setup.PBC) { + pin.setVisibility(View.GONE); + dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message, + config.deviceAddress)); + } else { + dialog.setMessage(r.getString(R.string.wifi_p2p_pin_go_negotiation_request_message, + config.deviceAddress)); + } + + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + dialog.show(); + } + + private void notifyP2pProvDiscPbcRequest(WifiP2pDevice peer) { + Resources r = Resources.getSystem(); + final View textEntryView = LayoutInflater.from(mContext) + .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null); + final EditText pin = (EditText) textEntryView .findViewById(R.id.wifi_p2p_wps_pin); + + AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) + .setView(textEntryView) + .setPositiveButton(r.getString(R.string.ok), new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (DBG) Slog.d(TAG, getName() + " wps_pbc"); + sendMessage(WifiP2pManager.WPS_PBC); + } + }) + .setNegativeButton(r.getString(R.string.cancel), null) + .create(); + + pin.setVisibility(View.GONE); + dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message, + peer.deviceAddress)); + + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + dialog.show(); + } + + private void notifyP2pProvDiscPinRequest(WifiP2pDevice peer) { + Resources r = Resources.getSystem(); + final View textEntryView = LayoutInflater.from(mContext) + .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null); + final EditText pin = (EditText) textEntryView .findViewById(R.id.wifi_p2p_wps_pin); + + AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) + .setView(textEntryView) + .setPositiveButton(r.getString(R.string.ok), new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (DBG) Slog.d(TAG, getName() + " wps_pin"); + sendMessage(WifiP2pManager.WPS_PIN, pin.getText().toString()); + } + }) + .setNegativeButton(r.getString(R.string.cancel), null) + .create(); + + dialog.setMessage(r.getString(R.string.wifi_p2p_pin_go_negotiation_request_message, + peer.deviceAddress)); + + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + dialog.show(); + } + + private void notifyP2pInvitationReceived(WifiP2pGroup group) { + mSavedP2pGroup = group; + Resources r = Resources.getSystem(); + final View textEntryView = LayoutInflater.from(mContext) + .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null); + final EditText pin = (EditText) textEntryView .findViewById(R.id.wifi_p2p_wps_pin); + + AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(r.getString(R.string.wifi_p2p_dialog_title)) + .setView(textEntryView) + .setPositiveButton(r.getString(R.string.ok), new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + WifiP2pConfig config = new WifiP2pConfig(); + config.deviceAddress = mSavedP2pGroup.getOwner().deviceAddress; + config.joinExistingGroup = true; + if (DBG) Slog.d(TAG, getName() + " connect to invited group"); + sendMessage(WifiP2pManager.CONNECT, config); + mSavedP2pGroup = null; + } + }) + .setNegativeButton(r.getString(R.string.cancel), null) + .create(); + + pin.setVisibility(View.GONE); + dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message, + group.getOwner().deviceAddress)); + + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + dialog.show(); + } + + private void updateDeviceStatus(String deviceAddress, Status status) { + for (WifiP2pDevice d : mPeers.getDeviceList()) { + // TODO: fix later + // if (d.deviceAddress.equals(deviceAddress)) { + if (d.deviceAddress.startsWith(deviceAddress.substring(0, 8))) { + d.status = status; + } + } + } + } +} diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pStatus.aidl b/wifi/java/android/net/wifi/p2p/WifiP2pStatus.aidl new file mode 100644 index 000000000000..7bab5d3c8337 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pStatus.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2011, 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.wifi.p2p; + +parcelable WifiP2pStatus; diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pStatus.java b/wifi/java/android/net/wifi/p2p/WifiP2pStatus.java new file mode 100644 index 000000000000..1c9b76c5b6a0 --- /dev/null +++ b/wifi/java/android/net/wifi/p2p/WifiP2pStatus.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011 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.wifi.p2p; + +import android.os.Parcelable; +import android.os.Parcel; + +/** + * A class representing Wi-fi P2p status + * @hide + */ +public class WifiP2pStatus implements Parcelable { + + //Comes from the wpa_supplicant + enum p2p_status_code { + SUCCESS, + FAIL_INFO_CURRENTLY_UNAVAILABLE, + FAIL_INCOMPATIBLE_PARAMS, + FAIL_LIMIT_REACHED, + FAIL_INVALID_PARAMS, + FAIL_UNABLE_TO_ACCOMMODATE, + FAIL_PREV_PROTOCOL_ERROR, + FAIL_NO_COMMON_CHANNELS, + FAIL_UNKNOWN_GROUP, + FAIL_BOTH_GO_INTENT_15, + FAIL_INCOMPATIBLE_PROV_METHOD, + FAIL_REJECTED_BY_USER + }; + + public WifiP2pStatus() { + } + + //TODO: add support + public String toString() { + StringBuffer sbuf = new StringBuffer(); + return sbuf.toString(); + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** copy constructor {@hide} */ + //TODO: implement + public WifiP2pStatus(WifiP2pStatus source) { + if (source != null) { + } + } + + /** Implement the Parcelable interface {@hide} */ + // STOPSHIP: implement + public void writeToParcel(Parcel dest, int flags) { + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiP2pStatus> CREATOR = + new Creator<WifiP2pStatus>() { + public WifiP2pStatus createFromParcel(Parcel in) { + WifiP2pStatus status = new WifiP2pStatus(); + return status; + } + + public WifiP2pStatus[] newArray(int size) { + return new WifiP2pStatus[size]; + } + }; +} |