diff options
178 files changed, 6474 insertions, 2716 deletions
diff --git a/Android.mk b/Android.mk index 3b0fc599ed17..7b8d0bc92f11 100644 --- a/Android.mk +++ b/Android.mk @@ -195,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 \ diff --git a/api/current.txt b/api/current.txt index f88001d825f1..db7713ad7161 100644 --- a/api/current.txt +++ b/api/current.txt @@ -607,7 +607,7 @@ 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 = 16843644; // 0x101037c + field public static final deprecated 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 @@ -617,7 +617,7 @@ package android { 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 = 16843640; // 0x1010378 - field public static final int layout_rowFlexibility = 16843642; // 0x101037a + field public static final deprecated 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 @@ -1828,6 +1828,7 @@ package android.accessibilityservice { method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final int DEFAULT = 1; // 0x1 + field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff field public static final int FEEDBACK_AUDIBLE = 4; // 0x4 field public static final int FEEDBACK_GENERIC = 16; // 0x10 field public static final int FEEDBACK_HAPTIC = 2; // 0x2 @@ -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); @@ -16710,7 +16711,7 @@ package android.provider { field public static final java.lang.String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype"; field public static final java.lang.String SETTINGS_CLASSNAME = "settings_classname"; field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version"; - field public static final java.lang.String TOUCH_EXPLORATION_REQUESTED = "touch_exploration_requested"; + field public static final java.lang.String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled"; field public static final java.lang.String TTS_DEFAULT_COUNTRY = "tts_default_country"; field public static final java.lang.String TTS_DEFAULT_LANG = "tts_default_lang"; field public static final java.lang.String TTS_DEFAULT_PITCH = "tts_default_pitch"; @@ -17951,11 +17952,12 @@ package android.service.textservice { 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; + 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(); @@ -22231,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(); @@ -22472,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); @@ -24054,6 +24058,8 @@ package android.view.textservice { 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; } @@ -24072,8 +24078,7 @@ package android.view.textservice { 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 = 4; // 0x4 - field public static final int RESULT_ATTR_SUGGESTIONS_AVAILABLE = 2; // 0x2 + field public static final int RESULT_ATTR_LOOKS_TYPO = 2; // 0x2 } public final class TextInfo implements android.os.Parcelable { @@ -24089,8 +24094,7 @@ package android.view.textservice { } public final class TextServicesManager { - method public android.view.textservice.SpellCheckerInfo getCurrentSpellChecker(java.util.Locale); - method public android.service.textservice.SpellCheckerSession newSpellCheckerSession(android.view.textservice.SpellCheckerInfo, java.util.Locale, android.service.textservice.SpellCheckerSession.SpellCheckerSessionListener); + method public android.service.textservice.SpellCheckerSession newSpellCheckerSession(java.util.Locale, android.service.textservice.SpellCheckerSession.SpellCheckerSessionListener, boolean); } } @@ -25434,15 +25438,14 @@ package android.widget { method public void setRowCount(int); method public void setRowOrderPreserved(boolean); method public void setUseDefaultMargins(boolean); - method public static android.widget.GridLayout.Spec spec(int, int, android.widget.GridLayout.Alignment, int); - method public static android.widget.GridLayout.Spec spec(int, android.widget.GridLayout.Alignment, int); method public static android.widget.GridLayout.Spec spec(int, int, android.widget.GridLayout.Alignment); method public static android.widget.GridLayout.Spec spec(int, android.widget.GridLayout.Alignment); + method public static android.widget.GridLayout.Spec spec(int, int); + method public static android.widget.GridLayout.Spec spec(int); field public static final int ALIGN_BOUNDS = 0; // 0x0 field public static final int ALIGN_MARGINS = 1; // 0x1 field public static final android.widget.GridLayout.Alignment BASELINE; field public static final android.widget.GridLayout.Alignment BOTTOM; - field public static final int CAN_STRETCH = 2; // 0x2 field public static final android.widget.GridLayout.Alignment CENTER; field public static final android.widget.GridLayout.Alignment FILL; field public static final int HORIZONTAL = 0; // 0x0 @@ -26197,7 +26200,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(); @@ -26206,6 +26209,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/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/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index a09607a7f9ad..41a3eacaafc6 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -75,6 +75,17 @@ public class AccessibilityServiceInfo implements Parcelable { public static final int FEEDBACK_GENERIC = 0x0000010; /** + * Mask for all feedback types. + * + * @see #FEEDBACK_SPOKEN + * @see #FEEDBACK_HAPTIC + * @see #FEEDBACK_AUDIBLE + * @see #FEEDBACK_VISUAL + * @see #FEEDBACK_GENERIC + */ + public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF; + + /** * If an {@link AccessibilityService} is the default for a given type. * Default service is invoked only if no package specific one exists. In case of * more than one package specific service only the earlier registered is notified. diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index aeb16f4756a4..8d03ac70f802 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1593,10 +1593,10 @@ public class Activity extends ContextThemeWrapper //Log.v(TAG, "invalidateFragmentIndex: index=" + index); if (mAllLoaderManagers != null) { LoaderManagerImpl lm = mAllLoaderManagers.get(index); - if (lm != null) { + if (lm != null && !lm.mRetaining) { lm.doDestroy(); + mAllLoaderManagers.remove(index); } - mAllLoaderManagers.remove(index); } } diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index e5a7980b0c8c..93330a798901 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -224,6 +224,12 @@ final class BackStackRecord extends FragmentTransaction implements writer.print(" mExitAnim=#"); writer.println(Integer.toHexString(mExitAnim)); } + if (mPopEnterAnim != 0 || mPopExitAnim !=0) { + writer.print(prefix); writer.print("mPopEnterAnim=#"); + writer.print(Integer.toHexString(mPopEnterAnim)); + writer.print(" mPopExitAnim=#"); + writer.println(Integer.toHexString(mPopExitAnim)); + } if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { writer.print(prefix); writer.print("mBreadCrumbTitleRes=#"); writer.print(Integer.toHexString(mBreadCrumbTitleRes)); @@ -248,13 +254,16 @@ final class BackStackRecord extends FragmentTransaction implements writer.print(innerPrefix); writer.print("cmd="); writer.print(op.cmd); writer.print(" fragment="); writer.println(op.fragment); if (op.enterAnim != 0 || op.exitAnim != 0) { - writer.print(prefix); writer.print("enterAnim="); writer.print(op.enterAnim); - writer.print(" exitAnim="); writer.println(op.exitAnim); + writer.print(prefix); writer.print("enterAnim=#"); + writer.print(Integer.toHexString(op.enterAnim)); + writer.print(" exitAnim=#"); + writer.println(Integer.toHexString(op.exitAnim)); } if (op.popEnterAnim != 0 || op.popExitAnim != 0) { - writer.print(prefix); - writer.print("popEnterAnim="); writer.print(op.popEnterAnim); - writer.print(" popExitAnim="); writer.println(op.popExitAnim); + writer.print(prefix); writer.print("popEnterAnim=#"); + writer.print(Integer.toHexString(op.popEnterAnim)); + writer.print(" popExitAnim=#"); + writer.println(Integer.toHexString(op.popExitAnim)); } if (op.removed != null && op.removed.size() > 0) { for (int i=0; i<op.removed.size(); i++) { @@ -695,11 +704,13 @@ final class BackStackRecord extends FragmentTransaction implements } break; case OP_DETACH: { Fragment f = op.fragment; + f.mNextAnim = op.popEnterAnim; mManager.attachFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); } break; case OP_ATTACH: { Fragment f = op.fragment; + f.mNextAnim = op.popExitAnim; mManager.detachFragment(f, FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); } break; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 6289730cb56e..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; @@ -438,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/content/Context.java b/core/java/android/content/Context.java index 0a2253c8dc75..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. diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java index d4e5cc13b1c1..b32664eefc3c 100644 --- a/core/java/android/content/pm/Signature.java +++ b/core/java/android/content/pm/Signature.java @@ -45,16 +45,23 @@ public class Signature implements Parcelable { * {@link #toChars} or {@link #toCharsString()}. */ public Signature(String text) { - final int N = text.length()/2; - byte[] sig = new byte[N]; - for (int i=0; i<N; i++) { - char c = text.charAt(i*2); - byte b = (byte)( - (c >= 'a' ? (c - 'a' + 10) : (c - '0'))<<4); - c = text.charAt(i*2 + 1); - b |= (byte)(c >= 'a' ? (c - 'a' + 10) : (c - '0')); - sig[i] = b; + final byte[] input = text.getBytes(); + final int N = input.length; + final byte[] sig = new byte[N / 2]; + int sigIndex = 0; + + for (int i = 0; i < N;) { + int b; + + final int hi = input[i++]; + b = (hi >= 'a' ? (hi - 'a' + 10) : (hi - '0')) << 4; + + final int lo = input[i++]; + b |= (lo >= 'a' ? (lo - 'a' + 10) : (lo - '0')) & 0x0F; + + sig[sigIndex++] = (byte) (b & 0xFF); } + mSignature = sig; } 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/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/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 76534ef3d8a7..e289fc15d0bb 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -20,6 +20,7 @@ import java.net.InetAddress; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.UnknownHostException; +import java.util.Collection; import android.util.Log; @@ -235,4 +236,18 @@ public class NetworkUtils { throw new IllegalArgumentException(e); } } + + /** + * Create a string array of host addresses from a collection of InetAddresses + * @param addrs a Collection of InetAddresses + * @return an array of Strings containing their host addresses + */ + public static String[] makeStrings(Collection<InetAddress> addrs) { + String[] result = new String[addrs.size()]; + int i = 0; + for (InetAddress addr : addrs) { + result[i++] = addr.getHostAddress(); + } + return result; + } } 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/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 4a719ec7b9a1..a0f1eece6367 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -6514,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 1cd46dedd2d2..cd4e32e8ce32 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2688,11 +2688,9 @@ public final class Settings { public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled"; /** - * If touch exploration is requested. Touch exploration is enabled if it is - * requested by this setting, accessibility is enabled and there is at least - * one enabled accessibility serivce that provides spoken feedback. + * If touch exploration is enabled. */ - public static final String TOUCH_EXPLORATION_REQUESTED = "touch_exploration_requested"; + public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled"; /** * List of the enabled accessibility providers. @@ -3935,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 d68d8ba1e5b9..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) { @@ -545,65 +523,6 @@ public class BluetoothService extends IBluetooth.Stub { } }; - 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()]; @@ -650,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) { @@ -818,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; @@ -839,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; } @@ -1569,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, @@ -1650,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; } @@ -1840,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) { @@ -2389,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) || @@ -2405,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, @@ -2598,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 index 6ac99cab0ebf..270f51208ce0 100644 --- a/core/java/android/service/textservice/SpellCheckerService.java +++ b/core/java/android/service/textservice/SpellCheckerService.java @@ -36,7 +36,8 @@ import java.lang.ref.WeakReference; */ public abstract class SpellCheckerService extends Service { private static final String TAG = SpellCheckerService.class.getSimpleName(); - public static final String SERVICE_INTERFACE = SpellCheckerService.class.getName(); + public static final String SERVICE_INTERFACE = + "android.service.textservice.SpellCheckerService"; private final SpellCheckerServiceBinder mBinder = new SpellCheckerServiceBinder(this); diff --git a/core/java/android/service/textservice/SpellCheckerSession.java b/core/java/android/service/textservice/SpellCheckerSession.java index a575220ef915..400454da40f9 100644 --- a/core/java/android/service/textservice/SpellCheckerSession.java +++ b/core/java/android/service/textservice/SpellCheckerSession.java @@ -25,10 +25,12 @@ 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; /** @@ -42,6 +44,7 @@ public class SpellCheckerSession { private final InternalListener mInternalListener; private final ITextServicesManager mTextServicesManager; + private final SpellCheckerInfo mSpellCheckerInfo; private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl; private boolean mIsUsed; @@ -63,10 +66,12 @@ public class SpellCheckerSession { * Constructor * @hide */ - public SpellCheckerSession(ITextServicesManager tsm, SpellCheckerSessionListener listener) { - if (listener == null || tsm == null) { + 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; @@ -83,6 +88,14 @@ public class SpellCheckerSession { } /** + * 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. */ diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index c6ffe580753f..5926db3713db 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -281,10 +281,29 @@ public class Time { } /** - * return a negative number if a is less than b, a positive number if a is - * greater than b, and 0 if they are equal. - */ - native public static int compare(Time a, Time b); + * Compare two {@code Time} objects and return a negative number if {@code + * a} is less than {@code b}, a positive number if {@code a} is greater than + * {@code b}, or 0 if they are equal. + * + * @param a first {@code Time} instance to compare + * @param b second {@code Time} instance to compare + * @throws NullPointerException if either argument is {@code null} + * @throws IllegalArgumentException if {@link #allDay} is true but {@code + * hour}, {@code minute}, and {@code second} are not 0. + * @return a negative result if {@code a} is earlier, a positive result if + * {@code a} is earlier, or 0 if they are equal. + */ + public static int compare(Time a, Time b) { + if (a == null) { + throw new NullPointerException("a == null"); + } else if (b == null) { + throw new NullPointerException("b == null"); + } + + return nativeCompare(a, b); + } + + private static native int nativeCompare(Time a, Time b); /** * Print the current value given the format string provided. See man diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 503b54bd437d..14a77b40415f 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -33,6 +33,8 @@ import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import javax.microedition.khronos.opengles.GL; +import static javax.microedition.khronos.egl.EGL10.*; + /** * Interface for rendering a ViewAncestor using hardware acceleration. * @@ -70,6 +72,15 @@ public abstract class HardwareRenderer { static final String DISABLE_VSYNC_PROPERTY = "hwui.disable_vsync"; /** + * System property used to debug EGL configuration choice. + * + * Possible values: + * "choice", print the chosen configuration only + * "all", print all possible configurations + */ + static final String PRINT_CONFIG_PROPERTY = "hwui.print_config"; + + /** * Turn on to draw dirty regions every other frame. */ private static final boolean DEBUG_DIRTY_REGION = false; @@ -389,33 +400,33 @@ public abstract class HardwareRenderer { */ static String getEGLErrorString(int error) { switch (error) { - case EGL10.EGL_SUCCESS: + case EGL_SUCCESS: return "EGL_SUCCESS"; - case EGL10.EGL_NOT_INITIALIZED: + case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED"; - case EGL10.EGL_BAD_ACCESS: + case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS"; - case EGL10.EGL_BAD_ALLOC: + case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC"; - case EGL10.EGL_BAD_ATTRIBUTE: + case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE"; - case EGL10.EGL_BAD_CONFIG: + case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG"; - case EGL10.EGL_BAD_CONTEXT: + case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT"; - case EGL10.EGL_BAD_CURRENT_SURFACE: + case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; - case EGL10.EGL_BAD_DISPLAY: + case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY"; - case EGL10.EGL_BAD_MATCH: + case EGL_BAD_MATCH: return "EGL_BAD_MATCH"; - case EGL10.EGL_BAD_NATIVE_PIXMAP: + case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP"; - case EGL10.EGL_BAD_NATIVE_WINDOW: + case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW"; - case EGL10.EGL_BAD_PARAMETER: + case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER"; - case EGL10.EGL_BAD_SURFACE: + case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE"; case EGL11.EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST"; @@ -432,7 +443,7 @@ public abstract class HardwareRenderer { void checkEglErrors() { if (isEnabled()) { int error = sEgl.eglGetError(); - if (error != EGL10.EGL_SUCCESS) { + if (error != EGL_SUCCESS) { // something bad has happened revert to // normal rendering. fallback(error != EGL11.EGL_CONTEXT_LOST); @@ -460,7 +471,7 @@ public abstract class HardwareRenderer { if (mGl != null) { int err = sEgl.eglGetError(); - if (err != EGL10.EGL_SUCCESS) { + if (err != EGL_SUCCESS) { destroy(true); setRequested(false); } else { @@ -489,15 +500,17 @@ public abstract class HardwareRenderer { abstract GLES20Canvas createCanvas(); + abstract int[] getConfig(boolean dirtyRegions); + void initializeEgl() { synchronized (sEglLock) { if (sEgl == null && sEglConfig == null) { sEgl = (EGL10) EGLContext.getEGL(); // Get to the default display. - sEglDisplay = sEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (sEglDisplay == EGL10.EGL_NO_DISPLAY) { + if (sEglDisplay == EGL_NO_DISPLAY) { throw new RuntimeException("eglGetDisplay failed " + getEGLErrorString(sEgl.eglGetError())); } @@ -535,19 +548,63 @@ public abstract class HardwareRenderer { } private EGLConfig chooseEglConfig() { - int[] configsCount = new int[1]; EGLConfig[] configs = new EGLConfig[1]; + int[] configsCount = new int[1]; int[] configSpec = getConfig(sDirtyRegions); + + // Debug + final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, ""); + if ("all".equalsIgnoreCase(debug)) { + sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount); + + EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]]; + sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs, + configsCount[0], configsCount); + + for (EGLConfig config : debugConfigs) { + printConfig(config); + } + } + if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) { throw new IllegalArgumentException("eglChooseConfig failed " + getEGLErrorString(sEgl.eglGetError())); } else if (configsCount[0] > 0) { + if ("choice".equalsIgnoreCase(debug)) { + printConfig(configs[0]); + } return configs[0]; } + return null; } - abstract int[] getConfig(boolean dirtyRegions); + private void printConfig(EGLConfig config) { + int[] value = new int[1]; + + Log.d(LOG_TAG, "EGL configuration " + config + ":"); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value); + Log.d(LOG_TAG, " RED_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value); + Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value); + Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value); + Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value); + Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value); + Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]); + + sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value); + Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0])); + } GL createEglSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException { // Check preconditions. @@ -569,22 +626,21 @@ public abstract class HardwareRenderer { * The window size has changed, so we need to create a new * surface. */ - if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { + if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { /* * Unbind and destroy the old EGL surface, if * there is one. */ - sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); sEgl.eglDestroySurface(sEglDisplay, mEglSurface); } // Create an EGL surface we can render into. mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null); - if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { + if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { int error = sEgl.eglGetError(); - if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + if (error == EGL_BAD_NATIVE_WINDOW) { Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); return null; } @@ -621,9 +677,9 @@ public abstract class HardwareRenderer { } EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { - int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL10.EGL_NONE }; + int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE }; - return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, + return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, mGlVersion != 0 ? attrib_list : null); } @@ -646,8 +702,7 @@ public abstract class HardwareRenderer { mDestroyed = true; - sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); sEgl.eglDestroySurface(sEglDisplay, mEglSurface); mEglSurface = null; @@ -660,8 +715,8 @@ public abstract class HardwareRenderer { void invalidate() { // Cancels any existing buffer to ensure we'll get a buffer // of the right size before we call eglSwapBuffers - sEgl.eglMakeCurrent(sEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, + EGL_NO_SURFACE, EGL_NO_CONTEXT); } @Override @@ -772,7 +827,7 @@ public abstract class HardwareRenderer { } if (!mEglContext.equals(sEgl.eglGetCurrentContext()) || - !mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) { + !mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) { if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { fallback(true); Log.e(LOG_TAG, "eglMakeCurrent failed " + @@ -804,16 +859,16 @@ public abstract class HardwareRenderer { @Override int[] getConfig(boolean dirtyRegions) { return new int[] { - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_ALPHA_SIZE, 8, - EGL10.EGL_DEPTH_SIZE, 0, - EGL10.EGL_STENCIL_SIZE, 0, - EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT | + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_STENCIL_SIZE, 0, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT | (dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0), - EGL10.EGL_NONE + EGL_NONE }; } 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 ecb391d12bf1..22131881239b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3903,6 +3903,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit int[] locationOnScreen = mAttachInfo.mInvalidateChildLocation; getLocationOnScreen(locationOnScreen); + bounds.offsetTo(0, 0); bounds.offset(locationOnScreen[0], locationOnScreen[1]); info.setBoundsInScreen(bounds); @@ -4280,6 +4281,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}. @@ -12028,12 +12059,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/ViewGroup.java b/core/java/android/view/ViewGroup.java index 6f909713e4c1..8cf03a67a6cd 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.hasAlpha()) { + if (a.hasAlpha() && (child.mPrivateFlags & ALPHA_SET) == ALPHA_SET) { // alpha animations should cause the child to recreate its display list child.invalidate(true); } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 83c73cb85cc8..a80c2a7147c9 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -67,13 +67,17 @@ public final class AccessibilityManager { private static final String LOG_TAG = "AccessibilityManager"; + /** @hide */ + public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001; + + /** @hide */ + public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002; + static final Object sInstanceSync = new Object(); private static AccessibilityManager sInstance; - private static final int DO_SET_ACCESSIBILITY_ENABLED = 10; - - private static final int DO_SET_TOUCH_EXPLORATION_ENABLED = 20; + private static final int DO_SET_STATE = 10; final IAccessibilityManager mService; @@ -100,13 +104,8 @@ public final class AccessibilityManager { } final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { - public void setEnabled(boolean enabled) { - mHandler.obtainMessage(DO_SET_ACCESSIBILITY_ENABLED, enabled ? 1 : 0, 0).sendToTarget(); - } - - public void setTouchExplorationEnabled(boolean enabled) { - mHandler.obtainMessage(DO_SET_TOUCH_EXPLORATION_ENABLED, - enabled ? 1 : 0, 0).sendToTarget(); + public void setState(int state) { + mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget(); } }; @@ -119,14 +118,8 @@ public final class AccessibilityManager { @Override public void handleMessage(Message message) { switch (message.what) { - case DO_SET_ACCESSIBILITY_ENABLED : - final boolean isAccessibilityEnabled = (message.arg1 == 1); - setAccessibilityState(isAccessibilityEnabled); - return; - case DO_SET_TOUCH_EXPLORATION_ENABLED : - synchronized (mHandler) { - mIsTouchExplorationEnabled = (message.arg1 == 1); - } + case DO_SET_STATE : + setState(message.arg1); return; default : Log.w(LOG_TAG, "Unknown message type: " + message.what); @@ -163,8 +156,8 @@ public final class AccessibilityManager { mService = service; try { - final boolean isEnabled = mService.addClient(mClient); - setAccessibilityState(isEnabled); + final int stateFlags = mService.addClient(mClient); + setState(stateFlags); } catch (RemoteException re) { Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); } @@ -341,6 +334,17 @@ public final class AccessibilityManager { } /** + * Sets the current state. + * + * @param stateFlags The state flags. + */ + private void setState(int stateFlags) { + final boolean accessibilityEnabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; + setAccessibilityState(accessibilityEnabled); + mIsTouchExplorationEnabled = (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; + } + + /** * Sets the enabled state. * * @param isEnabled The accessibility state. diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 0e044712c0b2..6469b350a288 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -294,14 +294,14 @@ public class AccessibilityNodeInfo implements Parcelable { } /** - * Gets the unique id identifying this node's parent. + * Gets the parent. * <p> * <strong>Note:</strong> It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. * </p> * - * @return The node's patent id. + * @return The parent. */ public AccessibilityNodeInfo getParent() { enforceSealed(); diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index b14f02a75711..c621ff6d2d60 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -34,7 +34,7 @@ import android.view.IWindow; */ interface IAccessibilityManager { - boolean addClient(IAccessibilityManagerClient client); + int addClient(IAccessibilityManagerClient client); boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent); diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl index 4e69692494c5..5e7e8133e23c 100644 --- a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl @@ -24,7 +24,5 @@ package android.view.accessibility; */ oneway interface IAccessibilityManagerClient { - void setEnabled(boolean enabled); - - void setTouchExplorationEnabled(boolean enabled); + void setState(int stateFlags); } diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java index 1205adfaceb3..d88a39fc050b 100644 --- a/core/java/android/view/textservice/SpellCheckerInfo.java +++ b/core/java/android/view/textservice/SpellCheckerInfo.java @@ -18,8 +18,10 @@ 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; @@ -103,6 +105,24 @@ public final class SpellCheckerInfo implements Parcelable { }; /** + * 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 diff --git a/core/java/android/view/textservice/SuggestionsInfo.java b/core/java/android/view/textservice/SuggestionsInfo.java index b0ccbea7d91a..3332f1e262ee 100644 --- a/core/java/android/view/textservice/SuggestionsInfo.java +++ b/core/java/android/view/textservice/SuggestionsInfo.java @@ -23,27 +23,23 @@ 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 there are one or more suggestions - * available for the requested word. This doesn't necessarily mean that the suggestions - * are actually in this SuggestionsInfo. For instance, the caller could have been asked to - * limit the maximum number of suggestions returned. - */ - public static final int RESULT_ATTR_SUGGESTIONS_AVAILABLE = 0x0002; /** * 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 = 0x0004; + 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; @@ -53,11 +49,14 @@ public final class SuggestionsInfo implements Parcelable { * @param suggestions from the text service */ public SuggestionsInfo(int suggestionsAttributes, String[] suggestions) { + mSuggestionsAttributes = suggestionsAttributes; if (suggestions == null) { - throw new NullPointerException(); + mSuggestions = EMPTY; + mSuggestionsAvailable = false; + } else { + mSuggestions = suggestions; + mSuggestionsAvailable = true; } - mSuggestionsAttributes = suggestionsAttributes; - mSuggestions = suggestions; mCookie = 0; mSequence = 0; } @@ -72,10 +71,13 @@ public final class SuggestionsInfo implements Parcelable { public SuggestionsInfo( int suggestionsAttributes, String[] suggestions, int cookie, int sequence) { if (suggestions == null) { - throw new NullPointerException(); + mSuggestions = EMPTY; + mSuggestionsAvailable = false; + } else { + mSuggestions = suggestions; + mSuggestionsAvailable = true; } mSuggestionsAttributes = suggestionsAttributes; - mSuggestions = suggestions; mCookie = cookie; mSequence = sequence; } @@ -85,6 +87,7 @@ public final class SuggestionsInfo implements Parcelable { mSuggestions = source.readStringArray(); mCookie = source.readInt(); mSequence = source.readInt(); + mSuggestionsAvailable = source.readInt() == 1; } /** @@ -99,6 +102,7 @@ public final class SuggestionsInfo implements Parcelable { dest.writeStringArray(mSuggestions); dest.writeInt(mCookie); dest.writeInt(mSequence); + dest.writeInt(mSuggestionsAvailable ? 1 : 0); } /** @@ -136,9 +140,15 @@ public final class SuggestionsInfo implements Parcelable { } /** - * @return the count of suggestions + * @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; } diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java index 6fa7e4ddf9cd..ae253cf1671b 100644 --- a/core/java/android/view/textservice/TextServicesManager.java +++ b/core/java/android/view/textservice/TextServicesManager.java @@ -22,9 +22,9 @@ 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 android.util.Log; import java.util.Locale; @@ -38,6 +38,7 @@ import java.util.Locale; */ public final class TextServicesManager { private static final String TAG = TextServicesManager.class.getSimpleName(); + private static final boolean DBG = false; private static TextServicesManager sInstance; private static ITextServicesManager sService; @@ -63,44 +64,68 @@ public final class TextServicesManager { return sInstance; } - /** - * Get the current spell checker service info for the specified locale. - * @param locale locale of a spell checker - * @return SpellCheckerInfo for the specified locale. + * 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. - public SpellCheckerInfo getCurrentSpellChecker(Locale locale) { - if (locale == null) { - throw new NullPointerException("locale is null"); + // TODO: Handle referToSpellCheckerLanguageSettings + public SpellCheckerSession newSpellCheckerSession(Locale locale, + SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) { + if (listener == null) { + throw new NullPointerException(); } + // TODO: set a proper locale instead of the dummy locale + final String localeString = locale == null ? "en" : locale.toString(); + final SpellCheckerInfo info; try { - return sService.getCurrentSpellChecker(locale.toString()); + info = sService.getCurrentSpellChecker(localeString); } catch (RemoteException e) { return null; } + if (info == null) { + return null; + } + final SpellCheckerSession session = new SpellCheckerSession(info, sService, listener); + try { + sService.getSpellCheckerService(info, localeString, + session.getTextServicesSessionListener(), + session.getSpellCheckerSessionListener()); + } catch (RemoteException e) { + return null; + } + return session; } /** - * Get a spell checker session for a specified spell checker - * @param info SpellCheckerInfo of the spell checker - * @param locale the locale for the spell checker - * @param listener a spell checker session lister for getting results from a spell checker. - * @return the spell checker session of the spell checker + * @hide */ - public SpellCheckerSession newSpellCheckerSession( - SpellCheckerInfo info, Locale locale, SpellCheckerSessionListener listener) { - if (info == null || locale == null || listener == null) { - throw new NullPointerException(); + public SpellCheckerInfo[] getEnabledSpellCheckers() { + try { + final SpellCheckerInfo[] retval = sService.getEnabledSpellCheckers(); + if (DBG) { + Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null")); + } + return retval; + } catch (RemoteException e) { + Log.e(TAG, "Error in getEnabledSpellCheckers: " + e); + return null; } - final SpellCheckerSession session = new SpellCheckerSession(sService, listener); + } + + /** + * @hide + */ + public SpellCheckerInfo getCurrentSpellChecker() { try { - sService.getSpellCheckerService( - info, locale.toString(), session.getTextServicesSessionListener(), - session.getSpellCheckerSessionListener()); + // Passing null as a locale for ICS + return sService.getCurrentSpellChecker(null); } catch (RemoteException e) { return null; } - return session; } } 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 6a3b2ffefae1..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() + " " + diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java index 273c2588e90c..4419886bed4c 100644 --- a/core/java/android/widget/AdapterViewFlipper.java +++ b/core/java/android/widget/AdapterViewFlipper.java @@ -62,15 +62,14 @@ public class AdapterViewFlipper extends AdapterViewAnimator { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.ViewFlipper); + com.android.internal.R.styleable.AdapterViewFlipper); mFlipInterval = a.getInt( - com.android.internal.R.styleable.ViewFlipper_flipInterval, DEFAULT_INTERVAL); + com.android.internal.R.styleable.AdapterViewFlipper_flipInterval, DEFAULT_INTERVAL); mAutoStart = a.getBoolean( - com.android.internal.R.styleable.ViewFlipper_autoStart, false); + com.android.internal.R.styleable.AdapterViewFlipper_autoStart, false); - // By default we want the flipper to loop - mLoopViews = a.getBoolean( - com.android.internal.R.styleable.AdapterViewAnimator_loopViews, true); + // A view flipper should cycle through the views + mLoopViews = true; a.recycle(); } diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 3bd7fabe9ec7..f999960219da 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -21,7 +21,6 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; @@ -38,6 +37,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static android.view.Gravity.*; import static java.lang.Math.max; import static java.lang.Math.min; @@ -167,7 +167,7 @@ public class GridLayout extends ViewGroup { // Misc constants private static final String TAG = GridLayout.class.getName(); - static boolean DEBUG = false; + private static boolean DEBUG = false; private static final int PRF = 1; // Defaults @@ -198,7 +198,8 @@ public class GridLayout extends ViewGroup { private int mOrientation = DEFAULT_ORIENTATION; private boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS; private int mAlignmentMode = DEFAULT_ALIGNMENT_MODE; - private int mDefaultGravity = Gravity.NO_GRAVITY; + private Alignment mColumnAlignment = LEFT; + private Alignment mRowAlignment = BASELINE; private int mDefaultGap; // Constructors @@ -216,9 +217,9 @@ public class GridLayout extends ViewGroup { try { setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT)); setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT)); - mOrientation = a.getInt(ORIENTATION, DEFAULT_ORIENTATION); - mUseDefaultMargins = a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS); - mAlignmentMode = a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE); + setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION)); + setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS)); + setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE)); setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED)); setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED)); } finally { @@ -514,6 +515,8 @@ public class GridLayout extends ViewGroup { requestLayout(); } + // Static utility methods + private static int max2(int[] a, int valueIfEmpty) { int result = valueIfEmpty; for (int i = 0, N = a.length; i < N; i++) { @@ -537,6 +540,24 @@ public class GridLayout extends ViewGroup { return result; } + private static Alignment getAlignment(int gravity, boolean horizontal) { + int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK; + int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT; + int flags = (gravity & mask) >> shift; + switch (flags) { + case (AXIS_SPECIFIED | AXIS_PULL_BEFORE): + return LEADING; + case (AXIS_SPECIFIED | AXIS_PULL_AFTER): + return TRAILING; + case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER): + return FILL; + case AXIS_SPECIFIED: + return CENTER; + default: + return UNDEFINED_ALIGNMENT; + } + } + private int getDefaultMargin(View c, boolean horizontal, boolean leading) { return mDefaultGap / 2; } @@ -678,7 +699,7 @@ public class GridLayout extends ViewGroup { @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs, mDefaultGravity); + return new LayoutParams(getContext(), attrs); } @Override @@ -833,6 +854,9 @@ public class GridLayout extends ViewGroup { } private int getMeasurementIncludingMargin(View c, boolean horizontal) { + if (isGone(c)) { + return 0; + } int result = getMeasurement(c, horizontal); if (mAlignmentMode == ALIGN_MARGINS) { return result + getTotalMargin(c, horizontal); @@ -846,6 +870,11 @@ public class GridLayout extends ViewGroup { invalidateValues(); } + private Alignment getAlignment(Alignment alignment, boolean horizontal) { + return (alignment != UNDEFINED_ALIGNMENT) ? alignment : + (horizontal ? mColumnAlignment : mRowAlignment); + } + // Layout container /** @@ -895,8 +924,8 @@ public class GridLayout extends ViewGroup { int pWidth = getMeasurement(c, true); int pHeight = getMeasurement(c, false); - Alignment hAlign = columnSpec.alignment; - Alignment vAlign = rowSpec.alignment; + Alignment hAlign = getAlignment(columnSpec.alignment, true); + Alignment vAlign = getAlignment(rowSpec.alignment, false); int dx, dy; @@ -1035,14 +1064,10 @@ public class GridLayout extends ViewGroup { Assoc<Spec, Bounds> assoc = Assoc.of(Spec.class, Bounds.class); for (int i = 0, N = getChildCount(); i < N; i++) { View c = getChildAt(i); - if (isGone(c)) { - assoc.put(Spec.GONE, Bounds.GONE); - } else { - LayoutParams lp = getLayoutParams(c); - Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; - Bounds bounds = spec.alignment.getBounds(); - assoc.put(spec, bounds); - } + LayoutParams lp = getLayoutParams(c); + Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; + Bounds bounds = getAlignment(spec.alignment, horizontal).getBounds(); + assoc.put(spec, bounds); } return assoc.pack(); } @@ -1054,7 +1079,6 @@ public class GridLayout extends ViewGroup { } for (int i = 0, N = getChildCount(); i < N; i++) { View c = getChildAt(i); - if (isGone(c)) continue; LayoutParams lp = getLayoutParams(c); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; groupBounds.getValue(i).include(c, spec, GridLayout.this, this); @@ -1094,11 +1118,8 @@ public class GridLayout extends ViewGroup { for (int i = 0; i < bounds.length; i++) { int size = bounds[i].size(min); MutableInt valueHolder = links.getValue(i); - if (min) { - valueHolder.value = max(valueHolder.value, size); - } else { - valueHolder.value = -max(-valueHolder.value, size); - } + // this effectively takes the max() of the minima and the min() of the maxima + valueHolder.value = max(valueHolder.value, min ? size : -size); } } @@ -1397,7 +1418,7 @@ public class GridLayout extends ViewGroup { } if (!changed) { if (DEBUG) { - Log.d(TAG, axis + " iteration completed in " + (1 + i) + " steps of " + N); + Log.v(TAG, axis + " iteration completed in " + (1 + i) + " steps of " + N); } return; } @@ -1654,14 +1675,6 @@ public class GridLayout extends ViewGroup { private static final int DEFAULT_COLUMN = UNDEFINED; private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1); private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size(); - private static final Alignment DEFAULT_COLUMN_ALIGNMENT = LEFT; - private static final Alignment DEFAULT_ROW_ALIGNMENT = BASELINE; - - // Misc - - private static final Rect CONTAINER_BOUNDS = new Rect(0, 0, 2, 2); - private static final Alignment[] COLUMN_ALIGNMENTS = { LEFT, CENTER, RIGHT }; - private static final Alignment[] ROW_ALIGNMENTS = { TOP, CENTER, BOTTOM }; // TypedArray indices @@ -1728,8 +1741,7 @@ public class GridLayout extends ViewGroup { * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}. */ public LayoutParams() { - this(new Spec(DEFAULT_SPAN, DEFAULT_ROW_ALIGNMENT, Spec.DEFAULT_FLEXIBILITY), - new Spec(DEFAULT_SPAN, DEFAULT_COLUMN_ALIGNMENT, Spec.DEFAULT_FLEXIBILITY)); + this(spec(UNDEFINED), spec(UNDEFINED)); } // Copying constructors @@ -1753,18 +1765,12 @@ public class GridLayout extends ViewGroup { */ public LayoutParams(LayoutParams that) { super(that); - this.columnSpec = new Spec(that.columnSpec); this.rowSpec = new Spec(that.rowSpec); + this.columnSpec = new Spec(that.columnSpec); } // AttributeSet constructors - private LayoutParams(Context context, AttributeSet attrs, int defaultGravity) { - super(context, attrs); - reInitSuper(context, attrs); - init(context, attrs, defaultGravity); - } - /** * {@inheritDoc} * @@ -1772,27 +1778,13 @@ public class GridLayout extends ViewGroup { * defined in {@link LayoutParams}. */ public LayoutParams(Context context, AttributeSet attrs) { - this(context, attrs, Gravity.NO_GRAVITY); + super(context, attrs); + reInitSuper(context, attrs); + init(context, attrs); } // Implementation - private static boolean definesVertical(int gravity) { - return gravity > 0 && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != 0; - } - - private static boolean definesHorizontal(int gravity) { - return gravity > 0 && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != 0; - } - - private static <T> T getAlignment(T[] alignments, T fill, int min, int max, - boolean isUndefined, T defaultValue) { - if (isUndefined) { - return defaultValue; - } - return min != max ? fill : alignments[min]; - } - // Reinitialise the margins using a different default policy than MarginLayoutParams. // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state // so that a layout manager default can be accessed post set up. We need this as, at the @@ -1817,44 +1809,20 @@ public class GridLayout extends ViewGroup { } } - // Gravity. For conversion from the static the integers defined in the Gravity class, - // use Gravity.apply() to apply gravity to a view of zero size and see where it ends up. - private static Alignment getColAlignment(int gravity, int width) { - Rect r = new Rect(0, 0, 0, 0); - Gravity.apply(gravity, 0, 0, CONTAINER_BOUNDS, r); - - boolean fill = (width == MATCH_PARENT); - Alignment defaultAlignment = fill ? FILL : DEFAULT_COLUMN_ALIGNMENT; - return getAlignment(COLUMN_ALIGNMENTS, FILL, r.left, r.right, - !definesHorizontal(gravity), defaultAlignment); - } - - private static Alignment getRowAlignment(int gravity, int height) { - Rect r = new Rect(0, 0, 0, 0); - Gravity.apply(gravity, 0, 0, CONTAINER_BOUNDS, r); - - boolean fill = (height == MATCH_PARENT); - Alignment defaultAlignment = fill ? FILL : DEFAULT_ROW_ALIGNMENT; - return getAlignment(ROW_ALIGNMENTS, FILL, r.top, r.bottom, - !definesVertical(gravity), defaultAlignment); - } - - private void init(Context context, AttributeSet attrs, int defaultGravity) { + private void init(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout); try { - int gravity = a.getInt(GRAVITY, defaultGravity); + int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY); int column = a.getInt(COLUMN, DEFAULT_COLUMN); - int columnSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); - Interval hSpan = new Interval(column, column + columnSpan); - int hFlexibility = a.getInt(COLUMN_FLEXIBILITY, Spec.DEFAULT_FLEXIBILITY); - this.columnSpec = new Spec(hSpan, getColAlignment(gravity, width), hFlexibility); + int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); + int hFlexibility = a.getInt(COLUMN_FLEXIBILITY, Spec.UNDEFINED_FLEXIBILITY); + this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), hFlexibility); int row = a.getInt(ROW, DEFAULT_ROW); int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); - Interval vSpan = new Interval(row, row + rowSpan); - int vFlexibility = a.getInt(ROW_FLEXIBILITY, Spec.DEFAULT_FLEXIBILITY); - this.rowSpec = new Spec(vSpan, getRowAlignment(gravity, height), vFlexibility); + int vFlexibility = a.getInt(ROW_FLEXIBILITY, Spec.UNDEFINED_FLEXIBILITY); + this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), vFlexibility); } finally { a.recycle(); } @@ -1869,8 +1837,8 @@ public class GridLayout extends ViewGroup { * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity */ public void setGravity(int gravity) { - columnSpec = columnSpec.copyWriteAlignment(getColAlignment(gravity, width)); - rowSpec = rowSpec.copyWriteAlignment(getRowAlignment(gravity, height)); + rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false)); + columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true)); } @Override @@ -2045,7 +2013,7 @@ public class GridLayout extends ViewGroup { public int before; public int after; - public int flexibility; + public int flexibility; // we're flexible iff all included specs are flexible private Bounds() { reset(); @@ -2054,7 +2022,7 @@ public class GridLayout extends ViewGroup { protected void reset() { before = Integer.MIN_VALUE; after = Integer.MIN_VALUE; - flexibility = UNDEFINED_FLEXIBILITY; + flexibility = CAN_STRETCH; // from the above, we're flexible when empty } protected void include(int before, int after) { @@ -2064,10 +2032,7 @@ public class GridLayout extends ViewGroup { protected int size(boolean min) { if (!min) { - // Note in the usual case, components don't define anything - // leaving their flexibility is undefined and their stretchability - // defined as if the CAN_STRETCH flag was false. - if (canStretch(flexibility) && !isUndefined(flexibility)) { + if (canStretch(flexibility)) { return MAX_SIZE; } } @@ -2078,11 +2043,12 @@ public class GridLayout extends ViewGroup { return before - alignment.getAlignmentValue(c, size); } - protected void include(View c, Spec spec, GridLayout gridLayout, Axis axis) { - this.flexibility &= spec.flexibility; + protected final void include(View c, Spec spec, GridLayout gridLayout, Axis axis) { + this.flexibility &= spec.getFlexibility(); int size = gridLayout.getMeasurementIncludingMargin(c, axis.horizontal); // todo test this works correctly when the returned value is UNDEFINED - int before = spec.alignment.getAlignmentValue(c, size); + Alignment alignment = gridLayout.getAlignment(spec.alignment, axis.horizontal); + int before = alignment.getAlignmentValue(c, size); include(before, size - before); } @@ -2107,8 +2073,6 @@ public class GridLayout extends ViewGroup { * {@code x} such that {@code min <= x < max}. */ static class Interval { - private static final Interval GONE = new Interval(UNDEFINED, UNDEFINED); - /** * The minimum value. */ @@ -2186,40 +2150,38 @@ public class GridLayout extends ViewGroup { } } - /** - * A spec defines either the horizontal or vertical characteristics of a group of - * cells. - */ + /** + * A Spec defines the horizontal or vertical characteristics of a group of + * cells. Each spec. defines the <em>grid indices</em>, <em>alignment</em> and + * <em>flexibility</em> along the appropriate axis. + * <p> + * The <em>grid indices</em> are the leading and trailing edges of this cell group. + * See {@link GridLayout} for a description of the conventions used by GridLayout + * for grid indices. + * <p> + * The <em>alignment</em> property specifies how cells should be aligned in this group. + * For row groups, this specifies the vertical alignment. + * For column groups, this specifies the horizontal alignment. + */ public static class Spec { - private static final int DEFAULT_FLEXIBILITY = UNDEFINED_FLEXIBILITY; - - private static final Spec GONE = new Spec(Interval.GONE, Alignment.GONE); + private static final int UNDEFINED_FLEXIBILITY = UNDEFINED; - /** - * The grid indices of the leading and trailing edges of this cell group for the - * appropriate axis. - * <p> - * See {@link GridLayout} for a description of the conventions used by GridLayout - * for grid indices. - */ final Interval span; - /** - * Specifies how cells should be aligned in this group. - * For row groups, this specifies the vertical alignment. - * For column groups, this specifies the horizontal alignment. - */ + final Alignment alignment; - /** - * The flexibility field tells GridLayout how to derive minimum and maximum size - * values for a component. Specifications are made with respect to a child's - * 'measured size'. A child's measured size is, in turn, controlled by its - * height and width layout parameters which either specify a size or, in - * the case of {@link LayoutParams#WRAP_CONTENT WRAP_CONTENT}, defer to - * the computed size of the component. - * - * @see GridLayout#CAN_STRETCH - */ + /** + * The <em>flexibility</em> property tells GridLayout how to derive minimum and maximum size + * values for a component. Specifications are made with respect to a child's + * 'measured size'. A child's measured size is, in turn, controlled by its + * height and width layout parameters which either specify a size or, in + * the case of {@link LayoutParams#WRAP_CONTENT WRAP_CONTENT}, defer to + * the computed size of the component. + * <p> + * A cell group is flexible only if <em>all</em> of its components are flexible. + * <p> + * By default, flexibility is {@link #INFLEXIBLE} only when alignment/gravity is undefined. + */ final int flexibility; private Spec(Interval span, Alignment alignment, int flexibility) { @@ -2229,7 +2191,7 @@ public class GridLayout extends ViewGroup { } private Spec(Interval span, Alignment alignment) { - this(span, alignment, DEFAULT_FLEXIBILITY); + this(span, alignment, UNDEFINED_FLEXIBILITY); } /* Copying constructor */ @@ -2249,8 +2211,14 @@ public class GridLayout extends ViewGroup { return new Spec(span, alignment, flexibility); } - private Spec copyWriteFlexibility(int flexibility) { - return new Spec(span, alignment, flexibility); + private static int defaultFlexibility(Alignment alignment) { + return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH; + } + + int getFlexibility() { + return (flexibility != UNDEFINED_FLEXIBILITY) ? + flexibility : + defaultFlexibility(alignment); } /** @@ -2293,34 +2261,23 @@ public class GridLayout extends ViewGroup { } /** - * Return a Spec, {@code spec}, where: - * <ul> - * <li> {@code spec.span = [start, start + size]} </li> - * <li> {@code spec.alignment = alignment} </li> - * <li> {@code spec.flexibility = flexibility} </li> - * </ul> + * @deprecated Please use {@link #spec(int, int, Alignment)} instead, + * all spec's that define alignments (gravity) are assumed to be able to stretch. * - * @param start the start - * @param size the size - * @param alignment the alignment - * @param flexibility the flexibility + * @hide */ + @Deprecated public static Spec spec(int start, int size, Alignment alignment, int flexibility) { return new Spec(start, size, alignment, flexibility); } /** - * Return a Spec, {@code spec}, where: - * <ul> - * <li> {@code spec.span = [start, start + 1]} </li> - * <li> {@code spec.alignment = alignment} </li> - * <li> {@code spec.flexibility = flexibility} </li> - * </ul> + * @deprecated Please use {@link #spec(int, Alignment)} instead, + * all spec's that define alignments (gravity) are assumed to be able to stretch. * - * @param start the start - * @param alignment the alignment - * @param flexibility the flexibility + * @hide */ + @Deprecated public static Spec spec(int start, Alignment alignment, int flexibility) { return spec(start, 1, alignment, flexibility); } @@ -2337,7 +2294,7 @@ public class GridLayout extends ViewGroup { * @param alignment the alignment */ public static Spec spec(int start, int size, Alignment alignment) { - return spec(start, size, alignment, Spec.DEFAULT_FLEXIBILITY); + return spec(start, size, alignment, Spec.UNDEFINED_FLEXIBILITY); } /** @@ -2355,6 +2312,31 @@ public class GridLayout extends ViewGroup { } /** + * Return a Spec, {@code spec}, where: + * <ul> + * <li> {@code spec.span = [start, start + size]} </li> + * </ul> + * + * @param start the start + * @param size the size + */ + public static Spec spec(int start, int size) { + return spec(start, size, UNDEFINED_ALIGNMENT); + } + + /** + * Return a Spec, {@code spec}, where: + * <ul> + * <li> {@code spec.span = [start, start + 1]} </li> + * </ul> + * + * @param start the start index + */ + public static Spec spec(int start) { + return spec(start, 1); + } + + /** * Alignments specify where a view should be placed within a cell group and * what size it should be. * <p> @@ -2376,13 +2358,6 @@ public class GridLayout extends ViewGroup { * <p> */ public static abstract class Alignment { - private static final Alignment GONE = new Alignment() { - public int getAlignmentValue(View view, int viewSize) { - assert false; - return 0; - } - }; - Alignment() { } @@ -2422,11 +2397,16 @@ public class GridLayout extends ViewGroup { } } + private static final Alignment UNDEFINED_ALIGNMENT = new Alignment() { + public int getAlignmentValue(View view, int viewSize) { + return UNDEFINED; + } + }; + private static final Alignment LEADING = new Alignment() { public int getAlignmentValue(View view, int viewSize) { return 0; } - }; private static final Alignment TRAILING = new Alignment() { @@ -2542,38 +2522,17 @@ public class GridLayout extends ViewGroup { return (flexibility & CAN_STRETCH) != 0; } - private static boolean isUndefined(int flexibility) { - return (flexibility & UNDEFINED) != 0; - } - - /** - * Indicates that a view requests precisely the size specified by its layout parameters. - * - * @see Spec#flexibility - */ - private static final int NONE = 0; - - /** - * Indicates that a view's size should lie between its minimum and the size specified by - * its layout parameters. - * - * @see Spec#flexibility - */ - private static final int CAN_SHRINK = 1; + private static final int INFLEXIBLE = 0; /** * Indicates that a view's size should be greater than or equal to the size specified by * its layout parameters. * - * @see Spec#flexibility - */ - public static final int CAN_STRETCH = 2; - - /** - * A default value for flexibility. + * @deprecated Please use {@link #spec(int, int, Alignment)} instead, + * all spec's that define alignment (gravity) are assumed to able to stretch. * - * @see Spec#flexibility + * @hide */ - private static final int UNDEFINED_FLEXIBILITY = UNDEFINED | CAN_SHRINK | CAN_STRETCH; - + @Deprecated + public static final int CAN_STRETCH = 2; } 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/com/android/internal/textservice/ITextServicesManager.aidl b/core/java/com/android/internal/textservice/ITextServicesManager.aidl index ad0c1ff3f816..2a045e3ad754 100644 --- a/core/java/com/android/internal/textservice/ITextServicesManager.aidl +++ b/core/java/com/android/internal/textservice/ITextServicesManager.aidl @@ -32,4 +32,5 @@ interface ITextServicesManager { in ITextServicesSessionListener tsListener, in ISpellCheckerSessionListener scListener); oneway void finishSpellCheckerService(in ISpellCheckerSessionListener listener); + SpellCheckerInfo[] getEnabledSpellCheckers(); } 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/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp index 0c81634a4a08..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; @@ -537,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()); +} + + + // ---------------------------------------------------------------------------- /* @@ -608,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_text_format_Time.cpp b/core/jni/android_text_format_Time.cpp index c152aa8351af..69c6021e22e8 100644 --- a/core/jni/android_text_format_Time.cpp +++ b/core/jni/android_text_format_Time.cpp @@ -641,7 +641,7 @@ static JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "normalize", "(Z)J", (void*)android_text_format_Time_normalize }, { "switchTimezone", "(Ljava/lang/String;)V", (void*)android_text_format_Time_switchTimezone }, - { "compare", "(Landroid/text/format/Time;Landroid/text/format/Time;)I", (void*)android_text_format_Time_compare }, + { "nativeCompare", "(Landroid/text/format/Time;Landroid/text/format/Time;)I", (void*)android_text_format_Time_compare }, { "format1", "(Ljava/lang/String;)Ljava/lang/String;", (void*)android_text_format_Time_format }, { "format2445", "()Ljava/lang/String;", (void*)android_text_format_Time_format2445 }, { "toString", "()Ljava/lang/String;", (void*)android_text_format_Time_toString }, 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/res/layout/wifi_p2p_go_negotiation_request_alert.xml b/core/res/res/layout/wifi_p2p_go_negotiation_request_alert.xml new file mode 100644 index 000000000000..41ca6571cbc0 --- /dev/null +++ b/core/res/res/layout/wifi_p2p_go_negotiation_request_alert.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + 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 7d7aea920012..88ab983e40cd 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2278,6 +2278,8 @@ <flag name="feedbackVisual" value="0x00000008" /> <!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_GENERIC} feedback. --> <flag name="feedbackGeneric" value="0x00000010" /> + <!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_ALL_MASK} feedback. --> + <flag name="feedbackAllMask" value="0xffffffff" /> </attr> <!-- The minimal period in milliseconds between two accessibility events of the same type are sent to this serivce. This setting can be changed at runtime by calling @@ -3359,18 +3361,16 @@ The default is LEFT | BASELINE. See {@link android.widget.GridLayout.LayoutParams#setGravity(int)}. --> <attr name="layout_gravity" /> - <!-- A value specifying how much deficit or excess width this component can accomodate. - The default is FIXED. --> + <!-- {@deprecated To make a column group lexible, ensure that every component in the + group defines a horizontal gravity.} --> <attr name="layout_columnFlexibility" > - <!-- If possible, width should be greater than or equal to the specified width. - See {@link android.widget.GridLayout#CAN_STRETCH}. --> + <enum name="inflexible" value="0" /> <enum name="canStretch" value="2" /> </attr> - <!-- A value specifying how much deficit or excess height this component can accomodate. - The default is FIXED. --> + <!-- {@deprecated To make a row group flexible, ensure that every component in the + group defines a vertical gravity.} --> <attr name="layout_rowFlexibility" > - <!-- If possible, height should be greater than or equal to the specified height. - See {@link android.widget.GridLayout#CAN_STRETCH}. --> + <enum name="inflexible" value="0" /> <enum name="canStretch" value="2" /> </attr> </declare-styleable> 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/strings.xml b/core/res/res/values/strings.xml index feac38d6d695..e1a31f4d3e46 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2583,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> diff --git a/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java index bbf1696eb25b..4814c61abe7c 100644 --- a/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java +++ b/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java @@ -65,13 +65,13 @@ public class RecycleAccessibilityEventTest extends TestCase { assertEquals(0, first.getText().size()); assertFalse(first.isChecked()); assertNull(first.getContentDescription()); - assertEquals(0, first.getItemCount()); + assertEquals(-1, first.getItemCount()); assertEquals(AccessibilityEvent.INVALID_POSITION, first.getCurrentItemIndex()); assertFalse(first.isEnabled()); assertFalse(first.isPassword()); - assertEquals(0, first.getFromIndex()); - assertEquals(0, first.getAddedCount()); - assertEquals(0, first.getRemovedCount()); + assertEquals(-1, first.getFromIndex()); + assertEquals(-1, first.getAddedCount()); + assertEquals(-1, first.getRemovedCount()); // get another event from the pool (this must be the recycled first) AccessibilityEvent second = AccessibilityEvent.obtain(); diff --git a/drm/common/IDrmManagerService.cpp b/drm/common/IDrmManagerService.cpp index 2d8e877b97d1..986f32c4037e 100644 --- a/drm/common/IDrmManagerService.cpp +++ b/drm/common/IDrmManagerService.cpp @@ -110,11 +110,11 @@ static void clearDecryptHandle(DecryptHandle* handle) { handle->extendedData.clear(); } -int BpDrmManagerService::addUniqueId(int uniqueId) { +int BpDrmManagerService::addUniqueId(bool isNative) { LOGV("add uniqueid"); Parcel data, reply; data.writeInterfaceToken(IDrmManagerService::getInterfaceDescriptor()); - data.writeInt32(uniqueId); + data.writeInt32(isNative); remote()->transact(ADD_UNIQUEID, data, &reply); return reply.readInt32(); } diff --git a/drm/drmserver/DrmManager.cpp b/drm/drmserver/DrmManager.cpp index 1809619d0262..3e4fe8c07d23 100644 --- a/drm/drmserver/DrmManager.cpp +++ b/drm/drmserver/DrmManager.cpp @@ -49,32 +49,42 @@ DrmManager::~DrmManager() { } -int DrmManager::addUniqueId(int uniqueId) { +int DrmManager::addUniqueId(bool isNative) { Mutex::Autolock _l(mLock); - if (0 == uniqueId) { - int temp = 0; - bool foundUniqueId = false; - srand(time(NULL)); - - while (!foundUniqueId) { - const int size = mUniqueIdVector.size(); - temp = rand() % 100; - - int index = 0; - for (; index < size; ++index) { - if (mUniqueIdVector.itemAt(index) == temp) { - foundUniqueId = false; - break; - } - } - if (index == size) { - foundUniqueId = true; + + int temp = 0; + bool foundUniqueId = false; + const int size = mUniqueIdVector.size(); + const int uniqueIdRange = 0xfff; + int maxLoopTimes = (uniqueIdRange - 1) / 2; + srand(time(NULL)); + + while (!foundUniqueId) { + temp = rand() & uniqueIdRange; + + if (isNative) { + // set a flag to differentiate DrmManagerClient + // created from native side and java side + temp |= 0x1000; + } + + int index = 0; + for (; index < size; ++index) { + if (mUniqueIdVector.itemAt(index) == temp) { + foundUniqueId = false; + break; } } - uniqueId = temp; + if (index == size) { + foundUniqueId = true; + } + + maxLoopTimes --; + LOG_FATAL_IF(maxLoopTimes <= 0, "cannot find an unique ID for this session"); } - mUniqueIdVector.push(uniqueId); - return uniqueId; + + mUniqueIdVector.push(temp); + return temp; } void DrmManager::removeUniqueId(int uniqueId) { diff --git a/drm/drmserver/DrmManagerService.cpp b/drm/drmserver/DrmManagerService.cpp index 583669e980a0..7ebcac39380b 100644 --- a/drm/drmserver/DrmManagerService.cpp +++ b/drm/drmserver/DrmManagerService.cpp @@ -78,8 +78,8 @@ DrmManagerService::~DrmManagerService() { delete mDrmManager; mDrmManager = NULL; } -int DrmManagerService::addUniqueId(int uniqueId) { - return mDrmManager->addUniqueId(uniqueId); +int DrmManagerService::addUniqueId(bool isNative) { + return mDrmManager->addUniqueId(isNative); } void DrmManagerService::removeUniqueId(int uniqueId) { diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java index f3a034307ad9..9a7194c4d637 100755 --- a/drm/java/android/drm/DrmManagerClient.java +++ b/drm/java/android/drm/DrmManagerClient.java @@ -248,9 +248,7 @@ public class DrmManagerClient { mEventHandler = new EventHandler(eventThread.getLooper()); // save the unique id - mUniqueId = hashCode(); - - _initialize(mUniqueId, new WeakReference<DrmManagerClient>(this)); + mUniqueId = _initialize(new WeakReference<DrmManagerClient>(this)); } protected void finalize() { @@ -794,7 +792,7 @@ public class DrmManagerClient { } // private native interfaces - private native void _initialize(int uniqueId, Object weak_this); + private native int _initialize(Object weak_this); private native void _finalize(int uniqueId); diff --git a/drm/jni/android_drm_DrmManagerClient.cpp b/drm/jni/android_drm_DrmManagerClient.cpp index e13183990410..80a8447331a5 100644 --- a/drm/jni/android_drm_DrmManagerClient.cpp +++ b/drm/jni/android_drm_DrmManagerClient.cpp @@ -224,11 +224,12 @@ static sp<DrmManagerClientImpl> getDrmManagerClientImpl(JNIEnv* env, jobject thi return sp<DrmManagerClientImpl>(client); } -static void android_drm_DrmManagerClient_initialize( - JNIEnv* env, jobject thiz, jint uniqueId, jobject weak_thiz) { +static jint android_drm_DrmManagerClient_initialize( + JNIEnv* env, jobject thiz, jobject weak_thiz) { LOGV("initialize - Enter"); - sp<DrmManagerClientImpl> drmManager = DrmManagerClientImpl::create(&uniqueId); + int uniqueId = 0; + sp<DrmManagerClientImpl> drmManager = DrmManagerClientImpl::create(&uniqueId, false); drmManager->addClient(uniqueId); // Set the listener to DrmManager @@ -237,6 +238,8 @@ static void android_drm_DrmManagerClient_initialize( setDrmManagerClientImpl(env, thiz, drmManager); LOGV("initialize - Exit"); + + return uniqueId; } static void android_drm_DrmManagerClient_finalize(JNIEnv* env, jobject thiz, jint uniqueId) { @@ -711,7 +714,7 @@ static jobject android_drm_DrmManagerClient_closeConvertSession( static JNINativeMethod nativeMethods[] = { - {"_initialize", "(ILjava/lang/Object;)V", + {"_initialize", "(Ljava/lang/Object;)I", (void*)android_drm_DrmManagerClient_initialize}, {"_finalize", "(I)V", diff --git a/drm/libdrmframework/DrmManagerClient.cpp b/drm/libdrmframework/DrmManagerClient.cpp index b50199f649f0..c9c0d57961b9 100644 --- a/drm/libdrmframework/DrmManagerClient.cpp +++ b/drm/libdrmframework/DrmManagerClient.cpp @@ -24,7 +24,7 @@ using namespace android; DrmManagerClient::DrmManagerClient(): mUniqueId(0), mDrmManagerClientImpl(NULL) { - mDrmManagerClientImpl = DrmManagerClientImpl::create(&mUniqueId); + mDrmManagerClientImpl = DrmManagerClientImpl::create(&mUniqueId, true); mDrmManagerClientImpl->addClient(mUniqueId); } diff --git a/drm/libdrmframework/DrmManagerClientImpl.cpp b/drm/libdrmframework/DrmManagerClientImpl.cpp index a36bd4ae4023..67f58ca8fe20 100644 --- a/drm/libdrmframework/DrmManagerClientImpl.cpp +++ b/drm/libdrmframework/DrmManagerClientImpl.cpp @@ -33,13 +33,10 @@ sp<IDrmManagerService> DrmManagerClientImpl::sDrmManagerService; sp<DrmManagerClientImpl::DeathNotifier> DrmManagerClientImpl::sDeathNotifier; const String8 DrmManagerClientImpl::EMPTY_STRING(""); -DrmManagerClientImpl* DrmManagerClientImpl::create(int* pUniqueId) { - if (0 == *pUniqueId) { - int uniqueId = getDrmManagerService()->addUniqueId(*pUniqueId); - *pUniqueId = uniqueId; - } else { - getDrmManagerService()->addUniqueId(*pUniqueId); - } +DrmManagerClientImpl* DrmManagerClientImpl::create( + int* pUniqueId, bool isNative) { + *pUniqueId = getDrmManagerService()->addUniqueId(isNative); + return new DrmManagerClientImpl(); } diff --git a/drm/libdrmframework/include/DrmManager.h b/drm/libdrmframework/include/DrmManager.h index af2c2a8506d3..ac2b94606576 100644 --- a/drm/libdrmframework/include/DrmManager.h +++ b/drm/libdrmframework/include/DrmManager.h @@ -53,7 +53,7 @@ public: virtual ~DrmManager(); public: - int addUniqueId(int uniqueId); + int addUniqueId(bool isNative); void removeUniqueId(int uniqueId); diff --git a/drm/libdrmframework/include/DrmManagerClientImpl.h b/drm/libdrmframework/include/DrmManagerClientImpl.h index 564896bdf366..e3338d9e8679 100644 --- a/drm/libdrmframework/include/DrmManagerClientImpl.h +++ b/drm/libdrmframework/include/DrmManagerClientImpl.h @@ -38,7 +38,7 @@ private: DrmManagerClientImpl() { } public: - static DrmManagerClientImpl* create(int* pUniqueId); + static DrmManagerClientImpl* create(int* pUniqueId, bool isNative); static void remove(int uniqueId); diff --git a/drm/libdrmframework/include/DrmManagerService.h b/drm/libdrmframework/include/DrmManagerService.h index 227496a8bac0..9cb5804e6915 100644 --- a/drm/libdrmframework/include/DrmManagerService.h +++ b/drm/libdrmframework/include/DrmManagerService.h @@ -46,7 +46,7 @@ private: virtual ~DrmManagerService(); public: - int addUniqueId(int uniqueId); + int addUniqueId(bool isNative); void removeUniqueId(int uniqueId); diff --git a/drm/libdrmframework/include/IDrmManagerService.h b/drm/libdrmframework/include/IDrmManagerService.h index 7727e551a5ef..b9618bbc27aa 100644 --- a/drm/libdrmframework/include/IDrmManagerService.h +++ b/drm/libdrmframework/include/IDrmManagerService.h @@ -81,7 +81,7 @@ public: DECLARE_META_INTERFACE(DrmManagerService); public: - virtual int addUniqueId(int uniqueId) = 0; + virtual int addUniqueId(bool isNative) = 0; virtual void removeUniqueId(int uniqueId) = 0; @@ -167,7 +167,7 @@ public: BpDrmManagerService(const sp<IBinder>& impl) : BpInterface<IDrmManagerService>(impl) {} - virtual int addUniqueId(int uniqueId); + virtual int addUniqueId(bool isNative); virtual void removeUniqueId(int uniqueId); diff --git a/drm/libdrmframework/plugins/common/include/DrmEngineBase.h b/drm/libdrmframework/plugins/common/include/DrmEngineBase.h index b61e3d353a27..4a5afcf42c6b 100644 --- a/drm/libdrmframework/plugins/common/include/DrmEngineBase.h +++ b/drm/libdrmframework/plugins/common/include/DrmEngineBase.h @@ -143,7 +143,13 @@ protected: * Register a callback to be invoked when the caller required to * receive necessary information * - * @param[in] uniqueId Unique identifier for a session + * @param[in] uniqueId Unique identifier for a session. uniqueId is a random + * number generated in the DRM service. If the DrmManagerClient + * is created in native code, uniqueId will be a number ranged + * from 0x1000 to 0x1fff. If it comes from Java code, the uniqueId + * will be a number ranged from 0x00 to 0xfff. So bit 0x1000 in + * uniqueId could be used in DRM plugins to differentiate native + * OnInfoListener and Java OnInfoListener. * @param[in] infoListener Listener * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h index 134c208f4d26..2a8e7255602b 100644 --- a/include/gui/SurfaceTexture.h +++ b/include/gui/SurfaceTexture.h @@ -211,7 +211,6 @@ protected: // all slots. void freeAllBuffers(); static bool isExternalFormat(uint32_t format); - static GLenum getTextureTarget(uint32_t format); private: @@ -348,10 +347,6 @@ 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 support the getCurrentBuffer method. diff --git a/include/media/AudioEffect.h b/include/media/AudioEffect.h index 496b23e5f04f..1417416538dc 100644 --- a/include/media/AudioEffect.h +++ b/include/media/AudioEffect.h @@ -21,6 +21,7 @@ #include <sys/types.h> #include <media/IAudioFlinger.h> +#include <media/IAudioPolicyService.h> #include <media/IEffect.h> #include <media/IEffectClient.h> #include <hardware/audio_effect.h> @@ -111,6 +112,36 @@ public: /* + * Returns a list of descriptors corresponding to the pre processings enabled by default + * on an AudioRecord with the supplied audio session ID. + * + * Parameters: + * audioSession: audio session ID. + * descriptors: address where the effect descriptors should be returned. + * count: as input, the maximum number of descriptor than should be returned + * as output, the number of descriptor returned if status is NO_ERROR or the actual + * number of enabled pre processings if status is NO_MEMORY + * + * Returned status (from utils/Errors.h) can be: + * NO_ERROR successful operation. + * NO_MEMORY the number of descriptor to return is more than the maximum number + * indicated by count. + * PERMISSION_DENIED could not get AudioFlinger interface + * NO_INIT effect library failed to initialize + * BAD_VALUE invalid audio session or descriptor pointers + * + * Returned value + * *descriptor updated with descriptors of pre processings enabled by default + * *count number of descriptors returned if returned status is N_ERROR. + * total number of pre processing enabled by default if returned status is + * NO_MEMORY. This happens if the count passed as input is less than the number + * of descriptors to return + */ + static status_t queryDefaultPreProcessing(int audioSession, + effect_descriptor_t *descriptors, + uint32_t *count); + + /* * Events used by callback function (effect_callback_t). */ enum event_type { diff --git a/include/media/IAudioPolicyService.h b/include/media/IAudioPolicyService.h index 86b9f85f8635..ed265e16d704 100644 --- a/include/media/IAudioPolicyService.h +++ b/include/media/IAudioPolicyService.h @@ -85,6 +85,9 @@ public: int id) = 0; virtual status_t unregisterEffect(int id) = 0; virtual bool isStreamActive(int stream, uint32_t inPastMs = 0) const = 0; + virtual status_t queryDefaultPreProcessing(int audioSession, + effect_descriptor_t *descriptors, + uint32_t *count) = 0; }; diff --git a/include/media/IMediaRecorder.h b/include/media/IMediaRecorder.h index 007aea6c0362..ec84e2574aed 100644 --- a/include/media/IMediaRecorder.h +++ b/include/media/IMediaRecorder.h @@ -43,7 +43,6 @@ public: virtual status_t setAudioEncoder(int ae) = 0; virtual status_t setOutputFile(const char* path) = 0; virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0; - virtual status_t setOutputFileAuxiliary(int fd) = 0; virtual status_t setVideoSize(int width, int height) = 0; virtual status_t setVideoFrameRate(int frames_per_second) = 0; virtual status_t setParameters(const String8& params) = 0; diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h index 72d3736296d1..30db64246a34 100644 --- a/include/media/mediarecorder.h +++ b/include/media/mediarecorder.h @@ -215,7 +215,6 @@ public: status_t setAudioEncoder(int ae); status_t setOutputFile(const char* path); status_t setOutputFile(int fd, int64_t offset, int64_t length); - status_t setOutputFileAuxiliary(int fd); status_t setVideoSize(int width, int height); status_t setVideoFrameRate(int frames_per_second); status_t setParameters(const String8& params); @@ -249,7 +248,6 @@ private: bool mIsAudioEncoderSet; bool mIsVideoEncoderSet; bool mIsOutputFileSet; - bool mIsAuxiliaryOutputFileSet; Mutex mLock; Mutex mNotifyLock; }; diff --git a/include/media/stagefright/CameraSourceTimeLapse.h b/include/media/stagefright/CameraSourceTimeLapse.h index f07ebbab2803..0e264c7be02a 100644 --- a/include/media/stagefright/CameraSourceTimeLapse.h +++ b/include/media/stagefright/CameraSourceTimeLapse.h @@ -53,27 +53,10 @@ public: void startQuickReadReturns(); private: - // If true, will use still camera takePicture() for time lapse frames - // If false, will use the videocamera frames instead. - bool mUseStillCameraForTimeLapse; - - // Size of picture taken from still camera. This may be larger than the size - // of the video, as still camera may not support the exact video resolution - // demanded. See setPictureSizeToClosestSupported(). - int32_t mPictureWidth; - int32_t mPictureHeight; - // size of the encoded video. int32_t mVideoWidth; int32_t mVideoHeight; - // True if we need to crop the still camera image to get the video frame. - bool mNeedCropping; - - // Start location of the cropping rectangle. - int32_t mCropRectStartX; - int32_t mCropRectStartY; - // Time between capture of two frames during time lapse recording // Negative value indicates that timelapse is disabled. int64_t mTimeBetweenTimeLapseFrameCaptureUs; @@ -84,9 +67,6 @@ private: // Real timestamp of the last encoded time lapse frame int64_t mLastTimeLapseFrameRealTimestampUs; - // Thread id of thread which takes still picture and sleeps in a loop. - pthread_t mThreadTimeLapse; - // Variable set in dataCallbackTimestamp() to help skipCurrentFrame() // to know if current frame needs to be skipped. bool mSkipCurrentFrame; @@ -111,9 +91,6 @@ private: // Lock for accessing quick stop variables. Mutex mQuickStopLock; - // Condition variable to wake up still picture thread. - Condition mTakePictureCondition; - // mQuickStop is set to true if we use quick read() returns, otherwise it is set // to false. Once in this mode read() return a copy of the last read frame // with the same time stamp. See startQuickReadReturns(). @@ -148,32 +125,13 @@ private: // Wrapper over CameraSource::read() to implement quick stop. virtual status_t read(MediaBuffer **buffer, const ReadOptions *options = NULL); - // For still camera case starts a thread which calls camera's takePicture() - // in a loop. For video camera case, just starts the camera's video recording. - virtual void startCameraRecording(); - - // For still camera case joins the thread created in startCameraRecording(). // For video camera case, just stops the camera's video recording. virtual void stopCameraRecording(); - // For still camera case don't need to do anything as memory is locally - // allocated with refcounting. - // For video camera case just tell the camera to release the frame. - virtual void releaseRecordingFrame(const sp<IMemory>& frame); - // mSkipCurrentFrame is set to true in dataCallbackTimestamp() if the current // frame needs to be skipped and this function just returns the value of mSkipCurrentFrame. virtual bool skipCurrentFrame(int64_t timestampUs); - // Handles the callback to handle raw frame data from the still camera. - // Creates a copy of the frame data as the camera can reuse the frame memory - // once this callback returns. The function also sets a new timstamp corresponding - // to one frame time ahead of the last encoded frame's time stamp. It then - // calls dataCallbackTimestamp() of the base class with the copied data and the - // modified timestamp, which will think that it recieved the frame from a video - // camera and proceed as usual. - virtual void dataCallback(int32_t msgType, const sp<IMemory> &data); - // In the video camera case calls skipFrameAndModifyTimeStamp() to modify // timestamp and set mSkipCurrentFrame. // Then it calls the base CameraSource::dataCallbackTimestamp() @@ -189,24 +147,6 @@ private: // Otherwise returns false. bool trySettingVideoSize(int32_t width, int32_t height); - // The still camera may not support the demanded video width and height. - // We look for the supported picture sizes from the still camera and - // choose the smallest one with either dimensions higher than the corresponding - // video dimensions. The still picture will be cropped to get the video frame. - // The function returns true if the camera supports picture sizes greater than - // or equal to the passed in width and height, and false otherwise. - bool setPictureSizeToClosestSupported(int32_t width, int32_t height); - - // Computes the offset of the rectangle from where to start cropping the - // still image into the video frame. We choose the center of the image to be - // cropped. The offset is stored in (mCropRectStartX, mCropRectStartY). - bool computeCropRectangleOffset(); - - // Crops the source data into a smaller image starting at - // (mCropRectStartX, mCropRectStartY) and of the size of the video frame. - // The data is returned into a newly allocated IMemory. - sp<IMemory> cropYUVImage(const sp<IMemory> &source_data); - // When video camera is used for time lapse capture, returns true // until enough time has passed for the next time lapse frame. When // the frame needs to be encoded, it returns false and also modifies @@ -217,22 +157,6 @@ private: // Wrapper to enter threadTimeLapseEntry() static void *ThreadTimeLapseWrapper(void *me); - // Runs a loop which sleeps until a still picture is required - // and then calls mCamera->takePicture() to take the still picture. - // Used only in the case mUseStillCameraForTimeLapse = true. - void threadTimeLapseEntry(); - - // Wrapper to enter threadStartPreview() - static void *ThreadStartPreviewWrapper(void *me); - - // Starts the camera's preview. - void threadStartPreview(); - - // Starts thread ThreadStartPreviewWrapper() for restarting preview. - // Needs to be done in a thread so that dataCallback() which calls this function - // can return, and the camera can know that takePicture() is done. - void restartPreview(); - // Creates a copy of source_data into a new memory of final type MemoryBase. sp<IMemory> createIMemoryCopy(const sp<IMemory> &source_data); diff --git a/include/media/stagefright/MetadataBufferType.h b/include/media/stagefright/MetadataBufferType.h index 52a3257d7b91..4eaf8ac2990e 100644 --- a/include/media/stagefright/MetadataBufferType.h +++ b/include/media/stagefright/MetadataBufferType.h @@ -69,6 +69,16 @@ typedef enum { * kMetadataBufferTypeGrallocSource is used to indicate that * the payload of the metadata buffers can be interpreted as * a buffer_handle_t. + * So in this case,the metadata that the encoder receives + * will have a byte stream that consists of two parts: + * 1. First, there is an integer indicating that it is a GRAlloc + * source (kMetadataBufferTypeGrallocSource) + * 2. This is followed by the buffer_handle_t that is a handle to the + * GRalloc buffer. The encoder needs to interpret this GRalloc handle + * and encode the frames. + * -------------------------------------------------------------- + * | kMetadataBufferTypeGrallocSource | sizeof(buffer_handle_t) | + * -------------------------------------------------------------- */ kMetadataBufferTypeGrallocSource = 1, 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 index 56bd9c315005..fab258c2272d 100644 --- a/include/media/stagefright/SurfaceMediaSource.h +++ b/include/media/stagefright/SurfaceMediaSource.h @@ -63,6 +63,10 @@ public: MediaBuffer **buffer, const ReadOptions *options = NULL); virtual sp<MetaData> getFormat(); + // Pass the metadata over to the buffer, call when you have the lock + void passMetadataBufferLocked(MediaBuffer **buffer); + bool checkBufferMatchesSlot(int slot, MediaBuffer *buffer); + // Get / Set the frame rate used for encoding. Default fps = 30 status_t setFrameRate(int32_t fps) ; int32_t getFrameRate( ) const; @@ -152,7 +156,7 @@ public: status_t setBufferCountServer(int bufferCount); // getTimestamp retrieves the timestamp associated with the image - // set by the most recent call to updateFrameInfoLocked(). + // set by the most recent call to read() // // The timestamp is in nanoseconds, and is monotonically increasing. Its // other semantics (zero point, etc) are source-dependent and should be 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/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 c190195b53d0..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), @@ -651,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) { @@ -678,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; @@ -692,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; @@ -717,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]) { @@ -959,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/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp index 0fac6cda05a4..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)); diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index 4a40a63485ac..996acd5bceac 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -181,11 +181,8 @@ void GradientCache::generateTexture(SkBitmap* bitmap, Texture* texture) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap->rowBytesAsPixels(), texture->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getPixels()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + texture->setFilter(GL_LINEAR, GL_LINEAR); + texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); } }; // namespace uirenderer diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 3c2d80de3a79..0c536b077021 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -51,8 +51,6 @@ struct Layer { texture.width = layerWidth; texture.height = layerHeight; colorFilter = NULL; - firstFilter = true; - firstWrap = true; } ~Layer() { @@ -150,27 +148,11 @@ struct Layer { } void setWrap(GLenum wrapS, GLenum wrapT, bool bindTexture = false, bool force = false) { - if (firstWrap || force || wrapS != texture.wrapS || wrapT != texture.wrapT) { - firstWrap = true; - texture.setWrap(wrapS, wrapT); - if (bindTexture) { - glBindTexture(renderTarget, texture.id); - } - glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS); - glTexParameteri(renderTarget, GL_TEXTURE_WRAP_T, wrapT); - } + texture.setWrap(wrapS, wrapT, bindTexture, force, renderTarget); } void setFilter(GLenum min, GLenum mag, bool bindTexture = false, bool force = false) { - if (firstFilter || force || min != texture.minFilter || mag != texture.magFilter) { - firstFilter = false; - texture.setFilter(min, mag); - if (bindTexture) { - glBindTexture(renderTarget, texture.id); - } - glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min); - glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag); - } + texture.setFilter(min, mag,bindTexture, force, renderTarget); } inline bool isCacheable() { @@ -296,8 +278,6 @@ private: */ mat4 texTransform; - bool firstFilter; - bool firstWrap; }; // struct Layer }; // namespace uirenderer diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp index 1a15e8788955..36083afd53bc 100644 --- a/libs/hwui/LayerCache.cpp +++ b/libs/hwui/LayerCache.cpp @@ -107,7 +107,7 @@ Layer* LayerCache::get(const uint32_t width, const uint32_t height) { layer->generateTexture(); layer->bindTexture(); layer->setFilter(GL_NEAREST, GL_NEAREST); - layer->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + layer->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, false); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); #if DEBUG_LAYERS diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 85a976222d88..e67abbd7e17d 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -1293,16 +1293,16 @@ void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, Sk SkXfermode::Mode mode; getAlphaAndMode(paint, &alpha, &mode); - setTextureWrapModes(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); - float x = left; float y = top; + GLenum filter = GL_LINEAR; bool ignoreTransform = false; if (mSnapshot->transform->isPureTranslate()) { x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f); y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f); ignoreTransform = true; + filter = GL_NEAREST; } setupDraw(); @@ -1315,7 +1315,11 @@ void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, Sk setupDrawBlending(true, mode); setupDrawProgram(); setupDrawModelView(x, y, x + texture->width, y + texture->height, ignoreTransform); + setupDrawTexture(texture->id); + texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + texture->setFilter(filter, filter); + setupDrawPureColorUniforms(); setupDrawColorFilterUniforms(); setupDrawShaderUniforms(); @@ -1379,7 +1383,9 @@ void OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHei Texture* texture = mCaches.textureCache.get(bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); - setTextureWrapModes(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + + texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, true); + texture->setFilter(GL_LINEAR, GL_LINEAR, true); int alpha; SkXfermode::Mode mode; @@ -1462,7 +1468,7 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, Texture* texture = mCaches.textureCache.get(bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); - setTextureWrapModes(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, true); const float width = texture->width; const float height = texture->height; @@ -1483,11 +1489,13 @@ void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, const float x = (int) floorf(dstLeft + mSnapshot->transform->getTranslateX() + 0.5f); const float y = (int) floorf(dstTop + mSnapshot->transform->getTranslateY() + 0.5f); + texture->setFilter(GL_NEAREST, GL_NEAREST, true); drawTextureMesh(x, y, x + (dstRight - dstLeft), y + (dstBottom - dstTop), texture->id, alpha / 255.0f, mode, texture->blend, &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, false, true); } else { + texture->setFilter(GL_LINEAR, GL_LINEAR, true); drawTextureMesh(dstLeft, dstTop, dstRight, dstBottom, texture->id, alpha / 255.0f, mode, texture->blend, &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount); @@ -1507,7 +1515,8 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int Texture* texture = mCaches.textureCache.get(bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); - setTextureWrapModes(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, true); + texture->setFilter(GL_LINEAR, GL_LINEAR, true); int alpha; SkXfermode::Mode mode; @@ -2411,16 +2420,18 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b SkXfermode::Mode mode; getAlphaAndMode(paint, &alpha, &mode); - setTextureWrapModes(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, true); if (mSnapshot->transform->isPureTranslate()) { const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f); const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f); + texture->setFilter(GL_NEAREST, GL_NEAREST, true); drawTextureMesh(x, y, x + texture->width, y + texture->height, texture->id, alpha / 255.0f, mode, texture->blend, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, false, true); } else { + texture->setFilter(GL_LINEAR, GL_LINEAR, true); drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode, texture->blend, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount); @@ -2550,22 +2561,5 @@ SkXfermode::Mode OpenGLRenderer::getXfermode(SkXfermode* mode) { return resultMode; } -void OpenGLRenderer::setTextureWrapModes(Texture* texture, GLenum wrapS, GLenum wrapT) { - bool bound = false; - if (wrapS != texture->wrapS) { - glBindTexture(GL_TEXTURE_2D, texture->id); - bound = true; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS); - texture->wrapS = wrapS; - } - if (wrapT != texture->wrapT) { - if (!bound) { - glBindTexture(GL_TEXTURE_2D, texture->id); - } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT); - texture->wrapT = wrapT; - } -} - }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 0a3d509058fc..fa893f02a69d 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -464,12 +464,6 @@ private: } /** - * Sets the wrap modes for the specified texture. The wrap modes are modified - * only when needed. - */ - inline void setTextureWrapModes(Texture* texture, GLenum wrapS, GLenum wrapT); - - /** * Enable or disable blending as necessary. This function sets the appropriate * blend function based on the specified xfermode. */ diff --git a/libs/hwui/ShapeCache.h b/libs/hwui/ShapeCache.h index f4d968639df2..33953be5d20d 100644 --- a/libs/hwui/ShapeCache.h +++ b/libs/hwui/ShapeCache.h @@ -624,11 +624,8 @@ void ShapeCache<Entry>::generateTexture(SkBitmap& bitmap, Texture* texture) { glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + texture->setFilter(GL_LINEAR, GL_LINEAR); + texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); } }; // namespace uirenderer diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 8878c709809e..1a60dca04248 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -77,14 +77,7 @@ void SkiaShader::setupProgram(Program* program, const mat4& modelView, const Sna void SkiaShader::bindTexture(Texture* texture, GLenum wrapS, GLenum wrapT) { glBindTexture(GL_TEXTURE_2D, texture->id); - if (wrapS != texture->wrapS) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS); - texture->wrapS = wrapS; - } - if (wrapT != texture->wrapT) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT); - texture->wrapT = wrapT; - } + texture->setWrap(wrapS, wrapT); } void SkiaShader::computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView) { @@ -151,6 +144,9 @@ void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView, // Uniforms bindTexture(texture, mWrapS, mWrapT); + GLenum filter = textureTransform.isPureTranslate() ? GL_NEAREST : GL_LINEAR; + texture->setFilter(filter, filter); + glUniform1i(program->getUniform("bitmapSampler"), textureSlot); glUniformMatrix4fv(program->getUniform("textureTransform"), 1, GL_FALSE, &textureTransform.data[0]); diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp index 8f6f860ff14e..a3ee63b08555 100644 --- a/libs/hwui/TextDropShadowCache.cpp +++ b/libs/hwui/TextDropShadowCache.cpp @@ -137,11 +137,8 @@ ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, shadow.image); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + texture->setFilter(GL_LINEAR, GL_LINEAR); + texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); if (size < mMaxSize) { if (mDebugEnabled) { diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index c6ae326d0303..48229b6e4adb 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -35,16 +35,45 @@ struct Texture { minFilter = GL_NEAREST; magFilter = GL_NEAREST; + + firstFilter = true; + firstWrap = true; } - void setWrap(GLenum wrapS, GLenum wrapT) { - this->wrapS = wrapS; - this->wrapT = wrapT; + void setWrap(GLenum wrapS, GLenum wrapT, bool bindTexture = false, bool force = false, + GLenum renderTarget = GL_TEXTURE_2D) { + + if (firstWrap || force || wrapS != this->wrapS || wrapT != this->wrapT) { + firstWrap = true; + + this->wrapS = wrapS; + this->wrapT = wrapT; + + if (bindTexture) { + glBindTexture(renderTarget, id); + } + + glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS); + glTexParameteri(renderTarget, GL_TEXTURE_WRAP_T, wrapT); + } } - void setFilter(GLenum min, GLenum mag) { - minFilter = min; - magFilter = mag; + void setFilter(GLenum min, GLenum mag, bool bindTexture = false, bool force = false, + GLenum renderTarget = GL_TEXTURE_2D) { + + if (firstFilter || force || min != minFilter || mag != magFilter) { + firstFilter = false; + + minFilter = min; + magFilter = mag; + + if (bindTexture) { + glBindTexture(renderTarget, id); + } + + glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min); + glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag); + } } /** @@ -87,6 +116,10 @@ struct Texture { */ GLenum minFilter; GLenum magFilter; + +private: + bool firstFilter; + bool firstWrap; }; // struct Texture class AutoTexture { diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index 3752874053a6..f926fddcd7b3 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -219,11 +219,8 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege break; } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + texture->setFilter(GL_LINEAR, GL_LINEAR); + texture->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); } void TextureCache::uploadLoFiTexture(bool resize, SkBitmap* bitmap, diff --git a/libs/rs/driver/rsdRuntimeStubs.cpp b/libs/rs/driver/rsdRuntimeStubs.cpp index 2f9f410f9b9c..d8050ac7bdea 100644 --- a/libs/rs/driver/rsdRuntimeStubs.cpp +++ b/libs/rs/driver/rsdRuntimeStubs.cpp @@ -365,24 +365,49 @@ static const Allocation * SC_GetAllocation(const void *ptr) { return rsrGetAllocation(rsc, sc, ptr); } -static void SC_ForEach(Script *target, - Allocation *in, - Allocation *out, - const void *usr, - const RsScriptCall *call) { +static void SC_ForEach_SAA(Script *target, + Allocation *in, + Allocation *out) { + GET_TLS(); + rsrForEach(rsc, sc, target, in, out, NULL, 0, NULL); +} + +static void SC_ForEach_SAAU(Script *target, + Allocation *in, + Allocation *out, + const void *usr) { GET_TLS(); rsrForEach(rsc, sc, target, in, out, usr, 0, NULL); } -static void SC_ForEach2(Script *target, - Allocation *in, - Allocation *out, - const void *usr, - const RsScriptCall *call) { +static void SC_ForEach_SAAUS(Script *target, + Allocation *in, + Allocation *out, + const void *usr, + const RsScriptCall *call) { GET_TLS(); rsrForEach(rsc, sc, target, in, out, usr, 0, call); } +static void SC_ForEach_SAAUL(Script *target, + Allocation *in, + Allocation *out, + const void *usr, + uint32_t usrLen) { + GET_TLS(); + rsrForEach(rsc, sc, target, in, out, usr, usrLen, NULL); +} + +static void SC_ForEach_SAAULS(Script *target, + Allocation *in, + Allocation *out, + const void *usr, + uint32_t usrLen, + const RsScriptCall *call) { + GET_TLS(); + rsrForEach(rsc, sc, target, in, out, usr, usrLen, call); +} + ////////////////////////////////////////////////////////////////////////////// @@ -648,8 +673,11 @@ static RsdSymbolTable gSyms[] = { { "_Z19rsgClearDepthTargetv", (void *)&SC_ClearFrameBufferObjectDepthTarget, false }, { "_Z24rsgClearAllRenderTargetsv", (void *)&SC_ClearFrameBufferObjectTargets, false }, - { "_Z9rsForEach9rs_script13rs_allocationS0_PKv", (void *)&SC_ForEach, false }, - { "_Z9rsForEach9rs_script13rs_allocationS0_PKvj", (void *)&SC_ForEach2, false }, + { "_Z9rsForEach9rs_script13rs_allocationS0_", (void *)&SC_ForEach_SAA, false }, + { "_Z9rsForEach9rs_script13rs_allocationS0_PKv", (void *)&SC_ForEach_SAAU, false }, + { "_Z9rsForEach9rs_script13rs_allocationS0_PKvPK16rs_script_call_t", (void *)&SC_ForEach_SAAUS, false }, + { "_Z9rsForEach9rs_script13rs_allocationS0_PKvj", (void *)&SC_ForEach_SAAUL, false }, + { "_Z9rsForEach9rs_script13rs_allocationS0_PKvjPK16rs_script_call_t", (void *)&SC_ForEach_SAAULS, false }, // time { "_Z6rsTimePi", (void *)&SC_Time, true }, diff --git a/libs/rs/scriptc/rs_math.rsh b/libs/rs/scriptc/rs_math.rsh index f38f72c4ee08..fb5c4f6ddb65 100644 --- a/libs/rs/scriptc/rs_math.rsh +++ b/libs/rs/scriptc/rs_math.rsh @@ -249,15 +249,28 @@ typedef struct rs_script_call { uint32_t arrayEnd; } rs_script_call_t; +#if 1//(RS_VERSION >= 14) extern void __attribute__((overloadable)) - rsForEach(rs_script script, rs_allocation input, - rs_allocation output, const void * usrData); + rsForEach(rs_script script, rs_allocation input, rs_allocation output); extern void __attribute__((overloadable)) + rsForEach(rs_script script, rs_allocation input, rs_allocation output, + const void * usrData, size_t usrDataLen); + +extern void __attribute__((overloadable)) + rsForEach(rs_script script, rs_allocation input, rs_allocation output, + const void * usrData, size_t usrDataLen, const rs_script_call_t *); +#else +extern void __attribute__((overloadable)) rsForEach(rs_script script, rs_allocation input, rs_allocation output, const void * usrData, const rs_script_call_t *); +#endif +// Move me once dependant changes are in. +extern void __attribute__((overloadable)) + rsForEach(rs_script script, rs_allocation input, + rs_allocation output, const void * usrData); /** diff --git a/libs/rs/scriptc/rs_types.rsh b/libs/rs/scriptc/rs_types.rsh index 536d1f04da48..121e013cf0ee 100644 --- a/libs/rs/scriptc/rs_types.rsh +++ b/libs/rs/scriptc/rs_types.rsh @@ -19,6 +19,9 @@ typedef uint16_t ushort; typedef uint32_t uint; typedef uint64_t ulong; +typedef uint32_t size_t; +typedef int32_t ssize_t; + typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_element; typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_type; typedef struct { const int* const p; } __attribute__((packed, aligned(4))) rs_allocation; @@ -88,6 +91,8 @@ typedef float4 rs_quaternion; #define RS_PACKED __attribute__((packed, aligned(4))) +#define NULL ((const void *)0) + typedef enum { RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X = 0, RS_ALLOCATION_CUBEMAP_FACE_NEGATIVE_X = 1, diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index cb6c24627eca..6cf01c8d6887 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -4346,7 +4346,8 @@ status_t ResTable::createIdmap(const ResTable& overlay, uint32_t originalCrc, ui | (0x0000ffff & (entryIndex)); resource_name resName; if (!this->getResourceName(resID, &resName)) { - return UNKNOWN_ERROR; + LOGW("idmap: resource 0x%08x has spec but lacks values, skipping\n", resID); + continue; } const String16 overlayType(resName.type, resName.typeLen); 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/java/android/media/audiofx/AcousticEchoCanceler.java b/media/java/android/media/audiofx/AcousticEchoCanceler.java new file mode 100644 index 000000000000..7197dd2fc768 --- /dev/null +++ b/media/java/android/media/audiofx/AcousticEchoCanceler.java @@ -0,0 +1,67 @@ +/* + * 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.media.audiofx; + +/** + * Acoustic Echo Canceler (AEC). + * <p>Acoustic Echo Canceler (AEC) is an audio pre-processing which removes the contribution of the + * signal received from the remote party from the captured audio signal. + * <p>AEC is used by voice communication applications (voice chat, video conferencing, SIP calls) + * where the presence of echo with significant delay in the signal received from the remote party + * is highly disturbing. AEC is often used in conjunction with noise suppression (NS). + * <p>An application creates an AcousticEchoCanceler object to instantiate and control an AEC + * engine in the audio capture path. + * <p>To attach the AcousticEchoCanceler to a particular {@link android.media.AudioRecord}, + * specify the audio session ID of this AudioRecord when constructing the AcousticEchoCanceler. + * The audio session is retrieved by calling + * {@link android.media.AudioRecord#getAudioSessionId()} on the AudioRecord instance. + * <p>On some devices, an AEC can be inserted by default in the capture path by the platform + * according to the {@link android.media.MediaRecorder.AudioSource} used. The application can + * query which pre-processings are currently applied to an AudioRecord instance by calling + * {@link android.media.audiofx.AudioEffect#queryPreProcessings(int)} with the audio session of the + * AudioRecord. + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on + * controlling audio effects. + * @hide + */ + +public class AcousticEchoCanceler extends AudioEffect { + + private final static String TAG = "AcousticEchoCanceler"; + + /** + * Class constructor. + * <p> The application must catch exceptions when creating an AcousticEchoCanceler as the + * constructor is not guarantied to succeed: + * <ul> + * <li>IllegalArgumentException is thrown if the device does not implement an AEC</li> + * <li>UnsupportedOperationException is thrown is the resources allocated to audio + * pre-procesing are currently exceeded.</li> + * </ul> + * + * @param audioSession system wide unique audio session identifier. The AcousticEchoCanceler + * will be applied to the AudioRecord with the same audio session. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public AcousticEchoCanceler(int audioSession) + throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_AEC, EFFECT_TYPE_NULL, 0, audioSession); + } +} diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index 39c6d3ef4e3f..3ac010493053 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -66,6 +66,8 @@ public class AudioEffect { private final static String TAG = "AudioEffect-JAVA"; + // effect type UUIDs are taken from hardware/libhardware/include/hardware/audio_effect.h + /** * The following UUIDs define effect types corresponding to standard audio * effects whose implementation and interface conform to the OpenSL ES @@ -105,6 +107,27 @@ public class AudioEffect { .fromString("37cc2c00-dddd-11db-8577-0002a5d5c51b"); /** + * UUID for Automatic Gain Control (AGC) audio pre-processing + * @hide + */ + public static final UUID EFFECT_TYPE_AGC = UUID + .fromString("0a8abfe0-654c-11e0-ba26-0002a5d5c51b"); + + /** + * UUID for Acoustic Echo Canceler (AEC) audio pre-processing + * @hide + */ + public static final UUID EFFECT_TYPE_AEC = UUID + .fromString("7b491460-8d4d-11e0-bd61-0002a5d5c51b"); + + /** + * UUID for Noise Suppressor (NS) audio pre-processing + * @hide + */ + public static final UUID EFFECT_TYPE_NS = UUID + .fromString("58b4b260-8e06-11e0-aa8e-0002a5d5c51b"); + + /** * Null effect UUID. Used when the UUID for effect type of * @hide */ @@ -180,7 +203,8 @@ public class AudioEffect { * <ul> * <li>type: UUID corresponding to the OpenSL ES interface implemented by this effect</li> * <li>uuid: UUID for this particular implementation</li> - * <li>connectMode: {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}</li> + * <li>connectMode: {@link #EFFECT_INSERT}, {@link #EFFECT_AUXILIARY} or + * {at_link #EFFECT_PRE_PROCESSING}</li> * <li>name: human readable effect name</li> * <li>implementor: human readable effect implementor name</li> * </ul> @@ -212,11 +236,13 @@ public class AudioEffect { */ public UUID uuid; /** - * Indicates if the effect is of insert category {@link #EFFECT_INSERT} or auxiliary - * category {@link #EFFECT_AUXILIARY}. Insert effects (Typically an Equalizer) are applied + * Indicates if the effect is of insert category {@link #EFFECT_INSERT}, auxiliary + * category {@link #EFFECT_AUXILIARY} or pre processing category + * {at_link #EFFECT_PRE_PROCESSING}. Insert effects (Typically an Equalizer) are applied * to the entire audio source and usually not shared by several sources. Auxiliary effects * (typically a reverberator) are applied to part of the signal (wet) and the effect output * is added to the original signal (dry). + * Audio pre processing are applied to audio captured on a particular AudioRecord. */ public String connectMode; /** @@ -243,6 +269,12 @@ public class AudioEffect { * attaching it to the MediaPlayer or AudioTrack. */ public static final String EFFECT_AUXILIARY = "Auxiliary"; + /** + * Effect connection mode is pre processing. + * The audio pre processing effects are attached to an audio input (AudioRecord). + * @hide + */ + public static final String EFFECT_PRE_PROCESSING = "Pre Processing"; // -------------------------------------------------------------------------- // Member variables @@ -410,6 +442,19 @@ public class AudioEffect { return (Descriptor[]) native_query_effects(); } + /** + * Query all audio pre processing effects applied to the AudioRecord with the supplied + * audio session ID. Returns an array of {@link android.media.audiofx.AudioEffect.Descriptor} + * objects. + * @param audioSession system wide unique audio session identifier. + * @throws IllegalStateException + * @hide + */ + + static public Descriptor[] queryPreProcessings(int audioSession) { + return (Descriptor[]) native_query_pre_processing(audioSession); + } + // -------------------------------------------------------------------------- // Control methods // -------------------- @@ -1155,6 +1200,8 @@ public class AudioEffect { private static native Object[] native_query_effects(); + private static native Object[] native_query_pre_processing(int audioSession); + // --------------------------------------------------------- // Utility methods // ------------------ diff --git a/media/java/android/media/audiofx/AutomaticGainControl.java b/media/java/android/media/audiofx/AutomaticGainControl.java new file mode 100644 index 000000000000..44574f06f857 --- /dev/null +++ b/media/java/android/media/audiofx/AutomaticGainControl.java @@ -0,0 +1,67 @@ +/* + * 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.media.audiofx; + +/** + * Automatic Gain Control (AGC). + * <p>Automatic Gain Control (AGC) is an audio pre-processing which automatically normalizes the + * output of the captured signal by boosting or lowering input from the microphone to match a preset + * level so that that the output signal level is virtually constant. + * AGC can be used by applications where the input signal dynamic range is not important but where + * a constant strong capture level is desired. + * <p>An application creates a AutomaticGainControl object to instantiate and control an AGC + * engine in the audio framework. + * <p>To attach the AutomaticGainControl to a particular {@link android.media.AudioRecord}, + * specify the audio session ID of this AudioRecord when constructing the AutomaticGainControl. + * The audio session is retrieved by calling + * {@link android.media.AudioRecord#getAudioSessionId()} on the AudioRecord instance. + * <p>On some devices, an AGC can be inserted by default in the capture path by the platform + * according to the {@link android.media.MediaRecorder.AudioSource} used. The application can + * query which pre-processings are currently applied to an AudioRecord instance by calling + * {@link android.media.audiofx.AudioEffect#queryPreProcessings(int)} with the audio session of the + * AudioRecord. + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on + * controlling audio effects. + * @hide + */ + +public class AutomaticGainControl extends AudioEffect { + + private final static String TAG = "AutomaticGainControl"; + + /** + * Class constructor. + * <p> The application must catch exceptions when creating an AutomaticGainControl as the + * constructor is not guarantied to succeed: + * <ul> + * <li>IllegalArgumentException is thrown if the device does not implement an AGC</li> + * <li>UnsupportedOperationException is thrown is the resources allocated to audio + * pre-procesing are currently exceeded.</li> + * </ul> + * + * @param audioSession system wide unique audio session identifier. The AutomaticGainControl + * will be applied to the AudioRecord with the same audio session. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public AutomaticGainControl(int audioSession) + throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_AGC, EFFECT_TYPE_NULL, 0, audioSession); + } +} diff --git a/media/java/android/media/audiofx/NoiseSuppressor.java b/media/java/android/media/audiofx/NoiseSuppressor.java new file mode 100644 index 000000000000..4e7a8b6ef07f --- /dev/null +++ b/media/java/android/media/audiofx/NoiseSuppressor.java @@ -0,0 +1,68 @@ +/* + * 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.media.audiofx; + +/** + * Noise Suppressor (NS). + * <p>Noise suppression (NS) is an audio pre-processing which removes background noise from the + * captured signal. The component of the signal considered as noise can be either stationary + * (car/airplane engine, AC system) or non-stationary (other peoples conversations, car horn) for + * more advanced implementations. + * <p>NS is mostly used by voice communication applications (voice chat, video conferencing, + * SIP calls). + * <p>An application creates a NoiseSuppressor object to instantiate and control an NS + * engine in the audio framework. + * <p>To attach the NoiseSuppressor to a particular {@link android.media.AudioRecord}, + * specify the audio session ID of this AudioRecord when constructing the NoiseSuppressor. + * The audio session is retrieved by calling + * {@link android.media.AudioRecord#getAudioSessionId()} on the AudioRecord instance. + * <p>On some devices, NS can be inserted by default in the capture path by the platform + * according to the {@link android.media.MediaRecorder.AudioSource} used. The application can + * query which pre-processings are currently applied to an AudioRecord instance by calling + * {@link android.media.audiofx.AudioEffect#queryPreProcessings(int)} with the audio session of the + * AudioRecord. + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on + * controlling audio effects. + * @hide + */ + +public class NoiseSuppressor extends AudioEffect { + + private final static String TAG = "NoiseSuppressor"; + + /** + * Class constructor. + * <p> The application must catch exceptions when creating an NoiseSuppressor as the + * constructor is not guarantied to succeed: + * <ul> + * <li>IllegalArgumentException is thrown if the device does not implement an NS</li> + * <li>UnsupportedOperationException is thrown is the resources allocated to audio + * pre-procesing are currently exceeded.</li> + * </ul> + * + * @param audioSession system wide unique audio session identifier. The NoiseSuppressor + * will be applied to the AudioRecord with the same audio session. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public NoiseSuppressor(int audioSession) + throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_NS, EFFECT_TYPE_NULL, 0, audioSession); + } +} diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 83466a4abbff..922f7edcb721 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -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/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index e71e7272bc14..57cabe2775cb 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -725,18 +725,22 @@ android_media_AudioEffect_native_queryEffects(JNIEnv *env, jclass clazz) goto queryEffects_failure; } + if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { + jdescConnect = env->NewStringUTF("Auxiliary"); + } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_INSERT) { + jdescConnect = env->NewStringUTF("Insert"); + } else if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC) { + jdescConnect = env->NewStringUTF("Pre Processing"); + } else { + continue; + } + AudioEffect::guidToString(&desc.type, str, EFFECT_STRING_LEN_MAX); jdescType = env->NewStringUTF(str); AudioEffect::guidToString(&desc.uuid, str, EFFECT_STRING_LEN_MAX); jdescUuid = env->NewStringUTF(str); - if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { - jdescConnect = env->NewStringUTF("Auxiliary"); - } else { - jdescConnect = env->NewStringUTF("Insert"); - } - jdescName = env->NewStringUTF(desc.name); jdescImplementor = env->NewStringUTF(desc.implementor); @@ -771,6 +775,87 @@ queryEffects_failure: } + + +static jobjectArray +android_media_AudioEffect_native_queryPreProcessings(JNIEnv *env, jclass clazz, jint audioSession) +{ + // kDefaultNumEffects is a "reasonable" value ensuring that only one query will be enough on + // most devices to get all active audio pre processing on a given session. + static const uint32_t kDefaultNumEffects = 5; + + effect_descriptor_t *descriptors = new effect_descriptor_t[kDefaultNumEffects]; + uint32_t numEffects = kDefaultNumEffects; + + status_t status = AudioEffect::queryDefaultPreProcessing(audioSession, + descriptors, + &numEffects); + if ((status != NO_ERROR && status != NO_MEMORY) || + numEffects == 0) { + delete[] descriptors; + return NULL; + } + if (status == NO_MEMORY) { + delete [] descriptors; + descriptors = new effect_descriptor_t[numEffects]; + status = AudioEffect::queryDefaultPreProcessing(audioSession, + descriptors, + &numEffects); + } + if (status != NO_ERROR || numEffects == 0) { + delete[] descriptors; + return NULL; + } + LOGV("queryDefaultPreProcessing() got %d effects", numEffects); + + jobjectArray ret = env->NewObjectArray(numEffects, fields.clazzDesc, NULL); + if (ret == NULL) { + delete[] descriptors; + return ret; + } + + char str[EFFECT_STRING_LEN_MAX]; + jstring jdescType; + jstring jdescUuid; + jstring jdescConnect; + jstring jdescName; + jstring jdescImplementor; + jobject jdesc; + + for (uint32_t i = 0; i < numEffects; i++) { + + AudioEffect::guidToString(&descriptors[i].type, str, EFFECT_STRING_LEN_MAX); + jdescType = env->NewStringUTF(str); + AudioEffect::guidToString(&descriptors[i].uuid, str, EFFECT_STRING_LEN_MAX); + jdescUuid = env->NewStringUTF(str); + jdescConnect = env->NewStringUTF("Pre Processing"); + jdescName = env->NewStringUTF(descriptors[i].name); + jdescImplementor = env->NewStringUTF(descriptors[i].implementor); + + jdesc = env->NewObject(fields.clazzDesc, + fields.midDescCstor, + jdescType, + jdescUuid, + jdescConnect, + jdescName, + jdescImplementor); + env->DeleteLocalRef(jdescType); + env->DeleteLocalRef(jdescUuid); + env->DeleteLocalRef(jdescConnect); + env->DeleteLocalRef(jdescName); + env->DeleteLocalRef(jdescImplementor); + if (jdesc == NULL) { + LOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)"); + env->DeleteLocalRef(ret); + return NULL;; + } + + env->SetObjectArrayElement(ret, i, jdesc); + } + + return ret; +} + // ---------------------------------------------------------------------------- // Dalvik VM type signatures @@ -787,6 +872,8 @@ static JNINativeMethod gMethods[] = { {"native_getParameter", "(I[BI[B)I", (void *)android_media_AudioEffect_native_getParameter}, {"native_command", "(II[BI[B)I", (void *)android_media_AudioEffect_native_command}, {"native_query_effects", "()[Ljava/lang/Object;", (void *)android_media_AudioEffect_native_queryEffects}, + {"native_query_pre_processing", "(I)[Ljava/lang/Object;", + (void *)android_media_AudioEffect_native_queryPreProcessings}, }; diff --git a/media/jni/mediaeditor/VideoEditorJava.cpp b/media/jni/mediaeditor/VideoEditorJava.cpp index 13f63505484b..ec8050f63c38 100755 --- a/media/jni/mediaeditor/VideoEditorJava.cpp +++ b/media/jni/mediaeditor/VideoEditorJava.cpp @@ -25,11 +25,13 @@ extern "C" { void -videoEditJava_checkAndThrowIllegalArgumentException( +videoEditJava_checkAndThrowIllegalArgumentExceptionFunc( bool* pResult, JNIEnv* pEnv, bool condition, - const char* pMessage) + const char* pMessage, + const char* pFile, + int lineNo) { // Check if the previous action succeeded. if (*pResult) @@ -39,7 +41,8 @@ videoEditJava_checkAndThrowIllegalArgumentException( { // Log the exception. VIDEOEDIT_LOG_EXCEPTION(ANDROID_LOG_ERROR, "VIDEO_EDITOR_JAVA",\ - "videoEditJava_checkAndThrowIllegalArgumentException, %s", pMessage); + "videoEditJava_checkAndThrowIllegalArgumentException, %s (%s:%d)", + pMessage, pFile, lineNo); // Reset the result flag. (*pResult) = false; @@ -51,11 +54,14 @@ videoEditJava_checkAndThrowIllegalArgumentException( } void -videoEditJava_checkAndThrowRuntimeException( +videoEditJava_checkAndThrowRuntimeExceptionFunc( bool* pResult, JNIEnv* pEnv, bool condition, - M4OSA_ERR result) + M4OSA_ERR result, + const char* pFile, + int lineNo + ) { const char* pMessage = NULL; @@ -70,7 +76,8 @@ videoEditJava_checkAndThrowRuntimeException( // Log the exception. VIDEOEDIT_LOG_EXCEPTION(ANDROID_LOG_ERROR, "VIDEO_EDITOR_JAVA", - "videoEditJava_checkAndThrowRuntimeException, %s", pMessage); + "videoEditJava_checkAndThrowRuntimeException, %s (%s:%d)", + pMessage, pFile, lineNo); // Reset the result flag. (*pResult) = false; @@ -82,11 +89,14 @@ videoEditJava_checkAndThrowRuntimeException( } void -videoEditJava_checkAndThrowIllegalStateException( +videoEditJava_checkAndThrowIllegalStateExceptionFunc( bool* pResult, JNIEnv* pEnv, bool condition, - const char* pMessage) + const char* pMessage, + const char* pFile, + int lineNo + ) { // Check if the previous action succeeded. if (*pResult) @@ -96,7 +106,8 @@ videoEditJava_checkAndThrowIllegalStateException( { // Log the exception. VIDEOEDIT_LOG_EXCEPTION(ANDROID_LOG_ERROR, "VIDEO_EDITOR_JAVA", - "videoEditJava_checkAndThrowIllegalStateException, %s", pMessage); + "videoEditJava_checkAndThrowIllegalStateException, %s (%s:%d)", + pMessage, pFile, lineNo); // Reset the result flag. (*pResult) = false; diff --git a/media/jni/mediaeditor/VideoEditorJava.h b/media/jni/mediaeditor/VideoEditorJava.h index 9d7f096bae77..0a2db08adb2c 100755 --- a/media/jni/mediaeditor/VideoEditorJava.h +++ b/media/jni/mediaeditor/VideoEditorJava.h @@ -351,26 +351,47 @@ typedef struct jmethodID methodIds[]; } VideoEditJava_MethodIds; +#define videoEditJava_checkAndThrowIllegalArgumentException(\ + a, b, c, d) videoEditJava_checkAndThrowIllegalArgumentExceptionFunc(\ + a, b, c, d, __FILE__, __LINE__) + +#define videoEditJava_checkAndThrowRuntimeException(\ + a, b, c, d) videoEditJava_checkAndThrowRuntimeExceptionFunc(\ + a, b, c, d, __FILE__, __LINE__) + +#define videoEditJava_checkAndThrowIllegalStateException(\ + a, b, c, d) videoEditJava_checkAndThrowIllegalStateExceptionFunc(\ + a, b, c, d, __FILE__, __LINE__) + void -videoEditJava_checkAndThrowIllegalArgumentException( +videoEditJava_checkAndThrowIllegalArgumentExceptionFunc( bool* pResult, JNIEnv* pEnv, bool condition, - const char* pMessage); + const char* pMessage, + const char* pFile, + int lineNo + ); void -videoEditJava_checkAndThrowRuntimeException( +videoEditJava_checkAndThrowRuntimeExceptionFunc( bool* pResult, JNIEnv* pEnv, bool condition, - M4OSA_ERR result); + M4OSA_ERR result, + const char* pFile, + int lineNo + ); void -videoEditJava_checkAndThrowIllegalStateException( +videoEditJava_checkAndThrowIllegalStateExceptionFunc( bool* pResult, JNIEnv* pEnv, bool condition, - const char* pMessage); + const char* pMessage, + const char* pFile, + int lineNo + ); void videoEditJava_getClass( diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp index 39195516fff6..063374416e58 100644 --- a/media/libmedia/AudioEffect.cpp +++ b/media/libmedia/AudioEffect.cpp @@ -419,6 +419,15 @@ status_t AudioEffect::getEffectDescriptor(effect_uuid_t *uuid, effect_descriptor return af->getEffectDescriptor(uuid, descriptor); } + +status_t AudioEffect::queryDefaultPreProcessing(int audioSession, + effect_descriptor_t *descriptors, + uint32_t *count) +{ + const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); + if (aps == 0) return PERMISSION_DENIED; + return aps->queryDefaultPreProcessing(audioSession, descriptors, count); +} // ------------------------------------------------------------------------- status_t AudioEffect::stringToGuid(const char *str, effect_uuid_t *guid) diff --git a/media/libmedia/IAudioPolicyService.cpp b/media/libmedia/IAudioPolicyService.cpp index 49d410f4ce86..15f4be04cc1a 100644 --- a/media/libmedia/IAudioPolicyService.cpp +++ b/media/libmedia/IAudioPolicyService.cpp @@ -53,6 +53,7 @@ enum { UNREGISTER_EFFECT, IS_STREAM_ACTIVE, GET_DEVICES_FOR_STREAM, + QUERY_DEFAULT_PRE_PROCESSING }; class BpAudioPolicyService : public BpInterface<IAudioPolicyService> @@ -321,6 +322,31 @@ public: remote()->transact(IS_STREAM_ACTIVE, data, &reply); return reply.readInt32(); } + + virtual status_t queryDefaultPreProcessing(int audioSession, + effect_descriptor_t *descriptors, + uint32_t *count) + { + if (descriptors == NULL || count == NULL) { + return BAD_VALUE; + } + Parcel data, reply; + data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor()); + data.writeInt32(audioSession); + data.writeInt32(*count); + status_t status = remote()->transact(QUERY_DEFAULT_PRE_PROCESSING, data, &reply); + if (status != NO_ERROR) { + return status; + } + status = static_cast <status_t> (reply.readInt32()); + uint32_t retCount = reply.readInt32(); + if (retCount != 0) { + uint32_t numDesc = (retCount < *count) ? retCount : *count; + reply.read(descriptors, sizeof(effect_descriptor_t) * numDesc); + } + *count = retCount; + return status; + } }; IMPLEMENT_META_INTERFACE(AudioPolicyService, "android.media.IAudioPolicyService"); @@ -559,6 +585,29 @@ status_t BnAudioPolicyService::onTransact( return NO_ERROR; } break; + case QUERY_DEFAULT_PRE_PROCESSING: { + CHECK_INTERFACE(IAudioPolicyService, data, reply); + int audioSession = data.readInt32(); + uint32_t count = data.readInt32(); + uint32_t retCount = count; + effect_descriptor_t *descriptors = + (effect_descriptor_t *)new char[count * sizeof(effect_descriptor_t)]; + status_t status = queryDefaultPreProcessing(audioSession, descriptors, &retCount); + reply->writeInt32(status); + if (status != NO_ERROR && status != NO_MEMORY) { + retCount = 0; + } + reply->writeInt32(retCount); + if (retCount) { + if (retCount < count) { + count = retCount; + } + reply->write(descriptors, sizeof(effect_descriptor_t) * count); + } + delete[] descriptors; + return status; + } + default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp index 7e44c2991c5c..38e111e54c9d 100644 --- a/media/libmedia/IMediaRecorder.cpp +++ b/media/libmedia/IMediaRecorder.cpp @@ -46,7 +46,6 @@ enum { SET_AUDIO_ENCODER, SET_OUTPUT_FILE_PATH, SET_OUTPUT_FILE_FD, - SET_OUTPUT_FILE_AUXILIARY_FD, SET_VIDEO_SIZE, SET_VIDEO_FRAMERATE, SET_PARAMETERS, @@ -177,15 +176,6 @@ public: return reply.readInt32(); } - status_t setOutputFileAuxiliary(int fd) { - LOGV("setOutputFileAuxiliary(%d)", fd); - Parcel data, reply; - data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor()); - data.writeFileDescriptor(fd); - remote()->transact(SET_OUTPUT_FILE_AUXILIARY_FD, data, &reply); - return reply.readInt32(); - } - status_t setVideoSize(int width, int height) { LOGV("setVideoSize(%dx%d)", width, height); @@ -404,13 +394,6 @@ status_t BnMediaRecorder::onTransact( ::close(fd); return NO_ERROR; } break; - case SET_OUTPUT_FILE_AUXILIARY_FD: { - LOGV("SET_OUTPUT_FILE_AUXILIARY_FD"); - CHECK_INTERFACE(IMediaRecorder, data, reply); - int fd = dup(data.readFileDescriptor()); - reply->writeInt32(setOutputFileAuxiliary(fd)); - return NO_ERROR; - } break; case SET_VIDEO_SIZE: { LOGV("SET_VIDEO_SIZE"); CHECK_INTERFACE(IMediaRecorder, data, reply); diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp index fab674ca515c..11d281fd8ee6 100644 --- a/media/libmedia/mediarecorder.cpp +++ b/media/libmedia/mediarecorder.cpp @@ -322,32 +322,6 @@ status_t MediaRecorder::setOutputFile(int fd, int64_t offset, int64_t length) return ret; } -status_t MediaRecorder::setOutputFileAuxiliary(int fd) -{ - LOGV("setOutputFileAuxiliary(%d)", fd); - if(mMediaRecorder == NULL) { - LOGE("media recorder is not initialized yet"); - return INVALID_OPERATION; - } - if (mIsAuxiliaryOutputFileSet) { - LOGE("output file has already been set"); - return INVALID_OPERATION; - } - if (!(mCurrentState & MEDIA_RECORDER_DATASOURCE_CONFIGURED)) { - LOGE("setOutputFile called in an invalid state(%d)", mCurrentState); - return INVALID_OPERATION; - } - - status_t ret = mMediaRecorder->setOutputFileAuxiliary(fd); - if (OK != ret) { - LOGV("setOutputFileAuxiliary failed: %d", ret); - mCurrentState = MEDIA_RECORDER_ERROR; - return ret; - } - mIsAuxiliaryOutputFileSet = true; - return ret; -} - status_t MediaRecorder::setVideoSize(int width, int height) { LOGV("setVideoSize(%d, %d)", width, height); @@ -629,7 +603,6 @@ void MediaRecorder::doCleanUp() mIsAudioEncoderSet = false; mIsVideoEncoderSet = false; mIsOutputFileSet = false; - mIsAuxiliaryOutputFileSet = false; } // Release should be OK in any state diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp index 905b88568eb7..6f80b35275a3 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.cpp +++ b/media/libmediaplayerservice/MediaRecorderClient.cpp @@ -178,17 +178,6 @@ status_t MediaRecorderClient::setOutputFile(int fd, int64_t offset, int64_t leng return mRecorder->setOutputFile(fd, offset, length); } -status_t MediaRecorderClient::setOutputFileAuxiliary(int fd) -{ - LOGV("setOutputFileAuxiliary(%d)", fd); - Mutex::Autolock lock(mLock); - if (mRecorder == NULL) { - LOGE("recorder is not initialized"); - return NO_INIT; - } - return mRecorder->setOutputFileAuxiliary(fd); -} - status_t MediaRecorderClient::setVideoSize(int width, int height) { LOGV("setVideoSize(%dx%d)", width, height); diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h index c87a3c0a23e8..c9ccf22ed944 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.h +++ b/media/libmediaplayerservice/MediaRecorderClient.h @@ -41,7 +41,6 @@ public: 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); diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp index 6427bb701646..6fdb72623fb7 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.cpp +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -28,9 +28,7 @@ #include <media/stagefright/AMRWriter.h> #include <media/stagefright/AACWriter.h> #include <media/stagefright/CameraSource.h> -#include <media/stagefright/VideoSourceDownSampler.h> #include <media/stagefright/CameraSourceTimeLapse.h> -#include <media/stagefright/MediaSourceSplitter.h> #include <media/stagefright/MPEG2TSWriter.h> #include <media/stagefright/MPEG4Writer.h> #include <media/stagefright/MediaDebug.h> @@ -67,8 +65,8 @@ static void addBatteryData(uint32_t params) { StagefrightRecorder::StagefrightRecorder() - : mWriter(NULL), mWriterAux(NULL), - mOutputFd(-1), mOutputFdAux(-1), + : mWriter(NULL), + mOutputFd(-1), mAudioSource(AUDIO_SOURCE_CNT), mVideoSource(VIDEO_SOURCE_LIST_END), mStarted(false), mSurfaceMediaSource(NULL) { @@ -259,24 +257,6 @@ status_t StagefrightRecorder::setOutputFile(int fd, int64_t offset, int64_t leng return OK; } -status_t StagefrightRecorder::setOutputFileAuxiliary(int fd) { - LOGV("setOutputFileAuxiliary: %d", fd); - - if (fd < 0) { - LOGE("Invalid file descriptor: %d", fd); - return -EBADF; - } - - mCaptureAuxVideo = true; - - if (mOutputFdAux >= 0) { - ::close(mOutputFdAux); - } - mOutputFdAux = dup(fd); - - return OK; -} - // Attempt to parse an int64 literal optionally surrounded by whitespace, // returns true on success, false otherwise. static bool safe_strtoi64(const char *s, int64_t *val) { @@ -573,42 +553,6 @@ status_t StagefrightRecorder::setParamTimeBetweenTimeLapseFrameCapture(int64_t t return OK; } -status_t StagefrightRecorder::setParamAuxVideoWidth(int32_t width) { - LOGV("setParamAuxVideoWidth : %d", width); - - if (width <= 0) { - LOGE("Width (%d) is not positive", width); - return BAD_VALUE; - } - - mAuxVideoWidth = width; - return OK; -} - -status_t StagefrightRecorder::setParamAuxVideoHeight(int32_t height) { - LOGV("setParamAuxVideoHeight : %d", height); - - if (height <= 0) { - LOGE("Height (%d) is not positive", height); - return BAD_VALUE; - } - - mAuxVideoHeight = height; - return OK; -} - -status_t StagefrightRecorder::setParamAuxVideoEncodingBitRate(int32_t bitRate) { - LOGV("StagefrightRecorder::setParamAuxVideoEncodingBitRate: %d", bitRate); - - if (bitRate <= 0) { - LOGE("Invalid video encoding bit rate: %d", bitRate); - return BAD_VALUE; - } - - mAuxVideoBitRate = bitRate; - return OK; -} - status_t StagefrightRecorder::setParamGeoDataLongitude( int32_t longitudex10000) { @@ -738,21 +682,6 @@ status_t StagefrightRecorder::setParameter( return setParamTimeBetweenTimeLapseFrameCapture( 1000LL * timeBetweenTimeLapseFrameCaptureMs); } - } else if (key == "video-aux-param-width") { - int32_t auxWidth; - if (safe_strtoi32(value.string(), &auxWidth)) { - return setParamAuxVideoWidth(auxWidth); - } - } else if (key == "video-aux-param-height") { - int32_t auxHeight; - if (safe_strtoi32(value.string(), &auxHeight)) { - return setParamAuxVideoHeight(auxHeight); - } - } else if (key == "video-aux-param-encoding-bitrate") { - int32_t auxVideoBitRate; - if (safe_strtoi32(value.string(), &auxVideoBitRate)) { - return setParamAuxVideoEncodingBitRate(auxVideoBitRate); - } } else { LOGE("setParameter: failed to find key %s", key.string()); } @@ -1517,7 +1446,6 @@ status_t StagefrightRecorder::setupAudioEncoder(const sp<MediaWriter>& writer) { } status_t StagefrightRecorder::setupMPEG4Recording( - bool useSplitCameraSource, int outputFd, int32_t videoWidth, int32_t videoHeight, int32_t videoBitRate, @@ -1531,28 +1459,7 @@ status_t StagefrightRecorder::setupMPEG4Recording( if (mVideoSource < VIDEO_SOURCE_LIST_END) { 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"); - mediaSource = mCameraSourceSplitter->createClient(); - } else { - 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. - mediaSource = - new VideoSourceDownSampler(mediaSource, videoWidth, videoHeight); - } + err = setupMediaSource(&mediaSource); if (err != OK) { return err; } @@ -1620,24 +1527,8 @@ void StagefrightRecorder::setupMPEG4MetaData(int64_t startTimeUs, int32_t totalB } status_t StagefrightRecorder::startMPEG4Recording() { - if (mCaptureAuxVideo) { - if (!mCaptureTimeLapse) { - LOGE("Auxiliary video can be captured only in time lapse mode"); - return UNKNOWN_ERROR; - } - LOGV("Creating MediaSourceSplitter"); - sp<CameraSource> cameraSource; - status_t err = setupCameraSource(&cameraSource); - if (err != OK) { - return err; - } - mCameraSourceSplitter = new MediaSourceSplitter(cameraSource); - } else { - mCameraSourceSplitter = NULL; - } - int32_t totalBitRate; - status_t err = setupMPEG4Recording(mCaptureAuxVideo, + status_t err = setupMPEG4Recording( mOutputFd, mVideoWidth, mVideoHeight, mVideoBitRate, &totalBitRate, &mWriter); if (err != OK) { @@ -1653,33 +1544,6 @@ status_t StagefrightRecorder::startMPEG4Recording() { return err; } - if (mCaptureAuxVideo) { - CHECK(mOutputFdAux >= 0); - if (mWriterAux != NULL) { - LOGE("Auxiliary File writer is not avaialble"); - return UNKNOWN_ERROR; - } - if ((mAuxVideoWidth > mVideoWidth) || (mAuxVideoHeight > mVideoHeight) || - ((mAuxVideoWidth == mVideoWidth) && mAuxVideoHeight == mVideoHeight)) { - LOGE("Auxiliary video size (%d x %d) same or larger than the main video size (%d x %d)", - mAuxVideoWidth, mAuxVideoHeight, mVideoWidth, mVideoHeight); - return UNKNOWN_ERROR; - } - - int32_t totalBitrateAux; - err = setupMPEG4Recording(mCaptureAuxVideo, - mOutputFdAux, mAuxVideoWidth, mAuxVideoHeight, - mAuxVideoBitRate, &totalBitrateAux, &mWriterAux); - if (err != OK) { - return err; - } - - sp<MetaData> metaAux = new MetaData; - setupMPEG4MetaData(startTimeUs, totalBitrateAux, &metaAux); - - return mWriterAux->start(metaAux.get()); - } - return OK; } @@ -1690,13 +1554,6 @@ status_t StagefrightRecorder::pause() { } mWriter->pause(); - if (mCaptureAuxVideo) { - if (mWriterAux == NULL) { - return UNKNOWN_ERROR; - } - mWriterAux->pause(); - } - if (mStarted) { mStarted = false; @@ -1724,13 +1581,6 @@ status_t StagefrightRecorder::stop() { mCameraSourceTimeLapse = NULL; } - if (mCaptureAuxVideo) { - if (mWriterAux != NULL) { - mWriterAux->stop(); - mWriterAux.clear(); - } - } - if (mWriter != NULL) { err = mWriter->stop(); mWriter.clear(); @@ -1741,13 +1591,6 @@ status_t StagefrightRecorder::stop() { mOutputFd = -1; } - if (mCaptureAuxVideo) { - if (mOutputFdAux >= 0) { - ::close(mOutputFdAux); - mOutputFdAux = -1; - } - } - if (mStarted) { mStarted = false; @@ -1787,11 +1630,8 @@ status_t StagefrightRecorder::reset() { mVideoEncoder = VIDEO_ENCODER_H263; mVideoWidth = 176; mVideoHeight = 144; - mAuxVideoWidth = 176; - mAuxVideoHeight = 144; mFrameRate = -1; mVideoBitRate = 192000; - mAuxVideoBitRate = 192000; mSampleRate = 8000; mAudioChannels = 1; mAudioBitRate = 12200; @@ -1811,8 +1651,6 @@ status_t StagefrightRecorder::reset() { mTrackEveryTimeDurationUs = 0; mCaptureTimeLapse = false; mTimeBetweenTimeLapseFrameCaptureUs = -1; - mCaptureAuxVideo = false; - mCameraSourceSplitter = NULL; mCameraSourceTimeLapse = NULL; mIsMetaDataStoredInVideoBuffers = false; mEncoderProfiles = MediaProfiles::getInstance(); @@ -1821,7 +1659,6 @@ status_t StagefrightRecorder::reset() { mLongitudex10000 = -3600000; mOutputFd = -1; - mOutputFdAux = -1; return OK; } @@ -1858,8 +1695,6 @@ status_t StagefrightRecorder::dump( snprintf(buffer, SIZE, " Recorder: %p\n", this); snprintf(buffer, SIZE, " Output file (fd %d):\n", mOutputFd); result.append(buffer); - snprintf(buffer, SIZE, " Output file Auxiliary (fd %d):\n", mOutputFdAux); - result.append(buffer); snprintf(buffer, SIZE, " File format: %d\n", mOutputFormat); result.append(buffer); snprintf(buffer, SIZE, " Max file size (bytes): %lld\n", mMaxFileSizeBytes); @@ -1904,14 +1739,10 @@ status_t StagefrightRecorder::dump( result.append(buffer); snprintf(buffer, SIZE, " Frame size (pixels): %dx%d\n", mVideoWidth, mVideoHeight); result.append(buffer); - snprintf(buffer, SIZE, " Aux Frame size (pixels): %dx%d\n", mAuxVideoWidth, mAuxVideoHeight); - result.append(buffer); snprintf(buffer, SIZE, " Frame rate (fps): %d\n", mFrameRate); result.append(buffer); snprintf(buffer, SIZE, " Bit rate (bps): %d\n", mVideoBitRate); result.append(buffer); - snprintf(buffer, SIZE, " Aux Bit rate (bps): %d\n", mAuxVideoBitRate); - result.append(buffer); ::write(fd, result.string(), result.size()); return OK; } diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h index 1618b92a78ae..5c5f05c465b5 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.h +++ b/media/libmediaplayerservice/StagefrightRecorder.h @@ -30,7 +30,6 @@ class Camera; class ICameraRecordingProxy; class CameraSource; class CameraSourceTimeLapse; -class MediaSourceSplitter; struct MediaSource; struct MediaWriter; class MetaData; @@ -55,7 +54,6 @@ struct StagefrightRecorder : public MediaRecorderBase { virtual status_t setPreviewSurface(const sp<Surface>& surface); 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 setParameters(const String8& params); virtual status_t setListener(const sp<IMediaRecorderClient>& listener); virtual status_t prepare(); @@ -74,8 +72,8 @@ private: sp<ICameraRecordingProxy> mCameraProxy; sp<Surface> mPreviewSurface; sp<IMediaRecorderClient> mListener; - sp<MediaWriter> mWriter, mWriterAux; - int mOutputFd, mOutputFdAux; + sp<MediaWriter> mWriter; + int mOutputFd; sp<AudioSource> mAudioSourceNode; audio_source_t mAudioSource; @@ -85,9 +83,8 @@ private: video_encoder mVideoEncoder; bool mUse64BitFileOffset; int32_t mVideoWidth, mVideoHeight; - int32_t mAuxVideoWidth, mAuxVideoHeight; int32_t mFrameRate; - int32_t mVideoBitRate, mAuxVideoBitRate; + int32_t mVideoBitRate; int32_t mAudioBitRate; int32_t mAudioChannels; int32_t mSampleRate; @@ -109,8 +106,6 @@ private: bool mCaptureTimeLapse; int64_t mTimeBetweenTimeLapseFrameCaptureUs; - bool mCaptureAuxVideo; - sp<MediaSourceSplitter> mCameraSourceSplitter; sp<CameraSourceTimeLapse> mCameraSourceTimeLapse; @@ -127,7 +122,6 @@ private: sp<SurfaceMediaSource> mSurfaceMediaSource; status_t setupMPEG4Recording( - bool useSplitCameraSource, int outputFd, int32_t videoWidth, int32_t videoHeight, int32_t videoBitRate, @@ -166,9 +160,6 @@ private: status_t setParamAudioTimeScale(int32_t timeScale); status_t setParamTimeLapseEnable(int32_t timeLapseEnable); status_t setParamTimeBetweenTimeLapseFrameCapture(int64_t timeUs); - status_t setParamAuxVideoHeight(int32_t height); - status_t setParamAuxVideoWidth(int32_t width); - status_t setParamAuxVideoEncodingBitRate(int32_t bitRate); status_t setParamVideoEncodingBitRate(int32_t bitRate); status_t setParamVideoIFramesInterval(int32_t seconds); status_t setParamVideoEncoderProfile(int32_t profile); diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp index fe78c462d1f5..1ba79e57edb3 100644 --- a/media/libstagefright/CameraSourceTimeLapse.cpp +++ b/media/libstagefright/CameraSourceTimeLapse.cpp @@ -24,15 +24,10 @@ #include <media/stagefright/CameraSourceTimeLapse.h> #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MetaData.h> -#include <media/stagefright/YUVImage.h> -#include <media/stagefright/YUVCanvas.h> #include <camera/Camera.h> #include <camera/CameraParameters.h> -#include <ui/Rect.h> #include <utils/String8.h> #include <utils/Vector.h> -#include "OMX_Video.h" -#include <limits.h> namespace android { @@ -74,20 +69,14 @@ CameraSourceTimeLapse::CameraSourceTimeLapse( mLastTimeLapseFrameRealTimestampUs(0), mSkipCurrentFrame(false) { - LOGD("starting time lapse mode: %lld us", mTimeBetweenTimeLapseFrameCaptureUs); + LOGD("starting time lapse mode: %lld us", + mTimeBetweenTimeLapseFrameCaptureUs); + mVideoWidth = videoSize.width; mVideoHeight = videoSize.height; - if (trySettingVideoSize(videoSize.width, videoSize.height)) { - mUseStillCameraForTimeLapse = false; - } else { - // TODO: Add a check to see that mTimeBetweenTimeLapseFrameCaptureUs is greater - // than the fastest rate at which the still camera can take pictures. - mUseStillCameraForTimeLapse = true; - CHECK(setPictureSizeToClosestSupported(videoSize.width, videoSize.height)); - mNeedCropping = computeCropRectangleOffset(); - mMeta->setInt32(kKeyWidth, videoSize.width); - mMeta->setInt32(kKeyHeight, videoSize.height); + if (!trySettingVideoSize(videoSize.width, videoSize.height)) { + mInitCheck = NO_INIT; } // Initialize quick stop variables. @@ -101,24 +90,22 @@ CameraSourceTimeLapse::~CameraSourceTimeLapse() { } void CameraSourceTimeLapse::startQuickReadReturns() { + LOGV("startQuickReadReturns"); Mutex::Autolock autoLock(mQuickStopLock); - LOGV("Enabling quick read returns"); // Enable quick stop mode. mQuickStop = true; - if (mUseStillCameraForTimeLapse) { - // wake up the thread right away. - mTakePictureCondition.signal(); - } else { - // Force dataCallbackTimestamp() coming from the video camera to not skip the - // next frame as we want read() to get a get a frame right away. - mForceRead = true; - } + // Force dataCallbackTimestamp() coming from the video camera to + // not skip the next frame as we want read() to get a get a frame + // right away. + mForceRead = true; } -bool CameraSourceTimeLapse::trySettingVideoSize(int32_t width, int32_t height) { - LOGV("trySettingVideoSize: %dx%d", width, height); +bool CameraSourceTimeLapse::trySettingVideoSize( + int32_t width, int32_t height) { + + LOGV("trySettingVideoSize"); int64_t token = IPCThreadState::self()->clearCallingIdentity(); String8 s = mCamera->getParameters(); @@ -162,53 +149,8 @@ bool CameraSourceTimeLapse::trySettingVideoSize(int32_t width, int32_t height) { return isSuccessful; } -bool CameraSourceTimeLapse::setPictureSizeToClosestSupported(int32_t width, int32_t height) { - LOGV("setPictureSizeToClosestSupported: %dx%d", width, height); - int64_t token = IPCThreadState::self()->clearCallingIdentity(); - String8 s = mCamera->getParameters(); - IPCThreadState::self()->restoreCallingIdentity(token); - - CameraParameters params(s); - Vector<Size> supportedSizes; - params.getSupportedPictureSizes(supportedSizes); - - int32_t minPictureSize = INT_MAX; - for (uint32_t i = 0; i < supportedSizes.size(); ++i) { - int32_t pictureWidth = supportedSizes[i].width; - int32_t pictureHeight = supportedSizes[i].height; - - if ((pictureWidth >= width) && (pictureHeight >= height)) { - int32_t pictureSize = pictureWidth*pictureHeight; - if (pictureSize < minPictureSize) { - minPictureSize = pictureSize; - mPictureWidth = pictureWidth; - mPictureHeight = pictureHeight; - } - } - } - LOGV("Picture size = (%d, %d)", mPictureWidth, mPictureHeight); - return (minPictureSize != INT_MAX); -} - -bool CameraSourceTimeLapse::computeCropRectangleOffset() { - if ((mPictureWidth == mVideoWidth) && (mPictureHeight == mVideoHeight)) { - return false; - } - - CHECK((mPictureWidth > mVideoWidth) && (mPictureHeight > mVideoHeight)); - - int32_t widthDifference = mPictureWidth - mVideoWidth; - int32_t heightDifference = mPictureHeight - mVideoHeight; - - mCropRectStartX = widthDifference/2; - mCropRectStartY = heightDifference/2; - - LOGV("setting crop rectangle offset to (%d, %d)", mCropRectStartX, mCropRectStartY); - - return true; -} - void CameraSourceTimeLapse::signalBufferReturned(MediaBuffer* buffer) { + LOGV("signalBufferReturned"); Mutex::Autolock autoLock(mQuickStopLock); if (mQuickStop && (buffer == mLastReadBufferCopy)) { buffer->setObserver(NULL); @@ -218,7 +160,12 @@ void CameraSourceTimeLapse::signalBufferReturned(MediaBuffer* buffer) { } } -void createMediaBufferCopy(const MediaBuffer& sourceBuffer, int64_t frameTime, MediaBuffer **newBuffer) { +void createMediaBufferCopy( + const MediaBuffer& sourceBuffer, + int64_t frameTime, + MediaBuffer **newBuffer) { + + LOGV("createMediaBufferCopy"); size_t sourceSize = sourceBuffer.size(); void* sourcePointer = sourceBuffer.data(); @@ -229,6 +176,7 @@ void createMediaBufferCopy(const MediaBuffer& sourceBuffer, int64_t frameTime, M } void CameraSourceTimeLapse::fillLastReadBufferCopy(MediaBuffer& sourceBuffer) { + LOGV("fillLastReadBufferCopy"); int64_t frameTime; CHECK(sourceBuffer.meta_data()->findInt64(kKeyTime, &frameTime)); createMediaBufferCopy(sourceBuffer, frameTime, &mLastReadBufferCopy); @@ -238,11 +186,12 @@ void CameraSourceTimeLapse::fillLastReadBufferCopy(MediaBuffer& sourceBuffer) { status_t CameraSourceTimeLapse::read( MediaBuffer **buffer, const ReadOptions *options) { + LOGV("read"); if (mLastReadBufferCopy == NULL) { mLastReadStatus = CameraSource::read(buffer, options); - // mQuickStop may have turned to true while read was blocked. Make a copy of - // the buffer in that case. + // mQuickStop may have turned to true while read was blocked. + // Make a copy of the buffer in that case. Mutex::Autolock autoLock(mQuickStopLock); if (mQuickStop && *buffer) { fillLastReadBufferCopy(**buffer); @@ -255,105 +204,19 @@ status_t CameraSourceTimeLapse::read( } } -// static -void *CameraSourceTimeLapse::ThreadTimeLapseWrapper(void *me) { - CameraSourceTimeLapse *source = static_cast<CameraSourceTimeLapse *>(me); - source->threadTimeLapseEntry(); - return NULL; -} - -void CameraSourceTimeLapse::threadTimeLapseEntry() { - while (mStarted) { - { - Mutex::Autolock autoLock(mCameraIdleLock); - if (!mCameraIdle) { - mCameraIdleCondition.wait(mCameraIdleLock); - } - CHECK(mCameraIdle); - mCameraIdle = false; - } - - // Even if mQuickStop == true we need to take one more picture - // as a read() may be blocked, waiting for a frame to get available. - // After this takePicture, if mQuickStop == true, we can safely exit - // this thread as read() will make a copy of this last frame and keep - // returning it in the quick stop mode. - Mutex::Autolock autoLock(mQuickStopLock); - CHECK_EQ(OK, mCamera->takePicture(CAMERA_MSG_RAW_IMAGE)); - if (mQuickStop) { - LOGV("threadTimeLapseEntry: Exiting due to mQuickStop = true"); - return; - } - mTakePictureCondition.waitRelative(mQuickStopLock, - mTimeBetweenTimeLapseFrameCaptureUs * 1000); - } - LOGV("threadTimeLapseEntry: Exiting due to mStarted = false"); -} - -void CameraSourceTimeLapse::startCameraRecording() { - if (mUseStillCameraForTimeLapse) { - LOGV("start time lapse recording using still camera"); - - int64_t token = IPCThreadState::self()->clearCallingIdentity(); - String8 s = mCamera->getParameters(); - - CameraParameters params(s); - params.setPictureSize(mPictureWidth, mPictureHeight); - mCamera->setParameters(params.flatten()); - mCameraIdle = true; - mStopWaitingForIdleCamera = false; - - // disable shutter sound and play the recording sound. - mCamera->sendCommand(CAMERA_CMD_ENABLE_SHUTTER_SOUND, 0, 0); - mCamera->sendCommand(CAMERA_CMD_PLAY_RECORDING_SOUND, 0, 0); - IPCThreadState::self()->restoreCallingIdentity(token); - - // create a thread which takes pictures in a loop - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - - pthread_create(&mThreadTimeLapse, &attr, ThreadTimeLapseWrapper, this); - pthread_attr_destroy(&attr); - } else { - LOGV("start time lapse recording using video camera"); - CameraSource::startCameraRecording(); - } -} - void CameraSourceTimeLapse::stopCameraRecording() { - if (mUseStillCameraForTimeLapse) { - void *dummy; - pthread_join(mThreadTimeLapse, &dummy); - - // Last takePicture may still be underway. Wait for the camera to get - // idle. - Mutex::Autolock autoLock(mCameraIdleLock); - mStopWaitingForIdleCamera = true; - if (!mCameraIdle) { - mCameraIdleCondition.wait(mCameraIdleLock); - } - CHECK(mCameraIdle); - mCamera->setListener(NULL); - - // play the recording sound. - mCamera->sendCommand(CAMERA_CMD_PLAY_RECORDING_SOUND, 0, 0); - } else { - CameraSource::stopCameraRecording(); - } + LOGV("stopCameraRecording"); + CameraSource::stopCameraRecording(); if (mLastReadBufferCopy) { mLastReadBufferCopy->release(); mLastReadBufferCopy = NULL; } } -void CameraSourceTimeLapse::releaseRecordingFrame(const sp<IMemory>& frame) { - if (!mUseStillCameraForTimeLapse) { - CameraSource::releaseRecordingFrame(frame); - } -} +sp<IMemory> CameraSourceTimeLapse::createIMemoryCopy( + const sp<IMemory> &source_data) { -sp<IMemory> CameraSourceTimeLapse::createIMemoryCopy(const sp<IMemory> &source_data) { + LOGV("createIMemoryCopy"); size_t source_size = source_data->size(); void* source_pointer = source_data->pointer(); @@ -363,102 +226,8 @@ sp<IMemory> CameraSourceTimeLapse::createIMemoryCopy(const sp<IMemory> &source_d return newMemory; } -// Allocates IMemory of final type MemoryBase with the given size. -sp<IMemory> allocateIMemory(size_t size) { - sp<MemoryHeapBase> newMemoryHeap = new MemoryHeapBase(size); - sp<MemoryBase> newMemory = new MemoryBase(newMemoryHeap, 0, size); - return newMemory; -} - -// static -void *CameraSourceTimeLapse::ThreadStartPreviewWrapper(void *me) { - CameraSourceTimeLapse *source = static_cast<CameraSourceTimeLapse *>(me); - source->threadStartPreview(); - return NULL; -} - -void CameraSourceTimeLapse::threadStartPreview() { - CHECK_EQ(OK, mCamera->startPreview()); - Mutex::Autolock autoLock(mCameraIdleLock); - mCameraIdle = true; - mCameraIdleCondition.signal(); -} - -void CameraSourceTimeLapse::restartPreview() { - // Start this in a different thread, so that the dataCallback can return - LOGV("restartPreview"); - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - - pthread_t threadPreview; - pthread_create(&threadPreview, &attr, ThreadStartPreviewWrapper, this); - pthread_attr_destroy(&attr); -} - -sp<IMemory> CameraSourceTimeLapse::cropYUVImage(const sp<IMemory> &source_data) { - // find the YUV format - int32_t srcFormat; - CHECK(mMeta->findInt32(kKeyColorFormat, &srcFormat)); - YUVImage::YUVFormat yuvFormat; - if (srcFormat == OMX_COLOR_FormatYUV420SemiPlanar) { - yuvFormat = YUVImage::YUV420SemiPlanar; - } else { - CHECK_EQ(srcFormat, OMX_COLOR_FormatYUV420Planar); - yuvFormat = YUVImage::YUV420Planar; - } - - // allocate memory for cropped image and setup a canvas using it. - sp<IMemory> croppedImageMemory = allocateIMemory( - YUVImage::bufferSize(yuvFormat, mVideoWidth, mVideoHeight)); - YUVImage yuvImageCropped(yuvFormat, - mVideoWidth, mVideoHeight, - (uint8_t *)croppedImageMemory->pointer()); - YUVCanvas yuvCanvasCrop(yuvImageCropped); - - YUVImage yuvImageSource(yuvFormat, - mPictureWidth, mPictureHeight, - (uint8_t *)source_data->pointer()); - yuvCanvasCrop.CopyImageRect( - Rect(mCropRectStartX, mCropRectStartY, - mCropRectStartX + mVideoWidth, - mCropRectStartY + mVideoHeight), - 0, 0, - yuvImageSource); - - return croppedImageMemory; -} - -void CameraSourceTimeLapse::dataCallback(int32_t msgType, const sp<IMemory> &data) { - if (msgType == CAMERA_MSG_COMPRESSED_IMAGE) { - // takePicture will complete after this callback, so restart preview. - restartPreview(); - return; - } - if (msgType != CAMERA_MSG_RAW_IMAGE) { - return; - } - - LOGV("dataCallback for timelapse still frame"); - CHECK_EQ(true, mUseStillCameraForTimeLapse); - - int64_t timestampUs; - if (mNumFramesReceived == 0) { - timestampUs = mStartTimeUs; - } else { - timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs; - } - - if (mNeedCropping) { - sp<IMemory> croppedImageData = cropYUVImage(data); - dataCallbackTimestamp(timestampUs, msgType, croppedImageData); - } else { - sp<IMemory> dataCopy = createIMemoryCopy(data); - dataCallbackTimestamp(timestampUs, msgType, dataCopy); - } -} - bool CameraSourceTimeLapse::skipCurrentFrame(int64_t timestampUs) { + LOGV("skipCurrentFrame"); if (mSkipCurrentFrame) { mSkipCurrentFrame = false; return true; @@ -468,72 +237,58 @@ bool CameraSourceTimeLapse::skipCurrentFrame(int64_t timestampUs) { } bool CameraSourceTimeLapse::skipFrameAndModifyTimeStamp(int64_t *timestampUs) { - if (!mUseStillCameraForTimeLapse) { - if (mLastTimeLapseFrameRealTimestampUs == 0) { - // First time lapse frame. Initialize mLastTimeLapseFrameRealTimestampUs - // to current time (timestampUs) and save frame data. - LOGV("dataCallbackTimestamp timelapse: initial frame"); + LOGV("skipFrameAndModifyTimeStamp"); + if (mLastTimeLapseFrameRealTimestampUs == 0) { + // First time lapse frame. Initialize mLastTimeLapseFrameRealTimestampUs + // to current time (timestampUs) and save frame data. + LOGV("dataCallbackTimestamp timelapse: initial frame"); - mLastTimeLapseFrameRealTimestampUs = *timestampUs; - return false; - } + mLastTimeLapseFrameRealTimestampUs = *timestampUs; + return false; + } - { - Mutex::Autolock autoLock(mQuickStopLock); - - // mForceRead may be set to true by startQuickReadReturns(). In that - // case don't skip this frame. - if (mForceRead) { - LOGV("dataCallbackTimestamp timelapse: forced read"); - mForceRead = false; - *timestampUs = - mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs; - return false; - } - } + { + Mutex::Autolock autoLock(mQuickStopLock); - // Workaround to bypass the first 2 input frames for skipping. - // The first 2 output frames from the encoder are: decoder specific info and - // the compressed video frame data for the first input video frame. - if (mNumFramesEncoded >= 1 && *timestampUs < - (mLastTimeLapseFrameRealTimestampUs + mTimeBetweenTimeLapseFrameCaptureUs)) { - // Skip all frames from last encoded frame until - // sufficient time (mTimeBetweenTimeLapseFrameCaptureUs) has passed. - // Tell the camera to release its recording frame and return. - LOGV("dataCallbackTimestamp timelapse: skipping intermediate frame"); - return true; - } else { - // Desired frame has arrived after mTimeBetweenTimeLapseFrameCaptureUs time: - // - Reset mLastTimeLapseFrameRealTimestampUs to current time. - // - Artificially modify timestampUs to be one frame time (1/framerate) ahead - // of the last encoded frame's time stamp. - LOGV("dataCallbackTimestamp timelapse: got timelapse frame"); - - mLastTimeLapseFrameRealTimestampUs = *timestampUs; - *timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs; + // mForceRead may be set to true by startQuickReadReturns(). In that + // case don't skip this frame. + if (mForceRead) { + LOGV("dataCallbackTimestamp timelapse: forced read"); + mForceRead = false; + *timestampUs = + mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs; return false; } } + + // Workaround to bypass the first 2 input frames for skipping. + // The first 2 output frames from the encoder are: decoder specific info and + // the compressed video frame data for the first input video frame. + if (mNumFramesEncoded >= 1 && *timestampUs < + (mLastTimeLapseFrameRealTimestampUs + mTimeBetweenTimeLapseFrameCaptureUs)) { + // Skip all frames from last encoded frame until + // sufficient time (mTimeBetweenTimeLapseFrameCaptureUs) has passed. + // Tell the camera to release its recording frame and return. + LOGV("dataCallbackTimestamp timelapse: skipping intermediate frame"); + return true; + } else { + // Desired frame has arrived after mTimeBetweenTimeLapseFrameCaptureUs time: + // - Reset mLastTimeLapseFrameRealTimestampUs to current time. + // - Artificially modify timestampUs to be one frame time (1/framerate) ahead + // of the last encoded frame's time stamp. + LOGV("dataCallbackTimestamp timelapse: got timelapse frame"); + + mLastTimeLapseFrameRealTimestampUs = *timestampUs; + *timestampUs = mLastFrameTimestampUs + mTimeBetweenTimeLapseVideoFramesUs; + return false; + } return false; } void CameraSourceTimeLapse::dataCallbackTimestamp(int64_t timestampUs, int32_t msgType, const sp<IMemory> &data) { - if (!mUseStillCameraForTimeLapse) { - mSkipCurrentFrame = skipFrameAndModifyTimeStamp(×tampUs); - } else { - Mutex::Autolock autoLock(mCameraIdleLock); - // If we are using the still camera and stop() has been called, it may - // be waiting for the camera to get idle. In that case return - // immediately. Calling CameraSource::dataCallbackTimestamp() will lead - // to a deadlock since it tries to access CameraSource::mLock which in - // this case is held by CameraSource::stop() currently waiting for the - // camera to get idle. And camera will not get idle until this call - // returns. - if (mStopWaitingForIdleCamera) { - return; - } - } + LOGV("dataCallbackTimestamp"); + mSkipCurrentFrame = skipFrameAndModifyTimeStamp(×tampUs); CameraSource::dataCallbackTimestamp(timestampUs, msgType, data); } 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 index ff4b08fb6c3f..3d8c56a159f2 100644 --- a/media/libstagefright/SurfaceMediaSource.cpp +++ b/media/libstagefright/SurfaceMediaSource.cpp @@ -23,6 +23,7 @@ #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaDebug.h> #include <media/stagefright/openmax/OMX_IVCommon.h> +#include <media/stagefright/MetadataBufferType.h> #include <surfaceflinger/ISurfaceComposer.h> #include <surfaceflinger/SurfaceComposerClient.h> @@ -710,9 +711,9 @@ status_t SurfaceMediaSource::read( MediaBuffer **buffer, 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); + // Pass the data to the MediaBuffer. Pass in only the metadata + passMetadataBufferLocked(buffer); + (*buffer)->setObserver(this); (*buffer)->add_ref(); (*buffer)->meta_data()->setInt64(kKeyTime, mCurrentTimestamp); @@ -720,6 +721,34 @@ status_t SurfaceMediaSource::read( MediaBuffer **buffer, return OK; } +// Pass the data to the MediaBuffer. Pass in only the metadata +// The metadata passed consists of two parts: +// 1. First, there is an integer indicating that it is a GRAlloc +// source (kMetadataBufferTypeGrallocSource) +// 2. This is followed by the buffer_handle_t that is a handle to the +// GRalloc buffer. The encoder needs to interpret this GRalloc handle +// and encode the frames. +// -------------------------------------------------------------- +// | kMetadataBufferTypeGrallocSource | sizeof(buffer_handle_t) | +// -------------------------------------------------------------- +// Note: Call only when you have the lock +void SurfaceMediaSource::passMetadataBufferLocked(MediaBuffer **buffer) { + LOGV("passMetadataBuffer"); + // MediaBuffer allocates and owns this data + MediaBuffer *tempBuffer = + new MediaBuffer(4 + sizeof(buffer_handle_t)); + char *data = (char *)tempBuffer->data(); + if (data == NULL) { + LOGE("Cannot allocate memory for passing buffer metadata!"); + return; + } + OMX_U32 type = kMetadataBufferTypeGrallocSource; + memcpy(data, &type, 4); + memcpy(data + 4, &(mCurrentBuf->handle), sizeof(buffer_handle_t)); + *buffer = tempBuffer; +} + + void SurfaceMediaSource::signalBufferReturned(MediaBuffer *buffer) { LOGV("signalBufferReturned"); @@ -727,14 +756,13 @@ void SurfaceMediaSource::signalBufferReturned(MediaBuffer *buffer) { Mutex::Autolock autoLock(mMutex); if (!mStarted) { - LOGV("started = false. Nothing to do"); + LOGW("signalBufferReturned: mStarted = 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); + CHECK(mSlots[*it].mGraphicBuffer != NULL); + if (checkBufferMatchesSlot(*it, buffer)) { mSlots[*it].mBufferState = BufferSlot::FREE; mQueue.erase(it); buffer->setObserver(0); @@ -751,6 +779,14 @@ void SurfaceMediaSource::signalBufferReturned(MediaBuffer *buffer) { } } +bool SurfaceMediaSource::checkBufferMatchesSlot(int slot, MediaBuffer *buffer) { + LOGV("Check if Buffer matches slot"); + // need to convert to char* for pointer arithmetic and then + // copy the byte stream into our handle + buffer_handle_t bufferHandle ; + memcpy( &bufferHandle, (char *)(buffer->data()) + 4, sizeof(buffer_handle_t)); + return mSlots[slot].mGraphicBuffer->handle == bufferHandle; +} } // 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/tests/SurfaceMediaSource_test.cpp b/media/libstagefright/tests/SurfaceMediaSource_test.cpp index ce108122b8f7..dc6f2c9dabc1 100644 --- a/media/libstagefright/tests/SurfaceMediaSource_test.cpp +++ b/media/libstagefright/tests/SurfaceMediaSource_test.cpp @@ -71,8 +71,8 @@ protected: mANW.clear(); } - const int mYuvTexWidth;// = 64; - const int mYuvTexHeight;// = 66; + const int mYuvTexWidth; + const int mYuvTexHeight; sp<SurfaceMediaSource> mSMS; sp<SurfaceTextureClient> mSTC; @@ -124,7 +124,6 @@ sp<MPEG4Writer> SurfaceMediaSourceTest::setUpWriter(OMXClient &client ) { // 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 = @@ -225,7 +224,6 @@ TEST_F(SurfaceMediaSourceTest, EncodingFromCpuFilledYV12BufferNpotOneBufferPass) 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)); @@ -239,7 +237,6 @@ TEST_F(SurfaceMediaSourceTest, EncodingFromCpuFilledYV12BufferNpotWrongSizeBuffe // 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)); @@ -258,6 +255,7 @@ TEST_F(SurfaceMediaSourceTest, EncodingFromCpuFilledYV12BufferNpotMultiBufferPa 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(); @@ -276,10 +274,12 @@ TEST_F(SurfaceMediaSourceTest, EncodingFromCpuFilledYV12BufferNpotMultiBufferPa // 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(); @@ -322,10 +322,9 @@ TEST_F(SurfaceMediaSourceTest, EncodingFromCpuFilledYV12BufferNpotMultiBufferPas 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)); // )); + 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)); - OMXClient client; CHECK_EQ(OK, client.connect()); 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/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java new file mode 100644 index 000000000000..eaffd1adb0e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -0,0 +1,317 @@ +/* + * 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.systemui; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.Animator.AnimatorListener; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.graphics.RectF; +import android.util.Log; +import android.view.animation.LinearInterpolator; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; + +public class SwipeHelper { + static final String TAG = "com.android.systemui.SwipeHelper"; + private static final boolean DEBUG = true; + private static final boolean DEBUG_INVALIDATE = false; + private static final boolean SLOW_ANIMATIONS = false; // DEBUG; + + public static final int X = 0; + public static final int Y = 1; + + private boolean CONSTRAIN_SWIPE = true; + private boolean FADE_OUT_DURING_SWIPE = true; + private boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true; + + private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec + private int MAX_ESCAPE_ANIMATION_DURATION = 500; // ms + private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms + + public static float ALPHA_FADE_START = 0.8f; // fraction of thumbnail width + // where fade starts + static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width + // beyond which alpha->0 + + private float mPagingTouchSlop; + private Callback mCallback; + private int mSwipeDirection; + private VelocityTracker mVelocityTracker; + + private float mInitialTouchPos; + private boolean mDragging; + private View mCurrView; + private float mDensityScale; + + public SwipeHelper(int swipeDirection, Callback callback, float densityScale, + float pagingTouchSlop) { + mCallback = callback; + mSwipeDirection = swipeDirection; + mVelocityTracker = VelocityTracker.obtain(); + mDensityScale = densityScale; + mPagingTouchSlop = pagingTouchSlop; + } + + public void setDensityScale(float densityScale) { + mDensityScale = densityScale; + } + + public void setPagingTouchSlop(float pagingTouchSlop) { + mPagingTouchSlop = pagingTouchSlop; + } + + private float getPos(MotionEvent ev) { + return mSwipeDirection == X ? ev.getX() : ev.getY(); + } + + private float getPos(View v) { + return mSwipeDirection == X ? v.getX() : v.getY(); + } + + private float getVelocity(VelocityTracker vt) { + return mSwipeDirection == X ? vt.getXVelocity() : + vt.getYVelocity(); + } + + private ObjectAnimator createTranslationAnimation(View v, float newPos) { + ObjectAnimator anim = ObjectAnimator.ofFloat(v, + mSwipeDirection == X ? "translationX" : "translationY", newPos); + return anim; + } + + private float getPerpendicularVelocity(VelocityTracker vt) { + return mSwipeDirection == X ? vt.getYVelocity() : + vt.getXVelocity(); + } + + private void setTranslation(View v, float translate) { + if (mSwipeDirection == X) { + v.setTranslationX(translate); + } else { + v.setTranslationY(translate); + } + } + + private float getSize(View v) { + return mSwipeDirection == X ? v.getMeasuredWidth() : + v.getMeasuredHeight(); + } + + private float getContentSize(View v) { + View content = mCallback.getChildContentView(v); + return getSize(content); + } + + private float getAlphaForOffset(View view, float thumbSize) { + final float fadeSize = ALPHA_FADE_END * thumbSize; + float result = 1.0f; + float pos = getPos(view); + if (pos >= thumbSize * ALPHA_FADE_START) { + result = 1.0f - (pos - thumbSize * ALPHA_FADE_START) / fadeSize; + } else if (pos < thumbSize * (1.0f - ALPHA_FADE_START)) { + result = 1.0f + (thumbSize * ALPHA_FADE_START + pos) / fadeSize; + } + return result; + } + + void invalidateGlobalRegion(View view) { + RectF childBounds = new RectF(view.getLeft(), view.getTop(), view.getRight(), view + .getBottom()); + childBounds.offset(view.getX(), view.getY()); + if (DEBUG_INVALIDATE) + Log.v(TAG, "-------------"); + while (view.getParent() != null && view.getParent() instanceof View) { + view = (View) view.getParent(); + view.getMatrix().mapRect(childBounds); + view.invalidate((int) Math.floor(childBounds.left), + (int) Math.floor(childBounds.top), + (int) Math.ceil(childBounds.right), + (int) Math.ceil(childBounds.bottom)); + if (DEBUG_INVALIDATE) { + Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left) + + "," + (int) Math.floor(childBounds.top) + + "," + (int) Math.ceil(childBounds.right) + + "," + (int) Math.ceil(childBounds.bottom)); + } + } + } + + public boolean onInterceptTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + + switch (action) { + case MotionEvent.ACTION_DOWN: + mDragging = false; + mCurrView = mCallback.getChildAtPosition(ev); + mVelocityTracker.clear(); + mVelocityTracker.addMovement(ev); + mInitialTouchPos = getPos(ev); + break; + case MotionEvent.ACTION_MOVE: + if (mCurrView != null) { + mVelocityTracker.addMovement(ev); + float pos = getPos(ev); + float delta = pos - mInitialTouchPos; + if (Math.abs(delta) > mPagingTouchSlop) { + mCallback.onBeginDrag(mCurrView); + mDragging = true; + mInitialTouchPos = getPos(ev) - getPos(mCurrView); + } + } + break; + case MotionEvent.ACTION_UP: + mDragging = false; + mCurrView = null; + break; + } + return mDragging; + } + + public void dismissChild(final View animView, float velocity) { + float newPos; + if (velocity < 0 || (velocity == 0 && getPos(animView) < 0)) { + newPos = -getSize(animView); + } else { + newPos = getSize(animView); + } + int duration = MAX_ESCAPE_ANIMATION_DURATION; + if (velocity != 0) { + duration = Math.min(duration, + (int) (Math.abs(newPos - getPos(animView)) * 1000f / Math + .abs(velocity))); + } + ObjectAnimator anim = createTranslationAnimation(animView, newPos); + anim.setInterpolator(new LinearInterpolator()); + anim.setDuration(duration); + anim.addListener(new AnimatorListener() { + public void onAnimationStart(Animator animation) { + } + + public void onAnimationRepeat(Animator animation) { + } + + public void onAnimationEnd(Animator animation) { + mCallback.onChildDismissed(animView); + } + + public void onAnimationCancel(Animator animation) { + mCallback.onChildDismissed(animView); + } + }); + anim.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + if (FADE_OUT_DURING_SWIPE) { + animView.setAlpha(getAlphaForOffset(animView, getContentSize(animView))); + } + invalidateGlobalRegion(animView); + } + }); + anim.start(); + } + + public void snapChild(final View animView, float velocity) { + ObjectAnimator anim = createTranslationAnimation(animView, 0); + int duration = SNAP_ANIM_LEN; + anim.setDuration(duration); + anim.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + if (FADE_OUT_DURING_SWIPE) { + animView.setAlpha(getAlphaForOffset(animView, getContentSize(animView))); + } + invalidateGlobalRegion(animView); + } + }); + anim.start(); + } + + public boolean onTouchEvent(MotionEvent ev) { + if (!mDragging) { + return false; + } + + mVelocityTracker.addMovement(ev); + final int action = ev.getAction(); + switch (action) { + case MotionEvent.ACTION_OUTSIDE: + case MotionEvent.ACTION_MOVE: + if (mCurrView != null) { + float delta = getPos(ev) - mInitialTouchPos; + // don't let items that can't be dismissed be dragged more than + // maxScrollDistance + if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) { + float size = getSize(mCurrView); + float maxScrollDistance = 0.15f * size; + if (Math.abs(delta) >= size) { + delta = delta > 0 ? maxScrollDistance : -maxScrollDistance; + } else { + delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2)); + } + } + setTranslation(mCurrView, delta); + if (FADE_OUT_DURING_SWIPE) { + mCurrView.setAlpha(getAlphaForOffset(mCurrView, getContentSize(mCurrView))); + } + invalidateGlobalRegion(mCurrView); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mCurrView != null) { + float maxVelocity = 1000; // px/sec + mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity); + float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale; + float velocity = getVelocity(mVelocityTracker); + float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker); + + // Decide whether to dismiss the current view + boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH && + Math.abs(getPos(mCurrView)) > 0.4 * getSize(mCurrView); + boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) && + (Math.abs(velocity) > Math.abs(perpendicularVelocity)) && + (velocity > 0) == (getPos(mCurrView) > 0); + + boolean dismissChild = mCallback.canChildBeDismissed(mCurrView) && + (childSwipedFastEnough || childSwipedFarEnough); + + if (dismissChild) { + // flingadingy + dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f); + } else { + // snappity + snapChild(mCurrView, velocity); + } + } + break; + } + return true; + } + + public interface Callback { + View getChildAtPosition(MotionEvent ev); + + View getChildContentView(View v); + + boolean canChildBeDismissed(View v); + + void onBeginDrag(View v); + + void onChildDismissed(View v); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java index 797f94cceb1d..5609ead6e284 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java @@ -25,6 +25,6 @@ public interface RecentsCallback { static final int SWIPE_DOWN = 3; void handleOnClick(View selectedView); - void handleSwipe(View selectedView, int direction); + void handleSwipe(View selectedView); void handleLongPress(View selectedView, View anchorView); } diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java index 2a5d1dd7ba70..14efdd0683c1 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java @@ -16,52 +16,35 @@ package com.android.systemui.recent; -import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.res.Configuration; import android.database.DataSetObserver; -import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; -import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import com.android.systemui.R; +import com.android.systemui.SwipeHelper; +import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter; -public class RecentsHorizontalScrollView extends HorizontalScrollView { +public class RecentsHorizontalScrollView extends HorizontalScrollView + implements SwipeHelper.Callback { private static final String TAG = RecentsPanelView.TAG; - private static final boolean DEBUG_INVALIDATE = false; private static final boolean DEBUG = RecentsPanelView.DEBUG; private LinearLayout mLinearLayout; private ActivityDescriptionAdapter mAdapter; private RecentsCallback mCallback; protected int mLastScrollPosition; - private View mCurrentView; - private float mLastY; - private boolean mDragging; - private VelocityTracker mVelocityTracker; - private float mDensityScale; - private float mPagingTouchSlop; + private SwipeHelper mSwipeHelper; private OnLongClickListener mOnLongClick = new OnLongClickListener() { public boolean onLongClick(View v) { final View anchorView = v.findViewById(R.id.app_description); - mCurrentView = v; mCallback.handleLongPress(v, anchorView); - mCurrentView = null; // make sure we don't accept the return click from this return true; } }; @@ -72,8 +55,9 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView { public RecentsHorizontalScrollView(Context context, AttributeSet attrs) { super(context, attrs, 0); - mDensityScale = getResources().getDisplayMetrics().density; - mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + float densityScale = getResources().getDisplayMetrics().density; + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop); } private int scrollPositionOfMostRecent() { @@ -84,8 +68,16 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView { mLinearLayout.removeAllViews(); for (int i = 0; i < mAdapter.getCount(); i++) { final View view = mAdapter.getView(i, null, mLinearLayout); - view.setClickable(true); + view.setLongClickable(true); view.setOnLongClickListener(mOnLongClick); + + final View thumbnail = getChildContentView(view); + // thumbnail is set to clickable in the layout file + thumbnail.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mCallback.handleOnClick(view); + } + }); mLinearLayout.addView(view); } // Scroll to end after layout. @@ -99,175 +91,52 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView { @Override public void removeViewInLayout(final View view) { - ObjectAnimator anim = animateClosed(view, Constants.MAX_ESCAPE_ANIMATION_DURATION, - "y", view.getY(), view.getY() + view.getHeight()); - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - RecentsHorizontalScrollView.super.removeView(view); - } - }); - anim.start(); + dismissChild(view); } - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mDragging = false; - mLastY = ev.getY(); - final float x = ev.getX() + getScrollX(); - final float y = ev.getY() + getScrollY(); - mCurrentView = null; - for (int i = 0; i < mLinearLayout.getChildCount(); i++) { - View item = mLinearLayout.getChildAt(i); - if (x >= item.getLeft() && x < item.getRight() - && y >= item.getTop() && y < item.getBottom()) { - mCurrentView = item; - if (DEBUG) Log.v(TAG, "Hit item " + item); - break; - } - } - break; - - case MotionEvent.ACTION_MOVE: - float delta = ev.getY() - mLastY; - if (DEBUG) Log.v(TAG, "ACTION_MOVE : " + delta); - if (Math.abs(delta) > mPagingTouchSlop) { - mDragging = true; - } - break; - - case MotionEvent.ACTION_UP: - if (mCurrentView != null) { - mCallback.handleOnClick(mCurrentView); - } - mDragging = false; - break; - } - return mDragging ? true : super.onInterceptTouchEvent(ev); - } - - private float getAlphaForOffset(View view, float thumbHeight) { - final float fadeHeight = Constants.ALPHA_FADE_END * thumbHeight; - float result = 1.0f; - if (view.getY() >= thumbHeight * Constants.ALPHA_FADE_START) { - result = 1.0f - (view.getY() - thumbHeight * Constants.ALPHA_FADE_START) / fadeHeight; - } else if (view.getY() < thumbHeight * (1.0f - Constants.ALPHA_FADE_START)) { - result = 1.0f + (thumbHeight * Constants.ALPHA_FADE_START + view.getY()) / fadeHeight; - } - return result; + return mSwipeHelper.onInterceptTouchEvent(ev) || + super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { - if (!mDragging) { - return super.onTouchEvent(ev); - } - - mVelocityTracker.addMovement(ev); - - final View animView = mCurrentView; - - switch (ev.getAction()) { - case MotionEvent.ACTION_MOVE: - if (animView != null) { - final float delta = ev.getY() - mLastY; - final View thumb = animView.findViewById(R.id.app_thumbnail); - animView.setY(animView.getY() + delta); - animView.setAlpha(getAlphaForOffset(animView, thumb.getHeight())); - invalidateGlobalRegion(animView); - } - mLastY = ev.getY(); - break; + return mSwipeHelper.onTouchEvent(ev) || + super.onTouchEvent(ev); + } - case MotionEvent.ACTION_UP: - final ObjectAnimator anim; - if (animView != null) { - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, 10000); - final float velocityX = velocityTracker.getXVelocity(); - final float velocityY = velocityTracker.getYVelocity(); - final float curY = animView.getY(); - final float newY = (velocityY >= 0.0f ? 1 : -1) * animView.getHeight(); - final float maxVelocity = Constants.ESCAPE_VELOCITY * mDensityScale; - if (Math.abs(velocityY) > Math.abs(velocityX) - && Math.abs(velocityY) > maxVelocity - && (velocityY >= 0.0f) == (animView.getY() >= 0)) { - long duration = - (long) (Math.abs(newY - curY) * 1000.0f / Math.abs(velocityY)); - duration = Math.min(duration, Constants.MAX_ESCAPE_ANIMATION_DURATION); - anim = animateClosed(animView, duration, "y", curY, newY); - } else { // Animate back to position - long duration = Math.abs(velocityY) > 0.0f ? - (long) (Math.abs(newY - curY) * 1000.0f / Math.abs(velocityY)) - : Constants.SNAP_BACK_DURATION; - duration = Math.min(duration, Constants.SNAP_BACK_DURATION); - anim = ObjectAnimator.ofFloat(animView, "y", animView.getY(), 0.0f); - anim.setInterpolator(new DecelerateInterpolator(4.0f)); - anim.setDuration(duration); - } + public boolean canChildBeDismissed(View v) { + return true; + } - final View thumb = animView.findViewById(R.id.app_thumbnail); - anim.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - animView.setAlpha(getAlphaForOffset(animView, thumb.getHeight())); - invalidateGlobalRegion(animView); - } - }); - anim.start(); - } + public void dismissChild(View v) { + mSwipeHelper.dismissChild(v, 0); + } - mVelocityTracker.recycle(); - mVelocityTracker = null; - break; - } - return true; + public void onChildDismissed(View v) { + mLinearLayout.removeView(v); + mCallback.handleSwipe(v); } - private ObjectAnimator animateClosed(final View animView, long duration, - String attr, float from, float to) { - ObjectAnimator anim = ObjectAnimator.ofFloat(animView, attr, from, to); - anim.setInterpolator(new LinearInterpolator()); - final int swipeDirection = animView.getX() >= 0.0f ? - RecentsCallback.SWIPE_RIGHT : RecentsCallback.SWIPE_LEFT; - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - mLinearLayout.removeView(animView); - mCallback.handleSwipe(animView, swipeDirection); - } - public void onAnimationCancel(Animator animation) { - mLinearLayout.removeView(animView); - mCallback.handleSwipe(animView, swipeDirection); - } - }); - anim.setDuration(duration); - return anim; + public void onBeginDrag(View v) { } - void invalidateGlobalRegion(View view) { - RectF childBounds - = new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); - childBounds.offset(view.getX(), view.getY()); - if (DEBUG_INVALIDATE) Log.v(TAG, "-------------"); - while (view.getParent() != null && view.getParent() instanceof View) { - view = (View) view.getParent(); - view.getMatrix().mapRect(childBounds); - view.invalidate((int) Math.floor(childBounds.left), - (int) Math.floor(childBounds.top), - (int) Math.ceil(childBounds.right), - (int) Math.ceil(childBounds.bottom)); - if (DEBUG_INVALIDATE) { - Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left) - + "," + (int) Math.floor(childBounds.top) - + "," + (int) Math.ceil(childBounds.right) - + "," + (int) Math.ceil(childBounds.bottom)); + public View getChildAtPosition(MotionEvent ev) { + final float x = ev.getX() + getScrollX(); + final float y = ev.getY() + getScrollY(); + for (int i = 0; i < mLinearLayout.getChildCount(); i++) { + View item = mLinearLayout.getChildAt(i); + if (x >= item.getLeft() && x < item.getRight() + && y >= item.getTop() && y < item.getBottom()) { + return item; } } + return null; + } + + public View getChildContentView(View v) { + return v.findViewById(R.id.app_thumbnail); } @Override @@ -283,8 +152,10 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mDensityScale = getResources().getDisplayMetrics().density; - mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + float densityScale = getResources().getDisplayMetrics().density; + mSwipeHelper.setDensityScale(densityScale); + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); } private void setOverScrollEffectPadding(int leftPadding, int i) { diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java index ea544456430d..e988b688a71f 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java @@ -529,7 +529,7 @@ public class RecentsPanelView extends RelativeLayout handleOnClick(view); } - public void handleSwipe(View view, int direction) { + public void handleSwipe(View view) { ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription; if (DEBUG) Log.v(TAG, "Jettison " + ad.label); mActivityDescriptions.remove(ad); diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java index 47ee4aa3f8f0..1bcc413cd15b 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java @@ -16,53 +16,35 @@ package com.android.systemui.recent; -import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.res.Configuration; import android.database.DataSetObserver; -import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; -import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; import android.widget.ScrollView; import com.android.systemui.R; +import com.android.systemui.SwipeHelper; +import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter; -public class RecentsVerticalScrollView extends ScrollView { +public class RecentsVerticalScrollView extends ScrollView implements SwipeHelper.Callback { private static final String TAG = RecentsPanelView.TAG; - private static final boolean DEBUG_INVALIDATE = false; private static final boolean DEBUG = RecentsPanelView.DEBUG; private LinearLayout mLinearLayout; private ActivityDescriptionAdapter mAdapter; private RecentsCallback mCallback; protected int mLastScrollPosition; - private View mCurrentView; - private float mLastX; - private boolean mDragging; - private VelocityTracker mVelocityTracker; - private float mDensityScale; - private float mPagingTouchSlop; + private SwipeHelper mSwipeHelper; + private OnLongClickListener mOnLongClick = new OnLongClickListener() { public boolean onLongClick(View v) { final View anchorView = v.findViewById(R.id.app_description); - mCurrentView = v; mCallback.handleLongPress(v, anchorView); - mCurrentView = null; // make sure we don't accept the return click from this return true; } }; @@ -73,8 +55,9 @@ public class RecentsVerticalScrollView extends ScrollView { public RecentsVerticalScrollView(Context context, AttributeSet attrs) { super(context, attrs, 0); - mDensityScale = getResources().getDisplayMetrics().density; - mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + float densityScale = getResources().getDisplayMetrics().density; + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); } private int scrollPositionOfMostRecent() { @@ -87,6 +70,15 @@ public class RecentsVerticalScrollView extends ScrollView { final View view = mAdapter.getView(i, null, mLinearLayout); view.setClickable(true); view.setOnLongClickListener(mOnLongClick); + + final View thumbnail = getChildContentView(view); + // thumbnail is set to clickable in the layout file + thumbnail.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mCallback.handleOnClick(view); + } + }); + mLinearLayout.addView(view); } // Scroll to end after layout. @@ -100,175 +92,52 @@ public class RecentsVerticalScrollView extends ScrollView { @Override public void removeViewInLayout(final View view) { - ObjectAnimator anim = animateClosed(view, Constants.MAX_ESCAPE_ANIMATION_DURATION, - "x", view.getX(), view.getX() + view.getWidth()); - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - RecentsVerticalScrollView.super.removeView(view); - } - }); - anim.start(); + dismissChild(view); } - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mDragging = false; - mLastX = ev.getX(); - final float x = ev.getX() + getScrollX(); - final float y = ev.getY() + getScrollY(); - mCurrentView = null; - for (int i = 0; i < mLinearLayout.getChildCount(); i++) { - View item = mLinearLayout.getChildAt(i); - if (x >= item.getLeft() && x < item.getRight() - && y >= item.getTop() && y < item.getBottom()) { - mCurrentView = item; - Log.v(TAG, "Hit item " + item); - break; - } - } - break; - - case MotionEvent.ACTION_MOVE: - float delta = ev.getX() - mLastX; - if (DEBUG) Log.v(TAG, "ACTION_MOVE : " + delta); - if (Math.abs(delta) > mPagingTouchSlop) { - mDragging = true; - } - break; - - case MotionEvent.ACTION_UP: - if (mCurrentView != null) { - mCallback.handleOnClick(mCurrentView); - } - mDragging = false; - break; - } - return mDragging ? true : super.onInterceptTouchEvent(ev); - } - - private float getAlphaForOffset(View view, float thumbWidth) { - final float fadeWidth = Constants.ALPHA_FADE_END * thumbWidth; - float result = 1.0f; - if (view.getX() >= thumbWidth*Constants.ALPHA_FADE_START) { - result = 1.0f - (view.getX() - thumbWidth*Constants.ALPHA_FADE_START) / fadeWidth; - } else if (view.getX() < thumbWidth* (1.0f - Constants.ALPHA_FADE_START)) { - result = 1.0f + (thumbWidth*Constants.ALPHA_FADE_START + view.getX()) / fadeWidth; - } - return result; + return mSwipeHelper.onInterceptTouchEvent(ev) || + super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { - if (!mDragging) { - return super.onTouchEvent(ev); - } - - mVelocityTracker.addMovement(ev); - - final View animView = mCurrentView; - - switch (ev.getAction()) { - case MotionEvent.ACTION_MOVE: - if (animView != null) { - final float delta = ev.getX() - mLastX; - final View thumb = animView.findViewById(R.id.app_thumbnail); - animView.setX(animView.getX() + delta); - animView.setAlpha(getAlphaForOffset(animView, thumb.getWidth())); - invalidateGlobalRegion(animView); - } - mLastX = ev.getX(); - break; + return mSwipeHelper.onTouchEvent(ev) || + super.onTouchEvent(ev); + } - case MotionEvent.ACTION_UP: - final ObjectAnimator anim; - if (animView != null) { - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, 10000); - final float velocityX = velocityTracker.getXVelocity(); - final float velocityY = velocityTracker.getYVelocity(); - final float curX = animView.getX(); - final float newX = (velocityX >= 0.0f ? 1 : -1) * animView.getWidth(); - final float maxVelocity = Constants.ESCAPE_VELOCITY * mDensityScale; - if (Math.abs(velocityX) > Math.abs(velocityY) - && Math.abs(velocityX) > maxVelocity - && (velocityX >= 0.0f) == (animView.getX() >= 0)) { - long duration = - (long) (Math.abs(newX-curX) * 1000.0f / Math.abs(velocityX)); - duration = Math.min(duration, Constants.MAX_ESCAPE_ANIMATION_DURATION); - anim = animateClosed(animView, duration, "x", curX, newX); - } else { // Animate back to position - long duration = Math.abs(velocityX) > 0.0f ? - (long) (Math.abs(newX-curX) * 1000.0f / Math.abs(velocityX)) - : Constants.SNAP_BACK_DURATION; - duration = Math.min(duration, Constants.SNAP_BACK_DURATION); - anim = ObjectAnimator.ofFloat(animView, "x", animView.getX(), 0.0f); - anim.setInterpolator(new DecelerateInterpolator(4.0f)); - anim.setDuration(duration); - } + public boolean canChildBeDismissed(View v) { + return true; + } - final View thumb = animView.findViewById(R.id.app_thumbnail); - anim.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - animView.setAlpha(getAlphaForOffset(animView, thumb.getWidth())); - invalidateGlobalRegion(animView); - } - }); - anim.start(); - } + public void dismissChild(View v) { + mSwipeHelper.dismissChild(v, 0); + } - mVelocityTracker.recycle(); - mVelocityTracker = null; - break; - } - return true; + public void onChildDismissed(View v) { + mLinearLayout.removeView(v); + mCallback.handleSwipe(v); } - private ObjectAnimator animateClosed(final View animView, long duration, - String attr, float from, float to) { - ObjectAnimator anim = ObjectAnimator.ofFloat(animView, attr, from, to); - anim.setInterpolator(new LinearInterpolator()); - final int swipeDirection = animView.getX() >= 0.0f ? - RecentsCallback.SWIPE_RIGHT : RecentsCallback.SWIPE_LEFT; - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - mLinearLayout.removeView(animView); - mCallback.handleSwipe(animView, swipeDirection); - } - public void onAnimationCancel(Animator animation) { - mLinearLayout.removeView(animView); - mCallback.handleSwipe(animView, swipeDirection); - } - }); - anim.setDuration(duration); - return anim; + public void onBeginDrag(View v) { } - void invalidateGlobalRegion(View view) { - RectF childBounds - = new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); - childBounds.offset(view.getX(), view.getY()); - if (DEBUG_INVALIDATE) Log.v(TAG, "-------------"); - while (view.getParent() != null && view.getParent() instanceof View) { - view = (View) view.getParent(); - view.getMatrix().mapRect(childBounds); - view.invalidate((int) Math.floor(childBounds.left), - (int) Math.floor(childBounds.top), - (int) Math.ceil(childBounds.right), - (int) Math.ceil(childBounds.bottom)); - if (DEBUG_INVALIDATE) { - Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left) - + "," + (int) Math.floor(childBounds.top) - + "," + (int) Math.ceil(childBounds.right) - + "," + (int) Math.ceil(childBounds.bottom)); + public View getChildAtPosition(MotionEvent ev) { + final float x = ev.getX() + getScrollX(); + final float y = ev.getY() + getScrollY(); + for (int i = 0; i < mLinearLayout.getChildCount(); i++) { + View item = mLinearLayout.getChildAt(i); + if (x >= item.getLeft() && x < item.getRight() + && y >= item.getTop() && y < item.getBottom()) { + return item; } } + return null; + } + + public View getChildContentView(View v) { + return v.findViewById(R.id.app_thumbnail); } @Override @@ -284,8 +153,10 @@ public class RecentsVerticalScrollView extends ScrollView { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mDensityScale = getResources().getDisplayMetrics().density; - mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + float densityScale = getResources().getDisplayMetrics().density; + mSwipeHelper.setDensityScale(densityScale); + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); } private void setOverScrollEffectPadding(int leftPadding, int i) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 83a5578cfa36..3e037c14b804 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -78,7 +78,8 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi SaveImageInBackgroundData> { private static final String TAG = "SaveImageInBackgroundTask"; private static final String SCREENSHOTS_DIR_NAME = "Screenshots"; - private static final String SCREENSHOT_FILE_PATH_TEMPLATE = "%s/%s/Screenshot_%s-%d.png"; + private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png"; + private static final String SCREENSHOT_FILE_PATH_TEMPLATE = "%s/%s/%s"; @Override protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) { @@ -87,20 +88,20 @@ 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 date = new SimpleDateFormat("yyyy-MM-dd-kk-mm-ss").format(new Date(currentTime)); String imageDir = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES).getAbsolutePath(); - String imageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, - imageDir, SCREENSHOTS_DIR_NAME, - date, currentTime % 1000); + String imageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, date); + String imageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, imageDir, + SCREENSHOTS_DIR_NAME, imageFileName); // Save the screenshot to the MediaStore ContentValues values = new ContentValues(); values.put(MediaStore.Images.ImageColumns.DATA, imageFilePath); - values.put(MediaStore.Images.ImageColumns.TITLE, "Screenshot"); - values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, "Screenshot"); + values.put(MediaStore.Images.ImageColumns.TITLE, imageFileName); + values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, imageFileName); values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, currentTime); values.put(MediaStore.Images.ImageColumns.DATE_ADDED, currentTime); values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, currentTime); @@ -114,7 +115,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 d5885bb81c25..90234c77560c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java @@ -21,47 +21,33 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeAnimator; -import android.animation.ValueAnimator; import android.content.Context; -import android.content.res.Resources; +import android.content.res.Configuration; import android.content.res.TypedArray; -import android.graphics.Canvas; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.Log; import android.util.Slog; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.animation.AccelerateInterpolator; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import java.util.HashSet; import com.android.systemui.R; +import com.android.systemui.SwipeHelper; + +import java.util.HashSet; -public class NotificationRowLayout extends ViewGroup { +public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Callback { private static final String TAG = "NotificationRowLayout"; private static final boolean DEBUG = false; private static final boolean SLOW_ANIMATIONS = false; // DEBUG; private static final boolean ANIMATE_LAYOUT = true; - private static final boolean CLEAR_IF_SWIPED_FAR_ENOUGH = true; - - private static final boolean CONSTRAIN_SWIPE_ON_PERMANENT = true; - private static final int APPEAR_ANIM_LEN = SLOW_ANIMATIONS ? 5000 : 250; private static final int DISAPPEAR_ANIM_LEN = APPEAR_ANIM_LEN; - private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; - - private static final float SWIPE_ESCAPE_VELOCITY = 1500f; - private static final float SWIPE_ANIM_VELOCITY_MIN = 1000f; Rect mTmpRect = new Rect(); int mNumRows = 0; @@ -71,10 +57,7 @@ public class NotificationRowLayout extends ViewGroup { HashSet<View> mAppearingViews = new HashSet<View>(); HashSet<View> mDisappearingViews = new HashSet<View>(); - VelocityTracker mVT; - float mInitialTouchX, mInitialTouchY; - View mSlidingChild = null; - float mLiftoffVelocity; + private SwipeHelper mSwipeHelper; public NotificationRowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -83,8 +66,6 @@ public class NotificationRowLayout extends ViewGroup { public NotificationRowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mVT = VelocityTracker.obtain(); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NotificationRowLayout, defStyle, 0); mRowHeight = a.getDimensionPixelSize(R.styleable.NotificationRowLayout_rowHeight, 0); @@ -107,117 +88,71 @@ public class NotificationRowLayout extends ViewGroup { setBackgroundColor(0x80FF8000); } + float densityScale = getResources().getDisplayMetrics().density; + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); } - // Swipey code @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - final int action = ev.getAction(); -// if (DEBUG) Slog.d(TAG, "intercepting touch event: " + ev); - switch (action) { - case MotionEvent.ACTION_DOWN: - mVT.clear(); - mVT.addMovement(ev); - mInitialTouchX = ev.getX(); - mInitialTouchY = ev.getY(); - mSlidingChild = null; - break; - case MotionEvent.ACTION_MOVE: - mVT.addMovement(ev); - if (mSlidingChild == null) { - if (Math.abs(ev.getX() - mInitialTouchX) > 4) { // slide slop - - // find the view under the pointer, accounting for GONE views - final int count = getChildCount(); - int y = 0; - int childIdx = 0; - for (; childIdx < count; childIdx++) { - mSlidingChild = getChildAt(childIdx); - if (mSlidingChild.getVisibility() == GONE) { - continue; - } - y += mRowHeight; - if (mInitialTouchY < y) break; - } - - mInitialTouchX -= mSlidingChild.getTranslationX(); - mSlidingChild.animate().cancel(); - - if (DEBUG) { - Slog.d(TAG, String.format( - "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; - } - return mSlidingChild != null; + if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); + return mSwipeHelper.onInterceptTouchEvent(ev) || + super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return mSwipeHelper.onTouchEvent(ev) || + super.onTouchEvent(ev); } - protected boolean canBeCleared(View v) { + public boolean canChildBeDismissed(View v) { final View veto = v.findViewById(R.id.veto); return (veto != null && veto.getVisibility() != View.GONE); } - protected boolean clear(View v) { + public void onChildDismissed(View v) { final View veto = v.findViewById(R.id.veto); if (veto != null && veto.getVisibility() != View.GONE) { veto.performClick(); - return true; } - return false; } - @Override - public boolean onTouchEvent(MotionEvent ev) { - final int action = ev.getAction(); -// if (DEBUG) Slog.d(TAG, "touch event: " + ev + " sliding: " + mSlidingChild); - if (mSlidingChild != null) { - switch (action) { - case MotionEvent.ACTION_OUTSIDE: - case MotionEvent.ACTION_MOVE: - mVT.addMovement(ev); - - float delta = (ev.getX() - mInitialTouchX); - if (CONSTRAIN_SWIPE_ON_PERMANENT && !canBeCleared(mSlidingChild)) { - delta = Math.copySign( - Math.min(Math.abs(delta), - mSlidingChild.getMeasuredWidth() * 0.2f), delta); - } - mSlidingChild.setTranslationX(delta); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mVT.addMovement(ev); - mVT.computeCurrentVelocity(1000 /* px/sec */); - if (DEBUG) Slog.d(TAG, "exit velocity: " + mVT.getXVelocity()); - boolean restore = true; - mLiftoffVelocity = mVT.getXVelocity(); - if (Math.abs(mLiftoffVelocity) > SWIPE_ESCAPE_VELOCITY - || (CLEAR_IF_SWIPED_FAR_ENOUGH && - (mSlidingChild.getTranslationX() * 2) > mSlidingChild.getMeasuredWidth())) - { - - // flingadingy - restore = ! clear(mSlidingChild); - } - if (restore) { - // snappity - mSlidingChild.animate().translationX(0) - .setDuration(SNAP_ANIM_LEN) - .start(); - } - break; + public void onBeginDrag(View v) { + // We need to prevent the surrounding ScrollView from intercepting us now; + // the scroll position will be locked while we swipe + requestDisallowInterceptTouchEvent(true); + } + + public View getChildAtPosition(MotionEvent ev) { + // find the view under the pointer, accounting for GONE views + final int count = getChildCount(); + int y = 0; + int touchY = (int) ev.getY(); + int childIdx = 0; + View slidingChild; + for (; childIdx < count; childIdx++) { + slidingChild = getChildAt(childIdx); + if (slidingChild.getVisibility() == GONE) { + continue; } - return true; + y += mRowHeight; + if (touchY < y) return slidingChild; } - return false; + return null; + } + + public View getChildContentView(View v) { + return v; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + float densityScale = getResources().getDisplayMetrics().density; + mSwipeHelper.setDensityScale(densityScale); + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); } //** @@ -260,9 +195,10 @@ public class NotificationRowLayout extends ViewGroup { child.setPivotY(0); - final float velocity = (mSlidingChild == child) - ? Math.min(mLiftoffVelocity, SWIPE_ANIM_VELOCITY_MIN) - : SWIPE_ESCAPE_VELOCITY; + //final float velocity = (mSlidingChild == child) + // ? Math.min(mLiftoffVelocity, SWIPE_ANIM_VELOCITY_MIN) + // : SWIPE_ESCAPE_VELOCITY; + final float velocity = 0f; final TimeAnimator zoom = new TimeAnimator(); zoom.setTimeListener(new TimeAnimator.TimeListener() { @Override diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index e0debf7ca994..bfc3cddb334f 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -2040,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); diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index ae13ab5a17a9..b7f6adffca1a 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -2444,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; } @@ -2541,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 0323fe0001a3..cb1f92134135 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -1043,6 +1043,25 @@ status_t AudioFlinger::ThreadBase::dumpBase(int fd, const Vector<String16>& args return NO_ERROR; } +status_t AudioFlinger::ThreadBase::dumpEffectChains(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "\n- %d Effect Chains:\n", mEffectChains.size()); + write(fd, buffer, strlen(buffer)); + + for (size_t i = 0; i < mEffectChains.size(); ++i) { + sp<EffectChain> chain = mEffectChains[i]; + if (chain != 0) { + chain->dump(fd, args); + } + } + return NO_ERROR; +} + + // ---------------------------------------------------------------------------- AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinger, @@ -1111,24 +1130,6 @@ status_t AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16> return NO_ERROR; } -status_t AudioFlinger::PlaybackThread::dumpEffectChains(int fd, const Vector<String16>& args) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - - snprintf(buffer, SIZE, "\n- %d Effect Chains:\n", mEffectChains.size()); - write(fd, buffer, strlen(buffer)); - - for (size_t i = 0; i < mEffectChains.size(); ++i) { - sp<EffectChain> chain = mEffectChains[i]; - if (chain != 0) { - chain->dump(fd, args); - } - } - return NO_ERROR; -} - status_t AudioFlinger::PlaybackThread::dumpInternals(int fd, const Vector<String16>& args) { const size_t SIZE = 256; @@ -4178,6 +4179,7 @@ status_t AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args) write(fd, result.string(), result.size()); dumpBase(fd, args); + dumpEffectChains(fd, args); return NO_ERROR; } diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index fff4f06ace47..e2cf946d0ace 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -285,6 +285,7 @@ private: }; status_t dumpBase(int fd, const Vector<String16>& args); + status_t dumpEffectChains(int fd, const Vector<String16>& args); // base for record and playback class TrackBase : public AudioBufferProvider, public RefBase { @@ -724,7 +725,6 @@ private: virtual status_t dumpInternals(int fd, const Vector<String16>& args); status_t dumpTracks(int fd, const Vector<String16>& args); - status_t dumpEffectChains(int fd, const Vector<String16>& args); SortedVector< sp<Track> > mTracks; // mStreamTypes[] uses 1 additionnal stream type internally for the OutputTrack used by DuplicatingThread diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp index dd1e153621c7..6d06d83fab12 100644 --- a/services/audioflinger/AudioPolicyService.cpp +++ b/services/audioflinger/AudioPolicyService.cpp @@ -497,6 +497,43 @@ bool AudioPolicyService::isStreamActive(int stream, uint32_t inPastMs) const return mpAudioPolicy->is_stream_active(mpAudioPolicy, stream, inPastMs); } +status_t AudioPolicyService::queryDefaultPreProcessing(int audioSession, + effect_descriptor_t *descriptors, + uint32_t *count) +{ + + if (mpAudioPolicy == NULL) { + *count = 0; + return NO_INIT; + } + Mutex::Autolock _l(mLock); + status_t status = NO_ERROR; + + size_t index; + for (index = 0; index < mInputs.size(); index++) { + if (mInputs.valueAt(index)->mSessionId == audioSession) { + break; + } + } + if (index == mInputs.size()) { + *count = 0; + return BAD_VALUE; + } + Vector< sp<AudioEffect> > effects = mInputs.valueAt(index)->mEffects; + + for (size_t i = 0; i < effects.size(); i++) { + effect_descriptor_t desc = effects[i]->descriptor(); + if (i < *count) { + memcpy(descriptors + i, &desc, sizeof(effect_descriptor_t)); + } + } + if (effects.size() > *count) { + status = NO_MEMORY; + } + *count = effects.size(); + return status; +} + void AudioPolicyService::binderDied(const wp<IBinder>& who) { LOGW("binderDied() %p, tid %d, calling tid %d", who.unsafe_get(), gettid(), IPCThreadState::self()->getCallingPid()); diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h index 62ad29ee5219..834b79421c78 100644 --- a/services/audioflinger/AudioPolicyService.h +++ b/services/audioflinger/AudioPolicyService.h @@ -104,6 +104,9 @@ public: virtual status_t unregisterEffect(int id); virtual bool isStreamActive(int stream, uint32_t inPastMs = 0) const; + virtual status_t queryDefaultPreProcessing(int audioSession, + effect_descriptor_t *descriptors, + uint32_t *count); virtual status_t onTransact( uint32_t code, const Parcel& data, diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index ea78b525d26b..0843948df667 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -1926,7 +1926,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } // Caller must grab mDnsLock. - private boolean updateDns(String network, Collection<InetAddress> dnses, String domains) { + private boolean updateDns(String network, String iface, + Collection<InetAddress> dnses, String domains) { boolean changed = false; int last = 0; if (dnses.size() == 0 && mDefaultDns != null) { @@ -1962,6 +1963,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { } mNumDnsEntries = last; + if (changed) { + try { + mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses)); + mNetd.setDefaultInterfaceForDns(iface); + } catch (Exception e) { + Slog.e(TAG, "exception setting default dns interface: " + e); + } + } if (!domains.equals(SystemProperties.get("net.dns.search"))) { SystemProperties.set("net.dns.search", domains); changed = true; @@ -1981,10 +1990,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { String network = nt.getNetworkInfo().getTypeName(); synchronized (mDnsLock) { if (!mDnsOverridden) { - changed = updateDns(network, dnses, ""); + changed = updateDns(network, p.getInterfaceName(), dnses, ""); } } } else { + try { + mNetd.setDnsServersForInterface(Integer.toString(netType), + NetworkUtils.makeStrings(dnses)); + } catch (Exception e) { + Slog.e(TAG, "exception setting dns servers: " + e); + } // set per-pid dns for attached secondary nets List pids = mNetRequestersPids[netType]; for (int y=0; y< pids.size(); y++) { @@ -2262,6 +2277,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() { @@ -2677,7 +2701,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Apply DNS changes. boolean changed = false; synchronized (mDnsLock) { - changed = updateDns("VPN", addresses, domains); + changed = updateDns("VPN", "VPN", addresses, domains); mDnsOverridden = true; } if (changed) { diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index d39f565f7f84..73d790a073c0 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1127,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 2366fcb41c8d..17ad268f0386 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -849,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) { @@ -897,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/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 5d7a48f491f4..8031c4e1f53e 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -469,6 +469,24 @@ public class NotificationManagerService extends INotificationManager.Stub record = mToastQueue.get(index); record.update(duration); } else { + // Limit the number of toasts that any given package except the android + // package can enqueue. Prevents DOS attacks and deals with leaks. + if (!"android".equals(pkg)) { + int count = 0; + final int N = mToastQueue.size(); + for (int i=0; i<N; i++) { + final ToastRecord r = mToastQueue.get(i); + if (r.pkg.equals(pkg)) { + count++; + if (count >= MAX_PACKAGE_NOTIFICATIONS) { + Slog.e(TAG, "Package has already posted " + count + + " toasts. Not showing more. Package=" + pkg); + return; + } + } + } + } + record = new ToastRecord(callingPid, pkg, callback, duration); mToastQueue.add(record); index = mToastQueue.size() - 1; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 766659153821..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; @@ -299,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); diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java index 4a0c837df0c6..3e76a3a1ae4e 100644 --- a/services/java/com/android/server/TextServicesManagerService.java +++ b/services/java/com/android/server/TextServicesManagerService.java @@ -81,14 +81,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { 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); - } + if (sci == null) return; final String packageName = sci.getPackageName(); final int change = isPackageDisappearing(packageName); if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) { @@ -125,6 +118,9 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { list.add(sci); map.put(sci.getId(), sci); } + if (DBG) { + Slog.d(TAG, "buildSpellCheckerMapLocked: " + list.size() + "," + map.size()); + } } // TODO: find an appropriate spell checker for specified locale @@ -138,6 +134,9 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { for (int i = 0; i < spellCheckersCount; ++i) { final SpellCheckerInfo sci = mSpellCheckerList.get(i); if (prefPackage.equals(sci.getPackageName())) { + if (DBG) { + Slog.d(TAG, "findAvailSpellCheckerLocked: " + sci.getPackageName()); + } return sci; } } @@ -153,11 +152,20 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { @Override public SpellCheckerInfo getCurrentSpellChecker(String locale) { synchronized (mSpellCheckerMap) { - final String curSpellCheckerId = + 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; + final SpellCheckerInfo sci = findAvailSpellCheckerLocked(null, null); + if (sci == null) return null; + // 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); + return sci; } return mSpellCheckerMap.get(curSpellCheckerId); } @@ -198,7 +206,21 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } @Override + public SpellCheckerInfo[] getEnabledSpellCheckers() { + if (DBG) { + Slog.d(TAG, "getEnabledSpellCheckers: " + mSpellCheckerList.size()); + for (int i = 0; i < mSpellCheckerList.size(); ++i) { + Slog.d(TAG, "EnabledSpellCheckers: " + mSpellCheckerList.get(i).getPackageName()); + } + } + return mSpellCheckerList.toArray(new SpellCheckerInfo[mSpellCheckerList.size()]); + } + + @Override public void finishSpellCheckerService(ISpellCheckerSessionListener listener) { + if (DBG) { + Slog.d(TAG, "FinishSpellCheckerService"); + } synchronized(mSpellCheckerMap) { for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) { if (group == null) continue; @@ -208,6 +230,9 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } 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()); @@ -229,6 +254,9 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } public void onServiceConnected(ISpellCheckerService spellChecker) { + if (DBG) { + Slog.d(TAG, "onServiceConnected"); + } synchronized(mSpellCheckerMap) { for (InternalDeathRecipient listener : mListeners) { try { @@ -243,6 +271,9 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { public void addListener(ITextServicesSessionListener tsListener, String locale, ISpellCheckerSessionListener scListener) { + if (DBG) { + Slog.d(TAG, "addListener: " + locale); + } synchronized(mSpellCheckerMap) { try { final int size = mListeners.size(); @@ -265,6 +296,9 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } public void removeListener(ISpellCheckerSessionListener listener) { + if (DBG) { + Slog.d(TAG, "remove listener"); + } synchronized(mSpellCheckerMap) { final int size = mListeners.size(); final ArrayList<InternalDeathRecipient> removeList = @@ -284,6 +318,9 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } private void cleanLocked() { + if (DBG) { + Slog.d(TAG, "cleanLocked"); + } if (mListeners.isEmpty()) { mSpellCheckerBindGroups.remove(this); // Unbind service when there is no active clients. diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java index c39dc805e684..de25747d476f 100755 --- a/services/java/com/android/server/VibratorService.java +++ b/services/java/com/android/server/VibratorService.java @@ -383,6 +383,12 @@ public class VibratorService extends IVibratorService.Stub { if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { synchronized (mVibrations) { doCancelVibrateLocked(); + + int size = mVibrations.size(); + for(int i = 0; i < size; i++) { + unlinkVibration(mVibrations.get(i)); + } + mVibrations.clear(); } } diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index bb9d15b3ca05..92647e65368b 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -49,6 +49,7 @@ import android.util.SparseArray; import android.view.IWindow; import android.view.View; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; @@ -129,10 +130,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private boolean mIsAccessibilityEnabled; - private boolean mIsTouchExplorationRequested; - private AccessibilityInputFilter mInputFilter; + private boolean mHasInputFilter; + private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = new ArrayList<AccessibilityServiceInfo>(); private boolean mIsTouchExplorationEnabled; @@ -189,7 +190,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub manageServicesLocked(); } } - + @Override public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { @@ -236,17 +237,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mIsAccessibilityEnabled = Settings.Secure.getInt( mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; - // if accessibility is enabled inform our clients we are on - if (mIsAccessibilityEnabled) { - sendAccessibilityEnabledToClientsLocked(); - } + manageServicesLocked(); // get touch exploration enabled setting on boot - mIsTouchExplorationRequested = Settings.Secure.getInt( + mIsTouchExplorationEnabled = Settings.Secure.getInt( mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_REQUESTED, 0) == 1; - updateTouchExplorationEnabledLocked(); + Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1; + updateInputFilterLocked(); + + sendStateToClientsLocked(); } return; @@ -288,13 +288,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } else { unbindAllServicesLocked(); } - sendAccessibilityEnabledToClientsLocked(); + sendStateToClientsLocked(); } } }); Uri touchExplorationRequestedUri = Settings.Secure.getUriFor( - Settings.Secure.TOUCH_EXPLORATION_REQUESTED); + Settings.Secure.TOUCH_EXPLORATION_ENABLED); contentResolver.registerContentObserver(touchExplorationRequestedUri, false, new ContentObserver(new Handler()) { @Override @@ -302,10 +302,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub super.onChange(selfChange); synchronized (mLock) { - mIsTouchExplorationRequested = Settings.Secure.getInt( + mIsTouchExplorationEnabled = Settings.Secure.getInt( mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_REQUESTED, 0) == 1; - updateTouchExplorationEnabledLocked(); + Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1; + updateInputFilterLocked(); + sendStateToClientsLocked(); } } }); @@ -325,7 +326,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }); } - public boolean addClient(IAccessibilityManagerClient client) throws RemoteException { + public int addClient(IAccessibilityManagerClient client) throws RemoteException { synchronized (mLock) { final IAccessibilityManagerClient addedClient = client; mClients.add(addedClient); @@ -338,7 +339,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } }, 0); - return mIsAccessibilityEnabled; + return getState(); } } @@ -628,7 +629,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub service.linkToOwnDeath(); mServices.add(service); mComponentNameToServiceMap.put(service.mComponentName, service); - updateTouchExplorationEnabledLocked(); + updateInputFilterLocked(); } catch (RemoteException e) { /* do nothing */ } @@ -648,7 +649,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mComponentNameToServiceMap.remove(service.mComponentName); mHandler.removeMessages(service.mId); service.unlinkToOwnDeath(); - updateTouchExplorationEnabledLocked(); + updateInputFilterLocked(); return removed; } @@ -781,12 +782,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** - * Updates the state of {@link android.view.accessibility.AccessibilityManager} clients. + * Sends the state to the clients. */ - private void sendAccessibilityEnabledToClientsLocked() { + private void sendStateToClientsLocked() { + final int state = getState(); for (int i = 0, count = mClients.size(); i < count; i++) { try { - mClients.get(i).setEnabled(mIsAccessibilityEnabled); + mClients.get(i).setState(state); } catch (RemoteException re) { mClients.remove(i); count--; @@ -796,48 +798,39 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** - * Sends the touch exploration state to clients. + * Gets the current state as a set of flags. + * + * @return The state. */ - private void sendTouchExplorationEnabledToClientsLocked() { - for (int i = 0, count = mClients.size(); i < count; i++) { - try { - mClients.get(i).setTouchExplorationEnabled(mIsTouchExplorationEnabled); - } catch (RemoteException re) { - mClients.remove(i); - count--; - i--; - } + private int getState() { + int state = 0; + if (mIsAccessibilityEnabled) { + state |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED; } + // Touch exploration relies on enabled accessibility. + if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) { + state |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED; + } + return state; } /** - * Updates the touch exploration state. Touch exploration is enabled if it - * is requested, accessibility is on and there is at least one enabled - * accessibility service providing spoken feedback. + * Updates the touch exploration state. */ - private void updateTouchExplorationEnabledLocked() { - if (mIsAccessibilityEnabled && mIsTouchExplorationRequested) { - final boolean hasSpeakingServicesEnabled = !getEnabledAccessibilityServiceList( - AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty(); - if (!mIsTouchExplorationEnabled) { - if (!hasSpeakingServicesEnabled) { - return; - } + private void updateInputFilterLocked() { + if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) { + if (!mHasInputFilter) { + mHasInputFilter = true; if (mInputFilter == null) { mInputFilter = new AccessibilityInputFilter(mContext); } mWindowManagerService.setInputFilter(mInputFilter); - mIsTouchExplorationEnabled = true; - sendTouchExplorationEnabledToClientsLocked(); - return; - } else if (hasSpeakingServicesEnabled) { - return; } + return; } - if (mIsTouchExplorationEnabled) { + if (mHasInputFilter) { + mHasInputFilter = false; mWindowManagerService.setInputFilter(null); - mIsTouchExplorationEnabled = false; - sendTouchExplorationEnabledToClientsLocked(); } } diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index 5a3a55d24730..0ad58d095660 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -19,21 +19,21 @@ package com.android.server.accessibility; import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END; import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START; -import com.android.server.accessibility.AccessibilityInputFilter.Explorer; -import com.android.server.wm.InputFilter; - import android.content.Context; import android.os.Handler; import android.os.SystemClock; import android.util.Slog; import android.view.MotionEvent; -import android.view.ViewConfiguration; -import android.view.WindowManagerPolicy; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; +import android.view.ViewConfiguration; +import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import com.android.server.accessibility.AccessibilityInputFilter.Explorer; +import com.android.server.wm.InputFilter; + import java.util.Arrays; /** @@ -146,6 +146,9 @@ public class TouchExplorer implements Explorer { // Command for delayed sending of a hover event. private final SendHoverDelayed mSendHoverDelayed; + // Command for delayed sending of a long press. + private final PerformLongPressDelayed mPerformLongPressDelayed; + /** * Creates a new instance. * @@ -160,6 +163,7 @@ public class TouchExplorer implements Explorer { mPointerTracker = new PointerTracker(context); mHandler = new Handler(context.getMainLooper()); mSendHoverDelayed = new SendHoverDelayed(); + mPerformLongPressDelayed = new PerformLongPressDelayed(); mAccessibilityManager = AccessibilityManager.getInstance(context); } @@ -208,15 +212,7 @@ public class TouchExplorer implements Explorer { final int activePointerCount = pointerTracker.getActivePointerCount(); switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - // Send a hover for every finger down so the user gets feedback - // where she is currently touching. - mSendHoverDelayed.forceSendAndRemove(); - final int pointerIndex = event.getActionIndex(); - final int pointerIdBits = (1 << event.getPointerId(pointerIndex)); - mSendHoverDelayed.post(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, - policyFlags, DELAY_SEND_HOVER_MOVE); - } break; + case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: { switch (activePointerCount) { case 0: { @@ -224,13 +220,13 @@ public class TouchExplorer implements Explorer { + "touch exploring state!"); } case 1: { - // Schedule a hover event which will lead to firing an - // accessibility event from the hovered view. - mSendHoverDelayed.remove(); + // Send hover if pending. + mSendHoverDelayed.forceSendAndRemove(); + // Send a hover for every finger down so the user gets feedback. final int pointerId = pointerTracker.getPrimaryActivePointerId(); final int pointerIdBits = (1 << pointerId); final int lastAction = pointerTracker.getLastInjectedHoverAction(); - // If a schedules hover enter for another pointer is delivered we send move. + // If a hover enter for another pointer is delivered we send move. final int action = (lastAction == MotionEvent.ACTION_HOVER_ENTER) ? MotionEvent.ACTION_HOVER_MOVE : MotionEvent.ACTION_HOVER_ENTER; @@ -244,7 +240,19 @@ public class TouchExplorer implements Explorer { // If more pointers down on the screen since the last touch // exploration we discard the last cached touch explore event. if (event.getPointerCount() != mLastTouchExploreEvent.getPointerCount()) { - mLastTouchExploreEvent = null; + mLastTouchExploreEvent = null; + break; + } + + // If the down is in the time slop => schedule a long press. + final long pointerDownTime = + pointerTracker.getReceivedPointerDownTime(pointerId); + final long lastExploreTime = mLastTouchExploreEvent.getEventTime(); + final long deltaTimeExplore = pointerDownTime - lastExploreTime; + if (deltaTimeExplore <= ACTIVATION_TIME_SLOP) { + mPerformLongPressDelayed.post(event, policyFlags, + ViewConfiguration.getLongPressTimeout()); + break; } } break; default: { @@ -275,6 +283,7 @@ public class TouchExplorer implements Explorer { sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START); // Make sure the scheduled down/move event is sent. mSendHoverDelayed.forceSendAndRemove(); + mPerformLongPressDelayed.remove(); // If we have transitioned to exploring state from another one // we need to send a hover enter event here. final int lastAction = mPointerTracker.getLastInjectedHoverAction(); @@ -291,20 +300,11 @@ public class TouchExplorer implements Explorer { policyFlags); } - // Detect long press on the last touch explored position. - if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null) { + // If the exploring pointer moved enough => cancel the long press. + if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null + && mPerformLongPressDelayed.isPenidng()) { - // If the down was not in the time slop => nothing else to do. - final long pointerDownTime = - pointerTracker.getReceivedPointerDownTime(pointerId); - final long lastExploreTime = mLastTouchExploreEvent.getEventTime(); - final long deltaTimeExplore = pointerDownTime - lastExploreTime; - if (deltaTimeExplore > ACTIVATION_TIME_SLOP) { - mLastTouchExploreEvent = null; - break; - } - - // If the pointer moved more than the tap slop => nothing else to do. + // If the pointer moved more than the tap slop => cancel long press. final float deltaX = mLastTouchExploreEvent.getX(pointerIndex) - event.getX(pointerIndex); final float deltaY = mLastTouchExploreEvent.getY(pointerIndex) @@ -312,24 +312,14 @@ public class TouchExplorer implements Explorer { final float moveDelta = (float) Math.hypot(deltaX, deltaY); if (moveDelta > mTouchExplorationTapSlop) { mLastTouchExploreEvent = null; + mPerformLongPressDelayed.remove(); break; } - - // If down for long enough we get a long press. - final long deltaTimeMove = event.getEventTime() - pointerDownTime; - if (deltaTimeMove > ViewConfiguration.getLongPressTimeout()) { - mCurrentState = STATE_DELEGATING; - // Make sure the scheduled hover exit is delivered. - mSendHoverDelayed.forceSendAndRemove(); - sendDownForAllActiveNotInjectedPointers(event, policyFlags); - sendMotionEvent(event, policyFlags); - mTouchExploreGestureInProgress = false; - mLastTouchExploreEvent = null; - } } } break; case 2: { mSendHoverDelayed.forceSendAndRemove(); + mPerformLongPressDelayed.remove(); // We want to no longer hover over the location so subsequent // touch at the same spot will generate a hover enter. sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, @@ -360,6 +350,7 @@ public class TouchExplorer implements Explorer { } break; default: { mSendHoverDelayed.forceSendAndRemove(); + mPerformLongPressDelayed.remove(); // We want to no longer hover over the location so subsequent // touch at the same spot will generate a hover enter. sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, @@ -388,22 +379,26 @@ public class TouchExplorer implements Explorer { break; } + mSendHoverDelayed.forceSendAndRemove(); + mPerformLongPressDelayed.remove(); + // If touch exploring announce the end of the gesture. + // Also do not click on the last explored location. if (mTouchExploreGestureInProgress) { - sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); mTouchExploreGestureInProgress = false; + mLastTouchExploreEvent = MotionEvent.obtain(event); + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); + break; } // Detect whether to activate i.e. click on the last explored location. if (mLastTouchExploreEvent != null) { - // If the down was not in the time slop => nothing else to do. final long eventTime = pointerTracker.getLastReceivedUpPointerDownTime(); final long exploreTime = mLastTouchExploreEvent.getEventTime(); final long deltaTime = eventTime - exploreTime; if (deltaTime > ACTIVATION_TIME_SLOP) { - mSendHoverDelayed.forceSendAndRemove(); final int lastAction = mPointerTracker.getLastInjectedHoverAction(); if (lastAction != MotionEvent.ACTION_HOVER_EXIT) { sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, @@ -413,15 +408,14 @@ public class TouchExplorer implements Explorer { break; } - // If the pointer moved more than the tap slop => nothing else to do. + // If a tap is farther than the tap slop => nothing to do. final int pointerIndex = event.findPointerIndex(pointerId); - final float deltaX = pointerTracker.getLastReceivedUpPointerDownX() + final float deltaX = mLastTouchExploreEvent.getX(pointerIndex) - event.getX(pointerIndex); - final float deltaY = pointerTracker.getLastReceivedUpPointerDownY() + final float deltaY = mLastTouchExploreEvent.getY(pointerIndex) - event.getY(pointerIndex); final float deltaMove = (float) Math.hypot(deltaX, deltaY); if (deltaMove > mTouchExplorationTapSlop) { - mSendHoverDelayed.forceSendAndRemove(); final int lastAction = mPointerTracker.getLastInjectedHoverAction(); if (lastAction != MotionEvent.ACTION_HOVER_EXIT) { sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, @@ -432,7 +426,6 @@ public class TouchExplorer implements Explorer { } // All preconditions are met, so click the last explored location. - mSendHoverDelayed.forceSendAndRemove(); sendActionDownAndUp(mLastTouchExploreEvent, policyFlags); mLastTouchExploreEvent = null; } else { @@ -448,6 +441,8 @@ public class TouchExplorer implements Explorer { } } break; case MotionEvent.ACTION_CANCEL: { + mSendHoverDelayed.remove(); + mPerformLongPressDelayed.remove(); final int lastAction = pointerTracker.getLastInjectedHoverAction(); if (lastAction != MotionEvent.ACTION_HOVER_EXIT) { final int pointerId = pointerTracker.getPrimaryActivePointerId(); @@ -934,8 +929,6 @@ public class TouchExplorer implements Explorer { private int mInjectedPointersDown; // Keep track of the last up pointer data. - private float mLastReceivedUpPointerDownX; - private float mLastReveivedUpPointerDownY; private long mLastReceivedUpPointerDownTime; private int mLastReceivedUpPointerId; private boolean mLastReceivedUpPointerActive; @@ -968,8 +961,6 @@ public class TouchExplorer implements Explorer { mPrimaryActivePointerId = 0; mHasMovingActivePointer = false; mInjectedPointersDown = 0; - mLastReceivedUpPointerDownX = 0; - mLastReveivedUpPointerDownY = 0; mLastReceivedUpPointerDownTime = 0; mLastReceivedUpPointerId = 0; mLastReceivedUpPointerActive = false; @@ -1126,20 +1117,6 @@ public class TouchExplorer implements Explorer { } /** - * @return The X coordinate where the last up received pointer went down. - */ - public float getLastReceivedUpPointerDownX() { - return mLastReceivedUpPointerDownX; - } - - /** - * @return The Y coordinate where the last up received pointer went down. - */ - public float getLastReceivedUpPointerDownY() { - return mLastReveivedUpPointerDownY; - } - - /** * @return The time when the last up received pointer went down. */ public long getLastReceivedUpPointerDownTime() { @@ -1220,8 +1197,6 @@ public class TouchExplorer implements Explorer { final int pointerFlag = (1 << pointerId); mLastReceivedUpPointerId = 0; - mLastReceivedUpPointerDownX = 0; - mLastReveivedUpPointerDownY = 0; mLastReceivedUpPointerDownTime = 0; mLastReceivedUpPointerActive = false; @@ -1262,8 +1237,6 @@ public class TouchExplorer implements Explorer { final int pointerFlag = (1 << pointerId); mLastReceivedUpPointerId = pointerId; - mLastReceivedUpPointerDownX = getReceivedPointerDownX(pointerId); - mLastReveivedUpPointerDownY = getReceivedPointerDownY(pointerId); mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); mLastReceivedUpPointerActive = isActivePointer(pointerId); @@ -1400,6 +1373,51 @@ public class TouchExplorer implements Explorer { } /** + * Class for delayed sending of long press. + */ + private final class PerformLongPressDelayed implements Runnable { + private MotionEvent mEvent; + private int mPolicyFlags; + + public void post(MotionEvent prototype, int policyFlags, long delay) { + mEvent = MotionEvent.obtain(prototype); + mPolicyFlags = policyFlags; + mHandler.postDelayed(this, delay); + } + + public void remove() { + if (isPenidng()) { + mHandler.removeCallbacks(this); + clear(); + } + } + + private boolean isPenidng() { + return (mEvent != null); + } + + @Override + public void run() { + mCurrentState = STATE_DELEGATING; + // Make sure the scheduled hover exit is delivered. + mSendHoverDelayed.forceSendAndRemove(); + sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags); + mTouchExploreGestureInProgress = false; + mLastTouchExploreEvent = null; + clear(); + } + + private void clear() { + if (!isPenidng()) { + return; + } + mEvent.recycle(); + mEvent = null; + mPolicyFlags = 0; + } + } + + /** * Class for delayed sending of hover events. */ private final class SendHoverDelayed implements Runnable { diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 82bc505ea707..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; @@ -456,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 { @@ -507,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; } @@ -600,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 cf75d6bbaed0..9cb772eb315a 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -396,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/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index d6a15e63fa90..9eb1179b8caf 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -2860,8 +2860,17 @@ public class PackageManagerService extends IPackageManager.Stub { } private File getDataPathForPackage(String packageName, int userId) { - return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId + /* + * Until we fully support multiple users, return the directory we + * previously would have. The PackageManagerTests will need to be + * revised when this is changed back.. + */ + if (userId == 0) { + return new File(mAppDataDir, packageName); + } else { + return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId + File.separator + packageName); + } } private PackageParser.Package scanPackageLI(PackageParser.Package pkg, diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java index 1234bfd8220e..46bcc4a259db 100644 --- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java @@ -29,12 +29,13 @@ import android.provider.Settings; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; /** * This test exercises the - * {@link com.android.server.AccessibilityManagerService} by mocking the + * {@link com.android.server.accessibility.AccessibilityManagerService} by mocking the * {@link android.view.accessibility.AccessibilityManager} which talks to to the * service. The service itself is interacting with the platform. Note: Testing * the service in full isolation would require significant amount of work for @@ -97,7 +98,9 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); // invoke the method under test - boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient); + final int stateFlagsDisabled = mManagerService.addClient(mockClient); + boolean enabledAccessibilityDisabled = + (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; // check expected result assertFalse("The client must be disabled since accessibility is disabled.", @@ -107,7 +110,10 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { ensureAccessibilityEnabled(mContext, true); // invoke the method under test - boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient); + final int stateFlagsEnabled = mManagerService.addClient(mockClient); + boolean enabledAccessibilityEnabled = + (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; + // check expected result assertTrue("The client must be enabled since accessibility is enabled.", @@ -123,7 +129,9 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient(); // invoke the method under test - boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient); + final int stateFlagsEnabled = mManagerService.addClient(mockClient); + boolean enabledAccessibilityEnabled = + (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; // check expected result assertTrue("The client must be enabled since accessibility is enabled.", @@ -133,7 +141,9 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { ensureAccessibilityEnabled(mContext, false); // invoke the method under test - boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient); + final int stateFlagsDisabled = mManagerService.addClient(mockClient); + boolean enabledAccessibilityDisabled = + (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; // check expected result assertFalse("The client must be disabled since accessibility is disabled.", @@ -537,10 +547,10 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { * This class is a mock {@link IAccessibilityManagerClient}. */ public class MyMockAccessibilityManagerClient extends IAccessibilityManagerClient.Stub { - boolean mIsEnabled; + int mState; - public void setEnabled(boolean enabled) { - mIsEnabled = enabled; + public void setState(int state) { + mState = state; } public void setTouchExplorationEnabled(boolean enabled) { diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java index 1463d3055fda..e08381532427 100644 --- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java @@ -70,7 +70,8 @@ public class AccessibilityManagerTest extends AndroidTestCase { // configure the mock service behavior IAccessibilityManager mockServiceInterface = mMockServiceInterface; - expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn( + AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); expect(mockServiceInterface.getInstalledAccessibilityServiceList()).andReturn( expectedServices); replay(mockServiceInterface); @@ -91,7 +92,8 @@ public class AccessibilityManagerTest extends AndroidTestCase { public void testInterrupt() throws Exception { // configure the mock service behavior IAccessibilityManager mockServiceInterface = mMockServiceInterface; - expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn( + AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); mockServiceInterface.interrupt(); replay(mockServiceInterface); @@ -107,7 +109,8 @@ public class AccessibilityManagerTest extends AndroidTestCase { public void testIsEnabled() throws Exception { // configure the mock service behavior IAccessibilityManager mockServiceInterface = mMockServiceInterface; - expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn( + AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); replay(mockServiceInterface); // invoke the method under test @@ -118,7 +121,7 @@ public class AccessibilityManagerTest extends AndroidTestCase { assertTrue("Must be enabled since the mock service is enabled", isEnabledServiceEnabled); // disable accessibility - manager.getClient().setEnabled(false); + manager.getClient().setState(0); // wait for the asynchronous IBinder call to complete Thread.sleep(TIMEOUT_BINDER_CALL); @@ -141,7 +144,8 @@ public class AccessibilityManagerTest extends AndroidTestCase { // configure the mock service behavior IAccessibilityManager mockServiceInterface = mMockServiceInterface; - expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true); + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn( + AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent))) .andReturn(true); expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent))) @@ -176,7 +180,7 @@ public class AccessibilityManagerTest extends AndroidTestCase { // configure the mock service behavior IAccessibilityManager mockServiceInterface = mMockServiceInterface; - expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(false); + expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(0); replay(mockServiceInterface); // invoke the method under test (accessibility disabled) 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/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 5df2edd6a281..d3bb6a546d94 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java @@ -247,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) || @@ -391,7 +392,7 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { phone.notifyServiceStateChanged(ss); } - if (hasCdmaDataConnectionAttached) { + if (hasCdmaDataConnectionAttached || has4gHandoff) { mAttachedRegistrants.notifyRegistrants(); } 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/tests/GridLayoutTest/res/layout/grid3.xml b/tests/GridLayoutTest/res/layout/grid3.xml index 3c2db545a6b3..2eca384be5ff 100644 --- a/tests/GridLayoutTest/res/layout/grid3.xml +++ b/tests/GridLayoutTest/res/layout/grid3.xml @@ -27,8 +27,8 @@ > <TextView - android:text="Email account" - android:textSize="48dip" + android:text="Email setup" + android:textSize="32dip" android:layout_columnSpan="4" android:layout_gravity="center_horizontal" @@ -36,7 +36,7 @@ <TextView android:text="You can configure email in just a few steps:" - android:textSize="20dip" + android:textSize="16dip" android:layout_columnSpan="4" android:layout_gravity="left" @@ -49,7 +49,7 @@ /> <EditText - android:layout_width="100dip" + android:layout_width="64dip" /> <TextView @@ -60,14 +60,14 @@ /> <EditText - android:layout_width="50dip" + android:layout_width="32dip" /> <Space android:layout_row="4" android:layout_column="2" - android:layout_rowFlexibility="canStretch" - android:layout_columnFlexibility="canStretch" + android:layout_margin="0dip" + android:layout_gravity="fill" /> <Button @@ -75,7 +75,6 @@ android:layout_row="5" android:layout_column="3" - android:layout_gravity="fill_horizontal" /> <Button diff --git a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java index 38a85a3a2c98..e1871ac809a0 100644 --- a/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java +++ b/tests/GridLayoutTest/src/com/android/test/layout/Activity2.java @@ -38,30 +38,31 @@ public class Activity2 extends Activity { vg.setUseDefaultMargins(true); vg.setAlignmentMode(ALIGN_BOUNDS); - Spec row1 = spec(0, CENTER); - Spec row2 = spec(1, CENTER); + Spec row1 = spec(0); + Spec row2 = spec(1); Spec row3 = spec(2, BASELINE); Spec row4 = spec(3, BASELINE); - Spec row5 = spec(4, FILL, CAN_STRETCH); - Spec row6 = spec(5, CENTER); - Spec row7 = spec(6, CENTER); + Spec row5 = spec(4, FILL); + Spec row6 = spec(5); + Spec row7 = spec(6); Spec col1a = spec(0, 4, CENTER); Spec col1b = spec(0, 4, LEFT); Spec col1c = spec(0, RIGHT); Spec col2 = spec(1, LEFT); - Spec col3 = spec(2, FILL, CAN_STRETCH); - Spec col4 = spec(3, FILL); + Spec col3 = spec(2, FILL); + Spec col4a = spec(3); + Spec col4b = spec(3, FILL); { TextView v = new TextView(context); - v.setTextSize(48); + v.setTextSize(32); v.setText("Email setup"); vg.addView(v, new LayoutParams(row1, col1a)); } { TextView v = new TextView(context); - v.setTextSize(20); + v.setTextSize(16); v.setText("You can configure email in just a few steps:"); vg.addView(v, new LayoutParams(row2, col1b)); } @@ -75,7 +76,7 @@ public class Activity2 extends Activity { v.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS); { LayoutParams lp = new LayoutParams(row3, col2); - lp.width = (int) v.getPaint().measureText("Frederick.W.Flintstone@bedrock.com "); + lp.width = (int) v.getPaint().measureText("Frederick.W.Flintstone"); vg.addView(v, lp); } } @@ -95,17 +96,19 @@ public class Activity2 extends Activity { } { Space v = new Space(context); - vg.addView(v, new LayoutParams(row5, col3)); + LayoutParams lp = new LayoutParams(row5, col3); + lp.setMargins(0, 0, 0, 0); + vg.addView(v, lp); } { Button v = new Button(context); v.setText("Manual setup"); - vg.addView(v, new LayoutParams(row6, col4)); + vg.addView(v, new LayoutParams(row6, col4a)); } { Button v = new Button(context); v.setText("Next"); - vg.addView(v, new LayoutParams(row7, col4)); + vg.addView(v, new LayoutParams(row7, col4b)); } return vg; diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/threshold.rs b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/threshold.rs index 16ebe08a11c7..d93238c2580c 100644 --- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/threshold.rs +++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/threshold.rs @@ -84,10 +84,10 @@ void filter() { fs.radius = radius; fs.ain = rsGetAllocation(ScratchPixel1); - rsForEach(hBlurScript, fs.ain, rsGetAllocation(ScratchPixel2), &fs); + rsForEach(hBlurScript, fs.ain, rsGetAllocation(ScratchPixel2), &fs, sizeof(fs)); fs.ain = rsGetAllocation(ScratchPixel2); - rsForEach(vBlurScript, fs.ain, rsGetAllocation(OutPixel), &fs); + rsForEach(vBlurScript, fs.ain, rsGetAllocation(OutPixel), &fs, sizeof(fs)); rsSendToClientBlocking(CMD_FINISHED); } diff --git a/tests/RenderScriptTests/ModelViewer/src/com/android/modelviewer/scenegraph.rs b/tests/RenderScriptTests/ModelViewer/src/com/android/modelviewer/scenegraph.rs index f04695260f41..0e619ea1aff8 100644 --- a/tests/RenderScriptTests/ModelViewer/src/com/android/modelviewer/scenegraph.rs +++ b/tests/RenderScriptTests/ModelViewer/src/com/android/modelviewer/scenegraph.rs @@ -60,7 +60,7 @@ int root(void) { robot2Ptr->transforms[1].w += 2.5f; robot2Ptr->isDirty = 1; - rsForEach(gTransformRS, gRootNode->children, gRootNode->children, 0); + rsForEach(gTransformRS, gRootNode->children, gRootNode->children, NULL, 0); rsgClearColor(1.0f, 1.0f, 1.0f, 1.0f); rsgClearDepth(1.0f); diff --git a/tests/RenderScriptTests/ModelViewer/src/com/android/modelviewer/transform.rs b/tests/RenderScriptTests/ModelViewer/src/com/android/modelviewer/transform.rs index f328025ecf96..85c06306071d 100644 --- a/tests/RenderScriptTests/ModelViewer/src/com/android/modelviewer/transform.rs +++ b/tests/RenderScriptTests/ModelViewer/src/com/android/modelviewer/transform.rs @@ -91,6 +91,6 @@ void root(const void *v_in, void *v_out, const void *usrData, uint32_t x, uint32 //rsDebug("Transform calling self with child ", (int)data->children.p); if (data->children.p) { - rsForEach(transformScript, data->children, data->children, (void*)&toChild); + rsForEach(transformScript, data->children, data->children, (void*)&toChild, sizeof(toChild)); } } diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs index eaafe1db3d40..db9783530971 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs @@ -280,7 +280,7 @@ static void displayFontSamples(int fillNum) { testData.renderSurfaceW = gRenderSurfaceW; testData.renderSurfaceH = gRenderSurfaceH; testData.user = fillNum; - rsForEach(gFontScript, gDummyAlloc, gDummyAlloc, &testData); + rsForEach(gFontScript, gDummyAlloc, gDummyAlloc, &testData, sizeof(testData)); } static void bindProgramVertexOrtho() { @@ -520,7 +520,7 @@ static void displaySimpleGeoSamples(bool useTexture, int numMeshes) { testData.user = 0; testData.user1 = useTexture ? 1 : 0; testData.user2 = numMeshes; - rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData); + rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData, sizeof(testData)); } static void displayCustomShaderSamples(int numMeshes) { @@ -530,7 +530,7 @@ static void displayCustomShaderSamples(int numMeshes) { testData.dt = gDt; testData.user = 1; testData.user1 = numMeshes; - rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData); + rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData, sizeof(testData)); } static void displayPixelLightSamples(int numMeshes, bool heavyVertex) { @@ -541,7 +541,7 @@ static void displayPixelLightSamples(int numMeshes, bool heavyVertex) { testData.user = 2; testData.user1 = numMeshes; testData.user2 = heavyVertex ? 1 : 0; - rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData); + rsForEach(gTorusScript, gDummyAlloc, gDummyAlloc, &testData, sizeof(testData)); } static void displayMultitextureSample(bool blend, int quadCount) { diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java index 49ddf1d2fcb2..0e24cc011203 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java @@ -117,8 +117,10 @@ public class DelegateClassAdapter extends ClassAdapter { int accessDelegate = access; // change access to public for the original one - access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); - access |= Opcodes.ACC_PUBLIC; + if (Main.sOptions.generatePublicAccess) { + access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); + access |= Opcodes.ACC_PUBLIC; + } MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX, desc, signature, exceptions); diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index ce480697c094..9bf52c7826bb 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -37,7 +37,7 @@ import java.util.Set; * which does: * <pre> * $ make layoutlib_create <bunch of framework jars> - * $ out/host/linux-x86/framework/bin/layoutlib_create \ + * $ java -jar out/host/linux-x86/framework/layoutlib_create.jar \ * out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \ * out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \ * out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar @@ -45,6 +45,12 @@ import java.util.Set; */ public class Main { + public static class Options { + public boolean generatePublicAccess = true; + } + + public static final Options sOptions = new Options(); + public static void main(String[] args) { Log log = new Log(); @@ -53,7 +59,7 @@ public class Main { String[] osDestJar = { null }; if (!processArgs(log, args, osJarPath, osDestJar)) { - log.error("Usage: layoutlib_create [-v] output.jar input.jar ..."); + log.error("Usage: layoutlib_create [-v] [-p] output.jar input.jar ..."); System.exit(1); } @@ -136,6 +142,8 @@ public class Main { String s = args[i]; if (s.equals("-v")) { log.setVerbose(true); + } else if (s.equals("-p")) { + sOptions.generatePublicAccess = false; } else if (!s.startsWith("-")) { if (osDestJar[0] == null) { osDestJar[0] = s; diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java index f2d97557f264..5a0a44a40e34 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java @@ -72,8 +72,10 @@ class TransformClassAdapter extends ClassAdapter { name = mClassName; // remove protected or private and set as public - access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); - access |= Opcodes.ACC_PUBLIC; + if (Main.sOptions.generatePublicAccess) { + access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); + access |= Opcodes.ACC_PUBLIC; + } // remove final access = access & ~Opcodes.ACC_FINAL; // note: leave abstract classes as such @@ -87,8 +89,10 @@ class TransformClassAdapter extends ClassAdapter { @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { // remove protected or private and set as public - access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); - access |= Opcodes.ACC_PUBLIC; + if (Main.sOptions.generatePublicAccess) { + access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); + access |= Opcodes.ACC_PUBLIC; + } // remove final access = access & ~Opcodes.ACC_FINAL; // note: leave abstract classes as such @@ -117,8 +121,10 @@ class TransformClassAdapter extends ClassAdapter { String methodSignature = mClassName.replace('/', '.') + "#" + name; // change access to public - access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); - access |= Opcodes.ACC_PUBLIC; + if (Main.sOptions.generatePublicAccess) { + access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); + access |= Opcodes.ACC_PUBLIC; + } // remove final access = access & ~Opcodes.ACC_FINAL; @@ -155,9 +161,10 @@ class TransformClassAdapter extends ClassAdapter { public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { // change access to public - access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); - access |= Opcodes.ACC_PUBLIC; - + if (Main.sOptions.generatePublicAccess) { + access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); + access |= Opcodes.ACC_PUBLIC; + } return super.visitField(access, name, desc, signature, value); } 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 f1f0fcc6ac9f..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 { @@ -186,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 f08bb6a2e5ea..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; @@ -89,6 +92,14 @@ 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 { @@ -97,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; @@ -167,6 +178,10 @@ public class WifiStateMachine extends StateMachine { // 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; @@ -208,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 ? */ @@ -330,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; @@ -421,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(); /** @@ -513,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)); @@ -559,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); @@ -1048,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(); @@ -1077,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!"); + } } /** @@ -1616,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); @@ -1631,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 *******************************************************/ @@ -1739,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); @@ -1774,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: @@ -1802,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; @@ -1833,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()); + } } @@ -2048,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; @@ -2082,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; @@ -2102,7 +2070,7 @@ public class WifiStateMachine extends StateMachine { 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(); @@ -2165,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(); @@ -2177,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(); @@ -2279,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); @@ -2322,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 @@ -2335,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: @@ -2495,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); @@ -2539,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 @@ -2585,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; @@ -2608,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 */ @@ -2657,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; @@ -2676,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); @@ -2763,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); @@ -2828,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: @@ -2873,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; @@ -2934,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: @@ -3019,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 */ @@ -3033,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); @@ -3076,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; @@ -3109,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()"); } @@ -3140,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; } @@ -3147,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/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]; + } + }; +} |