Merge "Import translations."
diff --git a/api/current.txt b/api/current.txt
index 7edfa53..97be454 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -992,6 +992,7 @@
field public static final int textColorTertiary = 16843282; // 0x1010212
field public static final int textColorTertiaryInverse = 16843283; // 0x1010213
field public static final int textCursorDrawable = 16843618; // 0x1010362
+ field public static final int textDirection = 16843688; // 0x10103a8
field public static final int textEditNoPasteWindowLayout = 16843541; // 0x1010315
field public static final int textEditPasteWindowLayout = 16843540; // 0x1010314
field public static final int textEditSideNoPasteWindowLayout = 16843615; // 0x101035f
@@ -20478,6 +20479,19 @@
method public int getTopPadding();
}
+ public abstract interface TextDirectionHeuristic {
+ }
+
+ public class TextDirectionHeuristics {
+ ctor public TextDirectionHeuristics();
+ field public static final android.text.TextDirectionHeuristic ANYRTL_LTR;
+ field public static final android.text.TextDirectionHeuristic FIRSTSTRONG_LTR;
+ field public static final android.text.TextDirectionHeuristic FIRSTSTRONG_RTL;
+ field public static final android.text.TextDirectionHeuristic LOCALE;
+ field public static final android.text.TextDirectionHeuristic LTR;
+ field public static final android.text.TextDirectionHeuristic RTL;
+ }
+
public class TextPaint extends android.graphics.Paint {
ctor public TextPaint();
ctor public TextPaint(int);
@@ -23171,6 +23185,7 @@
method public final android.view.ViewParent getParent();
method public float getPivotX();
method public float getPivotY();
+ method public int getResolvedTextDirection();
method public android.content.res.Resources getResources();
method public final int getRight();
method protected float getRightFadingEdgeStrength();
@@ -23190,6 +23205,7 @@
method public int getSystemUiVisibility();
method public java.lang.Object getTag();
method public java.lang.Object getTag(int);
+ method public int getTextDirection();
method public final int getTop();
method protected float getTopFadingEdgeStrength();
method protected int getTopPaddingOffset();
@@ -23321,8 +23337,10 @@
method public void requestLayout();
method public boolean requestRectangleOnScreen(android.graphics.Rect);
method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
+ method protected void resetResolvedTextDirection();
method public static int resolveSize(int, int);
method public static int resolveSizeAndState(int, int, int);
+ method protected void resolveTextDirection();
method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>);
method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
@@ -23402,6 +23420,7 @@
method public void setSystemUiVisibility(int);
method public void setTag(java.lang.Object);
method public void setTag(int, java.lang.Object);
+ method public void setTextDirection(int);
method public final void setTop(int);
method public void setTouchDelegate(android.view.TouchDelegate);
method public void setTranslationX(float);
@@ -23424,6 +23443,7 @@
method public boolean willNotCacheDrawing();
method public boolean willNotDraw();
field public static final android.util.Property ALPHA;
+ field protected static int DEFAULT_TEXT_DIRECTION;
field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
@@ -23500,6 +23520,12 @@
field public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 2; // 0x2
field public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 1; // 0x1
field public static final int SYSTEM_UI_FLAG_VISIBLE = 0; // 0x0
+ field public static final int TEXT_DIRECTION_ANY_RTL = 2; // 0x2
+ field public static final int TEXT_DIRECTION_FIRST_STRONG = 1; // 0x1
+ field public static final int TEXT_DIRECTION_INHERIT = 0; // 0x0
+ field public static final int TEXT_DIRECTION_LOCALE = 5; // 0x5
+ field public static final int TEXT_DIRECTION_LTR = 3; // 0x3
+ field public static final int TEXT_DIRECTION_RTL = 4; // 0x4
field public static final android.util.Property TRANSLATION_X;
field public static final android.util.Property TRANSLATION_Y;
field protected static final java.lang.String VIEW_LOG_TAG = "View";
@@ -23766,7 +23792,6 @@
method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public void requestTransparentRegion(android.view.View);
method protected void resetResolvedLayoutDirection();
- method protected void resetResolvedTextDirection();
method public void scheduleLayoutAnimation();
method public void setAddStatesFromChildren(boolean);
method public void setAlwaysDrawnWithCacheEnabled(boolean);
@@ -27595,7 +27620,6 @@
method protected void resetResolvedDrawables();
method protected void resetResolvedLayoutDirection();
method protected void resolveDrawables();
- method protected void resolveTextDirection();
method public void setAllCaps(boolean);
method public final void setAutoLinkMask(int);
method public void setCompoundDrawablePadding(int);
diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java
index e5c1e5b..ae41eab 100644
--- a/core/java/android/text/TextDirectionHeuristics.java
+++ b/core/java/android/text/TextDirectionHeuristics.java
@@ -17,13 +17,10 @@
package android.text;
-import java.util.Locale;
-
import android.util.LocaleUtil;
/**
* Some objects that implement TextDirectionHeuristic.
- * @hide
*/
public class TextDirectionHeuristics {
@@ -74,9 +71,8 @@
* Computes the text direction based on an algorithm. Subclasses implement
* {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the
* direction from the text alone.
- * @hide
*/
- public static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic {
+ private static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic {
private final TextDirectionAlgorithm mAlgorithm;
public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) {
@@ -157,13 +153,11 @@
/**
* Interface for an algorithm to guess the direction of a paragraph of text.
*
- * @hide
*/
- public static interface TextDirectionAlgorithm {
+ private static interface TextDirectionAlgorithm {
/**
* Returns whether the range of text is RTL according to the algorithm.
*
- * @hide
*/
TriState checkRtl(char[] text, int start, int count);
}
@@ -173,9 +167,8 @@
* the paragraph direction. This is the standard Unicode Bidirectional
* algorithm.
*
- * @hide
*/
- public static class FirstStrong implements TextDirectionAlgorithm {
+ private static class FirstStrong implements TextDirectionAlgorithm {
@Override
public TriState checkRtl(char[] text, int start, int count) {
TriState result = TriState.UNKNOWN;
@@ -196,9 +189,8 @@
* character (e.g. excludes LRE, LRO, RLE, RLO) to determine the
* direction of text.
*
- * @hide
*/
- public static class AnyStrong implements TextDirectionAlgorithm {
+ private static class AnyStrong implements TextDirectionAlgorithm {
private final boolean mLookForRtl;
@Override
@@ -239,7 +231,7 @@
/**
* Algorithm that uses the Locale direction to force the direction of a paragraph.
*/
- public static class TextDirectionHeuristicLocale extends TextDirectionHeuristicImpl {
+ private static class TextDirectionHeuristicLocale extends TextDirectionHeuristicImpl {
public TextDirectionHeuristicLocale() {
super(null);
diff --git a/core/java/android/text/method/KeyListener.java b/core/java/android/text/method/KeyListener.java
index 8594852..318149a 100644
--- a/core/java/android/text/method/KeyListener.java
+++ b/core/java/android/text/method/KeyListener.java
@@ -22,7 +22,7 @@
/**
* Interface for converting text key events into edit operations on an
- * Editable class. Note that for must cases this interface has been
+ * Editable class. Note that for most cases this interface has been
* superceded by general soft input methods as defined by
* {@link android.view.inputmethod.InputMethod}; it should only be used
* for cases where an application has its own on-screen keypad and also wants
diff --git a/core/java/android/text/method/TransformationMethod.java b/core/java/android/text/method/TransformationMethod.java
index 9f51c2a..b542109 100644
--- a/core/java/android/text/method/TransformationMethod.java
+++ b/core/java/android/text/method/TransformationMethod.java
@@ -18,7 +18,6 @@
import android.graphics.Rect;
import android.view.View;
-import android.widget.TextView;
/**
* TextView uses TransformationMethods to do things like replacing the
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 056be7f..87104f4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2618,7 +2618,6 @@
/**
* Text direction is inherited thru {@link ViewGroup}
- * @hide
*/
public static final int TEXT_DIRECTION_INHERIT = 0;
@@ -2627,7 +2626,6 @@
* determines the paragraph direction. If there is no strong directional character, the
* paragraph direction is the view's resolved layout direction.
*
- * @hide
*/
public static final int TEXT_DIRECTION_FIRST_STRONG = 1;
@@ -2636,42 +2634,36 @@
* any strong RTL character, otherwise it is LTR if it contains any strong LTR characters.
* If there are neither, the paragraph direction is the view's resolved layout direction.
*
- * @hide
*/
public static final int TEXT_DIRECTION_ANY_RTL = 2;
/**
* Text direction is forced to LTR.
*
- * @hide
*/
public static final int TEXT_DIRECTION_LTR = 3;
/**
* Text direction is forced to RTL.
*
- * @hide
*/
public static final int TEXT_DIRECTION_RTL = 4;
/**
* Text direction is coming from the system Locale.
*
- * @hide
*/
public static final int TEXT_DIRECTION_LOCALE = 5;
/**
* Default text direction is inherited
*
- * @hide
*/
protected static int DEFAULT_TEXT_DIRECTION = TEXT_DIRECTION_INHERIT;
/**
* The text direction that has been defined by {@link #setTextDirection(int)}.
*
- * {@hide}
*/
@ViewDebug.ExportedProperty(category = "text", mapping = {
@ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
@@ -2689,7 +2681,6 @@
* not TEXT_DIRECTION_INHERIT, otherwise resolution proceeds up the parent
* chain of the view.
*
- * {@hide}
*/
@ViewDebug.ExportedProperty(category = "text", mapping = {
@ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
@@ -3775,14 +3766,6 @@
}
if ((mPrivateFlags & FOCUSED) != 0) {
- // If this is the first focusable do not clear focus since the we
- // try to give it focus every time a view clears its focus. Hence,
- // the view that would gain focus already has it.
- View firstFocusable = getFirstFocusable();
- if (firstFocusable == this) {
- return;
- }
-
mPrivateFlags &= ~FOCUSED;
if (mParent != null) {
@@ -3791,24 +3774,9 @@
onFocusChanged(false, 0, null);
refreshDrawableState();
-
- // The view cleared focus and invoked the callbacks, so now is the
- // time to give focus to the the first focusable to ensure that the
- // gain focus is announced after clear focus.
- if (firstFocusable != null) {
- firstFocusable.requestFocus(FOCUS_FORWARD);
- }
}
}
- private View getFirstFocusable() {
- ViewRootImpl viewRoot = getViewRootImpl();
- if (viewRoot != null) {
- return viewRoot.focusSearch(null, FOCUS_FORWARD);
- }
- return null;
- }
-
/**
* Called to clear the focus of a view that is about to be removed.
* Doesn't call clearChildFocus, which prevents this view from taking
@@ -14106,7 +14074,6 @@
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE},
*
- * @hide
*/
public int getTextDirection() {
return mTextDirection;
@@ -14124,7 +14091,6 @@
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE},
*
- * @hide
*/
public void setTextDirection(int textDirection) {
if (textDirection != mTextDirection) {
@@ -14145,7 +14111,6 @@
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE},
*
- * @hide
*/
public int getResolvedTextDirection() {
if (mResolvedTextDirection == TEXT_DIRECTION_INHERIT) {
@@ -14157,7 +14122,6 @@
/**
* Resolve the text direction.
*
- * @hide
*/
protected void resolveTextDirection() {
if (mTextDirection != TEXT_DIRECTION_INHERIT) {
@@ -14174,7 +14138,6 @@
/**
* Reset resolved text direction. Will be resolved during a call to getResolvedTextDirection().
*
- * @hide
*/
protected void resetResolvedTextDirection() {
mResolvedTextDirection = TEXT_DIRECTION_INHERIT;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index a2e85a3..bc147ac 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -675,14 +675,11 @@
*/
@Override
public void clearFocus() {
- if (DBG) {
- System.out.println(this + " clearFocus()");
- }
- if (mFocused == null) {
- super.clearFocus();
- } else {
+ super.clearFocus();
+
+ // clear any child focus if it exists
+ if (mFocused != null) {
mFocused.clearFocus();
- mFocused = null;
}
}
@@ -694,12 +691,12 @@
if (DBG) {
System.out.println(this + " unFocus()");
}
- if (mFocused == null) {
- super.unFocus();
- } else {
+
+ super.unFocus();
+ if (mFocused != null) {
mFocused.unFocus();
- mFocused = null;
}
+ mFocused = null;
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1930a5e..1c3bbfa 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -174,7 +174,6 @@
View mView;
View mFocusedView;
View mRealFocusedView; // this is not set to null in touch mode
- View mOldFocusedView;
int mViewVisibility;
boolean mAppVisible = true;
int mOrigWindowType = -1;
@@ -2273,34 +2272,33 @@
public void requestChildFocus(View child, View focused) {
checkThread();
-
- if (DEBUG_INPUT_RESIZE) {
- Log.v(TAG, "Request child focus: focus now " + focused);
+ if (mFocusedView != focused) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused);
+ scheduleTraversals();
}
-
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, focused);
- scheduleTraversals();
-
mFocusedView = mRealFocusedView = focused;
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Request child focus: focus now "
+ + mFocusedView);
}
public void clearChildFocus(View child) {
checkThread();
- if (DEBUG_INPUT_RESIZE) {
- Log.v(TAG, "Clearing child focus");
- }
+ View oldFocus = mFocusedView;
- mOldFocusedView = mFocusedView;
-
- // Invoke the listener only if there is no view to take focus
- if (focusSearch(null, View.FOCUS_FORWARD) == null) {
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, null);
- }
-
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Clearing child focus");
mFocusedView = mRealFocusedView = null;
+ if (mView != null && !mView.hasFocus()) {
+ // If a view gets the focus, the listener will be invoked from requestChildFocus()
+ if (!mView.requestFocus(View.FOCUS_FORWARD)) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
+ }
+ } else if (oldFocus != null) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
+ }
}
+
public void focusableViewAvailable(View v) {
checkThread();
@@ -2772,7 +2770,6 @@
mView.unFocus();
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
mFocusedView = null;
- mOldFocusedView = null;
return true;
}
}
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 570f0f9..909f383 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -426,7 +426,8 @@
}
// A new word has been created across the interval boundaries with this edit.
- // Previous spans (ended on start / started on end) removed, not valid anymore
+ // The previous spans (that ended on start / started on end) are not valid
+ // anymore and must be removed.
if (wordStart < start && wordEnd > start) {
removeSpansAt(editable, start, spellCheckSpans);
removeSpansAt(editable, start, suggestionSpans);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 99349b0..7db8a1e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3472,6 +3472,9 @@
if (mText.length() == 0) {
invalidate();
}
+
+ // Invalidate display list if hint will be used
+ if (mText.length() == 0 && mHint != null) mTextDisplayListIsValid = false;
}
/**
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index 2ac3ca8..c5ff16e 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -116,14 +116,9 @@
return (jboolean)(::wifi_unload_driver() == 0);
}
-static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jobject)
+static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jobject, jboolean p2pSupported)
{
- return (jboolean)(::wifi_start_supplicant() == 0);
-}
-
-static jboolean android_net_wifi_startP2pSupplicant(JNIEnv* env, jobject)
-{
- return (jboolean)(::wifi_start_p2p_supplicant() == 0);
+ return (jboolean)(::wifi_start_supplicant(p2pSupported) == 0);
}
static jboolean android_net_wifi_killSupplicant(JNIEnv* env, jobject)
@@ -207,8 +202,7 @@
{ "loadDriver", "()Z", (void *)android_net_wifi_loadDriver },
{ "isDriverLoaded", "()Z", (void *)android_net_wifi_isDriverLoaded },
{ "unloadDriver", "()Z", (void *)android_net_wifi_unloadDriver },
- { "startSupplicant", "()Z", (void *)android_net_wifi_startSupplicant },
- { "startP2pSupplicant", "()Z", (void *)android_net_wifi_startP2pSupplicant },
+ { "startSupplicant", "(Z)Z", (void *)android_net_wifi_startSupplicant },
{ "killSupplicant", "()Z", (void *)android_net_wifi_killSupplicant },
{ "connectToSupplicant", "(Ljava/lang/String;)Z",
(void *)android_net_wifi_connectToSupplicant },
diff --git a/core/res/res/anim/recents_fade_in.xml b/core/res/res/anim/recents_fade_in.xml
new file mode 100644
index 0000000..c516fadb
--- /dev/null
+++ b/core/res/res/anim/recents_fade_in.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/recents_fade_in.xml
+**
+** Copyright 2012, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@interpolator/decelerate_quad"
+ android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:duration="150" />
diff --git a/core/res/res/anim/recents_fade_out.xml b/core/res/res/anim/recents_fade_out.xml
new file mode 100644
index 0000000..7468cc19
--- /dev/null
+++ b/core/res/res/anim/recents_fade_out.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/recents_fade_out.xml
+**
+** Copyright 2012, 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@interpolator/accelerate_quad"
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:duration="150" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index e84cb5c..4e55b4f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -218,6 +218,7 @@
<java-symbol type="bool" name="config_allowActionMenuItemTextWithIcon" />
<java-symbol type="bool" name="config_bluetooth_adapter_quick_switch" />
<java-symbol type="bool" name="config_bluetooth_sco_off_call" />
+ <java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
<java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
<java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" />
<java-symbol type="bool" name="config_enable_puk_unlock_screen" />
@@ -1083,6 +1084,7 @@
<java-symbol type="style" name="ActiveWallpaperSettings" />
<java-symbol type="style" name="Animation.InputMethodFancy" />
<java-symbol type="style" name="Animation.Wallpaper" />
+ <java-symbol type="style" name="Animation.RecentApplications" />
<java-symbol type="style" name="Animation.ZoomButtons" />
<java-symbol type="style" name="PreviewWallpaperSettings" />
<java-symbol type="style" name="TextAppearance.SlidingTabActive" />
@@ -3494,4 +3496,6 @@
=============================================================== -->
<public type="attr" name="isolatedProcess" id="0x010103a7" />
+ <public type="attr" name="textDirection"/>
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4995d2c..c95dddd 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2708,11 +2708,6 @@
<!-- 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. This will turn off Wi-Fi client/hotspot.</string>
- <string name="wifi_p2p_failed_message">Couldn\'t start Wi-Fi Direct.</string>
-
<string name="accept">Accept</string>
<string name="decline">Decline</string>
<string name="wifi_p2p_invitation_sent_title">Invitation sent</string>
@@ -2723,9 +2718,6 @@
<string name="wifi_p2p_enter_pin_message">Type the required PIN: </string>
<string name="wifi_p2p_show_pin_message">PIN: </string>
- <string name="wifi_p2p_enabled_notification_title">Wi-Fi Direct is on</string>
- <string name="wifi_p2p_enabled_notification_message">Touch for settings</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/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 571c4ad..610bad8 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -198,8 +198,10 @@
<!-- A special animation we can use for recent applications,
for devices that can support it (do alpha transformations). -->
<style name="Animation.RecentApplications">
- <item name="windowEnterAnimation">@anim/fade_in</item>
- <item name="windowExitAnimation">@anim/fade_out</item>
+ <item name="windowEnterAnimation">@anim/recents_fade_in</item>
+ <item name="windowShowAnimation">@anim/recents_fade_in</item>
+ <item name="windowExitAnimation">@anim/recents_fade_out</item>
+ <item name="windowHideAnimation">@anim/recents_fade_out</item>
</style>
<!-- A special animation value used internally for popup windows. -->
diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocus.java b/core/tests/coretests/src/android/widget/focus/RequestFocus.java
index 21d762a..af9ee17 100644
--- a/core/tests/coretests/src/android/widget/focus/RequestFocus.java
+++ b/core/tests/coretests/src/android/widget/focus/RequestFocus.java
@@ -21,7 +21,9 @@
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
+import android.widget.LinearLayout;
import android.widget.Button;
+import android.view.View;
/**
* Exercises cases where elements of the UI are requestFocus()ed.
diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
index baf587e..a78b0c9 100644
--- a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
+++ b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
@@ -16,27 +16,21 @@
package android.widget.focus;
-import android.os.Handler;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.util.AndroidRuntimeException;
-import android.view.View;
-import android.view.View.OnFocusChangeListener;
-import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
-import android.widget.Button;
-
+import android.widget.focus.RequestFocus;
import com.android.frameworks.coretests.R;
-import java.util.ArrayList;
-import java.util.List;
+import android.os.Handler;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.Button;
+import android.util.AndroidRuntimeException;
/**
* {@link RequestFocusTest} is set up to exercise cases where the views that
* have focus become invisible or GONE.
*/
-public class RequestFocusTest extends ActivityInstrumentationTestCase2<RequestFocus> {
+public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFocus> {
private Button mTopLeftButton;
private Button mBottomLeftButton;
@@ -45,7 +39,7 @@
private Handler mHandler;
public RequestFocusTest() {
- super(RequestFocus.class);
+ super("com.android.frameworks.coretests", RequestFocus.class);
}
@Override
@@ -100,145 +94,4 @@
e.getClass().getName());
}
}
-
- /**
- * This tests checks the case in which the first focusable View clears focus.
- * In such a case the framework tries to give the focus to another View starting
- * from the top. Hence, the framework will try to give focus to the view that
- * wants to clear its focus. From a client perspective, the view does not loose
- * focus after the call, therefore no callback for focus change should be invoked.
- *
- * @throws Exception If an error occurs.
- */
- @UiThreadTest
- public void testOnFocusChangeNotCalledIfFocusDoesNotMove() throws Exception {
- // Get the first focusable.
- Button button = mTopLeftButton;
-
- // Make sure that the button is the first focusable and focus it.
- button.getRootView().requestFocus(View.FOCUS_DOWN);
- assertTrue(button.hasFocus());
-
- // Attach on focus change listener that should not be called.
- button.setOnFocusChangeListener(new OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- throw new IllegalStateException("Unexpeced call to"
- + "OnFocusChangeListener#onFocusChange");
- }
- });
-
- // Attach on global focus change listener that should not be called.
- button.getViewTreeObserver().addOnGlobalFocusChangeListener(
- new OnGlobalFocusChangeListener() {
- @Override
- public void onGlobalFocusChanged(View oldFocus, View newFocus) {
- throw new IllegalStateException("Unexpeced call to"
- + "OnFocusChangeListener#onFocusChange");
- }
- });
-
- // Try to clear focus.
- button.clearFocus();
- }
-
- /**
- * This tests check whether the on focus change callbacks are invoked in
- * the proper order when a View loses focus and the framework gives it to
- * the fist focusable one.
- *
- * @throws Exception
- */
- @UiThreadTest
- public void testOnFocusChangeCallbackOrder() throws Exception {
- // Get the first focusable.
- Button clearingFocusButton = mTopRightButton;
- Button gainingFocusButton = mTopLeftButton;
-
- // Make sure that the clearing focus is not the first focusable.
- View focusCandidate = clearingFocusButton.getRootView().getParent().focusSearch(null,
- View.FOCUS_FORWARD);
- assertNotSame("The clearing focus button is not the first focusable.",
- clearingFocusButton, focusCandidate);
- assertSame("The gaining focus button is the first focusable.",
- gainingFocusButton, focusCandidate);
-
- // Focus the clearing focus button.
- clearingFocusButton.requestFocus();
- assertTrue(clearingFocusButton.hasFocus());
-
- // Register the invocation order checker.
- CallbackOrderChecker checker = new CallbackOrderChecker(clearingFocusButton,
- gainingFocusButton);
- clearingFocusButton.setOnFocusChangeListener(checker);
- gainingFocusButton.setOnFocusChangeListener(checker);
- clearingFocusButton.getViewTreeObserver().addOnGlobalFocusChangeListener(checker);
-
- // Try to clear focus.
- clearingFocusButton.clearFocus();
-
- // Check that no callback was invoked since focus did not move.
- checker.verify();
- }
-
- /**
- * This class check whether the focus change callback are invoked in order.
- */
- private class CallbackOrderChecker implements OnFocusChangeListener,
- OnGlobalFocusChangeListener {
-
- private class CallbackInvocation {
- final String mMethodName;
- final Object[] mArguments;
-
- CallbackInvocation(String methodName, Object[] arguments) {
- mMethodName = methodName;
- mArguments = arguments;
- }
- }
-
- private final View mClearingFocusView;
- private final View mGainingFocusView;
-
- private final List<CallbackInvocation> mInvocations = new ArrayList<CallbackInvocation>();
-
- public CallbackOrderChecker(View clearingFocusView, View gainingFocusView) {
- mClearingFocusView = clearingFocusView;
- mGainingFocusView = gainingFocusView;
- }
-
- @Override
- public void onFocusChange(View view, boolean hasFocus) {
- CallbackInvocation invocation = new CallbackInvocation(
- "OnFocusChangeListener#onFocusChange", new Object[] {view, hasFocus});
- mInvocations.add(invocation);
- }
-
- @Override
- public void onGlobalFocusChanged(View oldFocus, View newFocus) {
- CallbackInvocation invocation = new CallbackInvocation(
- "OnFocusChangeListener#onFocusChange", new Object[] {oldFocus, newFocus});
- mInvocations.add(invocation);
- }
-
- public void verify() {
- assertSame("All focus change callback should be invoked.", 3, mInvocations.size());
- assertInvioked("Callback for View clearing focus explected.", 0,
- "OnFocusChangeListener#onFocusChange",
- new Object[] {mClearingFocusView, false});
- assertInvioked("Callback for View global focus change explected.", 1,
- "OnFocusChangeListener#onFocusChange", new Object[] {mClearingFocusView,
- mGainingFocusView});
- assertInvioked("Callback for View gaining focus explected.", 2,
- "OnFocusChangeListener#onFocusChange", new Object[] {mGainingFocusView, true});
- }
-
- private void assertInvioked(String message, int order, String methodName,
- Object[] arguments) {
- CallbackInvocation invocation = mInvocations.get(order);
- assertEquals(message, methodName, invocation.mMethodName);
- assertEquals(message, arguments[0], invocation.mArguments[0]);
- assertEquals(message, arguments[1], invocation.mArguments[1]);
- }
- }
}
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 88ee362..44401c8 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2008-2012 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.
@@ -131,7 +131,7 @@
public static final int USAGE_GRAPHICS_RENDER_TARGET = 0x0010;
/**
- * USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT The allcation will be
+ * USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT The allocation will be
* used with a SurfaceTexture object. This usage will cause the
* allocation to be created read only.
*
@@ -140,18 +140,18 @@
public static final int USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE = 0x0020;
/**
- * USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT The allcation will be
+ * USAGE_IO_INPUT The allocation will be
* used with a SurfaceTexture object. This usage will cause the
* allocation to be created read only.
*
* @hide
*/
-
public static final int USAGE_IO_INPUT = 0x0040;
+
/**
- * USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT The allcation will be
+ * USAGE_IO_OUTPUT The allocation will be
* used with a SurfaceTexture object. This usage will cause the
- * allocation to be created read only.
+ * allocation to be created write only.
*
* @hide
*/
diff --git a/libs/rs/driver/rsdShaderCache.h b/libs/rs/driver/rsdShaderCache.h
index d64780b..0beecae 100644
--- a/libs/rs/driver/rsdShaderCache.h
+++ b/libs/rs/driver/rsdShaderCache.h
@@ -98,7 +98,8 @@
struct ProgramEntry {
ProgramEntry(uint32_t numVtxAttr, uint32_t numVtxUnis,
uint32_t numFragUnis) : vtx(0), frag(0), program(0), vtxAttrCount(0),
- vtxAttrs(0), vtxUniforms(0), fragUniforms(0) {
+ vtxAttrs(0), vtxUniforms(0), fragUniforms(0),
+ fragUniformIsSTO(0) {
vtxAttrCount = numVtxAttr;
if (numVtxAttr) {
vtxAttrs = new AttrData[numVtxAttr];
diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_item.xml b/packages/SystemUI/res/layout-land/status_bar_recent_item.xml
index 2d76455..aa27861 100644
--- a/packages/SystemUI/res/layout-land/status_bar_recent_item.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_recent_item.xml
@@ -58,6 +58,7 @@
android:maxHeight="@dimen/status_bar_recents_app_icon_max_height"
android:scaleType="centerInside"
android:adjustViewBounds="true"
+ android:visibility="invisible"
/>
<TextView android:id="@+id/app_label"
diff --git a/packages/SystemUI/res/layout-port/status_bar_recent_item.xml b/packages/SystemUI/res/layout-port/status_bar_recent_item.xml
index b653fcd..bc389f9 100644
--- a/packages/SystemUI/res/layout-port/status_bar_recent_item.xml
+++ b/packages/SystemUI/res/layout-port/status_bar_recent_item.xml
@@ -81,6 +81,7 @@
android:maxHeight="@dimen/status_bar_recents_app_icon_max_height"
android:scaleType="centerInside"
android:adjustViewBounds="true"
+ android:visibility="invisible"
/>
<TextView android:id="@+id/app_description"
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml
index cb26db00..333fcda 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml
@@ -65,6 +65,7 @@
android:maxHeight="@dimen/status_bar_recents_app_icon_max_height"
android:scaleType="centerInside"
android:adjustViewBounds="true"
+ android:visibility="invisible"
/>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 14ce266..cd8bd4e 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -236,6 +236,10 @@
public void onAnimationEnd(Animator animation) {
mCallback.onChildDismissed(view);
animView.setLayerType(View.LAYER_TYPE_NONE, null);
+ // Restore the alpha/translation parameters to what they were before swiping
+ // (for when these items are recycled)
+ animView.setAlpha(1f);
+ setTranslation(animView, 0f);
}
});
anim.addUpdateListener(new AnimatorUpdateListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
index ad38a11..f8a4592 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
@@ -134,6 +134,7 @@
void jumpTo(boolean appearing) {
mContentView.setTranslationY(appearing ? 0 : mPanelHeight);
+ mScrimView.getBackground().setAlpha(appearing ? 255 : 0);
}
public void setPanelHeight(int h) {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
index 4145fc4..92f4ca9 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
@@ -16,12 +16,6 @@
package com.android.systemui.recent;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -36,15 +30,17 @@
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Process;
-import android.os.SystemClock;
-import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.LruCache;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.tablet.TabletStatusBar;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
public class RecentTasksLoader {
static final String TAG = "RecentTasksLoader";
static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
@@ -55,11 +51,14 @@
private Context mContext;
private RecentsPanelView mRecentsPanel;
- private AsyncTask<Void, Integer, Void> mThumbnailLoader;
+ private AsyncTask<Void, ArrayList<TaskDescription>, Void> mTaskLoader;
+ private AsyncTask<Void, TaskDescription, Void> mThumbnailLoader;
private final Handler mHandler;
private int mIconDpi;
private Bitmap mDefaultThumbnailBackground;
+ private Bitmap mDefaultIconBackground;
+ private int mNumTasksInFirstScreenful;
public RecentTasksLoader(Context context) {
mContext = context;
@@ -76,12 +75,20 @@
mIconDpi = res.getDisplayMetrics().densityDpi;
}
+ // Render default icon (just a blank image)
+ int defaultIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.app_icon_size);
+ int iconSize = (int) (defaultIconSize * mIconDpi / res.getDisplayMetrics().densityDpi);
+ mDefaultIconBackground = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+
// Render the default thumbnail background
- int width = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
- int height = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+ int thumbnailWidth =
+ (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+ int thumbnailHeight =
+ (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
- mDefaultThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mDefaultThumbnailBackground =
+ Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(mDefaultThumbnailBackground);
c.drawColor(color);
@@ -95,12 +102,17 @@
public void setRecentsPanel(RecentsPanelView recentsPanel) {
mRecentsPanel = recentsPanel;
+ mNumTasksInFirstScreenful = mRecentsPanel.numItemsInOneScreenful();
}
public Bitmap getDefaultThumbnail() {
return mDefaultThumbnailBackground;
}
+ public Bitmap getDefaultIcon() {
+ return mDefaultIconBackground;
+ }
+
// Create an TaskDescription, returning null if the title or icon is null, or if it's the
// home activity
TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
@@ -114,6 +126,12 @@
homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
.resolveActivityInfo(pm, 0);
}
+ // Don't load the current home activity.
+ if (homeInfo != null
+ && homeInfo.packageName.equals(intent.getComponent().getPackageName())
+ && homeInfo.name.equals(intent.getComponent().getClassName())) {
+ return null;
+ }
intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
| Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -121,9 +139,8 @@
if (resolveInfo != null) {
final ActivityInfo info = resolveInfo.activityInfo;
final String title = info.loadLabel(pm).toString();
- Drawable icon = getFullResIcon(resolveInfo, pm);
- if (title != null && title.length() > 0 && icon != null) {
+ if (title != null && title.length() > 0) {
if (DEBUG) Log.v(TAG, "creating activity desc for id="
+ persistentTaskId + ", label=" + title);
@@ -131,14 +148,6 @@
persistentTaskId, resolveInfo, baseIntent, info.packageName,
description);
item.setLabel(title);
- item.setIcon(icon);
-
- // Don't load the current home activity.
- if (homeInfo != null
- && homeInfo.packageName.equals(intent.getComponent().getPackageName())
- && homeInfo.name.equals(intent.getComponent().getClassName())) {
- return null;
- }
return item;
} else {
@@ -148,10 +157,12 @@
return null;
}
- void loadThumbnail(TaskDescription td) {
+ void loadThumbnailAndIcon(TaskDescription td) {
final ActivityManager am = (ActivityManager)
mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ final PackageManager pm = mContext.getPackageManager();
ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(td.persistentTaskId);
+ Drawable icon = getFullResIcon(td.resolveInfo, pm);
if (DEBUG) Log.v(TAG, "Loaded bitmap for task "
+ td + ": " + thumbs.mainThumbnail);
@@ -161,6 +172,10 @@
} else {
td.setThumbnail(mDefaultThumbnailBackground);
}
+ if (icon != null) {
+ td.setIcon(icon);
+ }
+ td.setLoaded(true);
}
}
@@ -194,111 +209,149 @@
return getFullResDefaultActivityIcon();
}
- public void cancelLoadingThumbnails() {
+ public void cancelLoadingThumbnailsAndIcons() {
+ if (mTaskLoader != null) {
+ mTaskLoader.cancel(false);
+ mTaskLoader = null;
+ }
if (mThumbnailLoader != null) {
mThumbnailLoader.cancel(false);
mThumbnailLoader = null;
}
}
- // return a snapshot of the current list of recent apps
- ArrayList<TaskDescription> getRecentTasks() {
- cancelLoadingThumbnails();
-
- ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
- final PackageManager pm = mContext.getPackageManager();
- final ActivityManager am = (ActivityManager)
+ public void loadTasksInBackground() {
+ // cancel all previous loading of tasks and thumbnails
+ cancelLoadingThumbnailsAndIcons();
+ final LinkedBlockingQueue<TaskDescription> tasksWaitingForThumbnails =
+ new LinkedBlockingQueue<TaskDescription>();
+ final ArrayList<TaskDescription> taskDescriptionsWaitingToLoad =
+ new ArrayList<TaskDescription>();
+ mTaskLoader = new AsyncTask<Void, ArrayList<TaskDescription>, Void>() {
+ @Override
+ protected void onProgressUpdate(ArrayList<TaskDescription>... values) {
+ if (!isCancelled()) {
+ ArrayList<TaskDescription> newTasks = values[0];
+ // do a callback to RecentsPanelView to let it know we have more values
+ // how do we let it know we're all done? just always call back twice
+ mRecentsPanel.onTasksLoaded(newTasks);
+ }
+ }
+ @Override
+ protected Void doInBackground(Void... params) {
+ // We load in two stages: first, we update progress with just the first screenful
+ // of items. Then, we update with the rest of the items
+ final int origPri = Process.getThreadPriority(Process.myTid());
+ Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE);
+ final PackageManager pm = mContext.getPackageManager();
+ final ActivityManager am = (ActivityManager)
mContext.getSystemService(Context.ACTIVITY_SERVICE);
- final List<ActivityManager.RecentTaskInfo> recentTasks =
- am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
+ final List<ActivityManager.RecentTaskInfo> recentTasks =
+ am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
+ int numTasks = recentTasks.size();
+ ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0);
- ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
- .resolveActivityInfo(pm, 0);
+ boolean firstScreenful = true;
+ ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
- HashSet<Integer> recentTasksToKeepInCache = new HashSet<Integer>();
- int numTasks = recentTasks.size();
-
- // skip the first task - assume it's either the home screen or the current activity.
- final int first = 1;
- recentTasksToKeepInCache.add(recentTasks.get(0).persistentId);
- for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
- final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
-
- TaskDescription item = createTaskDescription(recentInfo.id,
- recentInfo.persistentId, recentInfo.baseIntent,
- recentInfo.origActivity, recentInfo.description, homeInfo);
-
- if (item != null) {
- tasks.add(item);
- ++index;
- }
- }
-
- // when we're not using the TaskDescription cache, we load the thumbnails in the
- // background
- loadThumbnailsInBackground(new ArrayList<TaskDescription>(tasks));
- return tasks;
- }
-
- private void loadThumbnailsInBackground(final ArrayList<TaskDescription> descriptions) {
- if (descriptions.size() > 0) {
- if (DEBUG) Log.v(TAG, "Showing " + descriptions.size() + " tasks");
- loadThumbnail(descriptions.get(0));
- if (descriptions.size() > 1) {
- mThumbnailLoader = new AsyncTask<Void, Integer, Void>() {
- @Override
- protected void onProgressUpdate(Integer... values) {
- final TaskDescription td = descriptions.get(values[0]);
- if (!isCancelled()) {
- mRecentsPanel.onTaskThumbnailLoaded(td);
- }
- // This is to prevent the loader thread from getting ahead
- // of our UI updates.
- mHandler.post(new Runnable() {
- @Override public void run() {
- synchronized (td) {
- td.notifyAll();
- }
- }
- });
+ // skip the first task - assume it's either the home screen or the current activity.
+ final int first = 1;
+ for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
+ if (isCancelled()) {
+ break;
}
+ final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
+ TaskDescription item = createTaskDescription(recentInfo.id,
+ recentInfo.persistentId, recentInfo.baseIntent,
+ recentInfo.origActivity, recentInfo.description, homeInfo);
- @Override
- protected Void doInBackground(Void... params) {
- final int origPri = Process.getThreadPriority(Process.myTid());
- Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE);
- long nextTime = SystemClock.uptimeMillis();
- for (int i=1; i<descriptions.size(); i++) {
- TaskDescription td = descriptions.get(i);
- loadThumbnail(td);
- long now = SystemClock.uptimeMillis();
- nextTime += 0;
- if (nextTime > now) {
- try {
- Thread.sleep(nextTime-now);
- } catch (InterruptedException e) {
- }
- }
-
- if (isCancelled()) {
+ if (item != null) {
+ while (true) {
+ try {
+ tasksWaitingForThumbnails.put(item);
break;
- }
- synchronized (td) {
- publishProgress(i);
- try {
- td.wait(500);
- } catch (InterruptedException e) {
- }
+ } catch (InterruptedException e) {
}
}
- Process.setThreadPriority(origPri);
- return null;
+ tasks.add(item);
+ if (firstScreenful && tasks.size() == mNumTasksInFirstScreenful) {
+ publishProgress(tasks);
+ tasks = new ArrayList<TaskDescription>();
+ firstScreenful = false;
+ //break;
+ }
+ ++index;
}
- };
- mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ if (!isCancelled()) {
+ publishProgress(tasks);
+ if (firstScreenful) {
+ // always should publish two updates
+ publishProgress(new ArrayList<TaskDescription>());
+ }
+ }
+
+ while (true) {
+ try {
+ tasksWaitingForThumbnails.put(new TaskDescription());
+ break;
+ } catch (InterruptedException e) {
+ }
+ }
+
+ Process.setThreadPriority(origPri);
+ return null;
}
- }
+ };
+ mTaskLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ loadThumbnailsAndIconsInBackground(tasksWaitingForThumbnails);
}
+ private void loadThumbnailsAndIconsInBackground(
+ final BlockingQueue<TaskDescription> tasksWaitingForThumbnails) {
+ // continually read items from tasksWaitingForThumbnails and load
+ // thumbnails and icons for them. finish thread when cancelled or there
+ // is a null item in tasksWaitingForThumbnails
+ mThumbnailLoader = new AsyncTask<Void, TaskDescription, Void>() {
+ @Override
+ protected void onProgressUpdate(TaskDescription... values) {
+ if (!isCancelled()) {
+ TaskDescription td = values[0];
+ mRecentsPanel.onTaskThumbnailLoaded(td);
+ }
+ }
+ @Override
+ protected Void doInBackground(Void... params) {
+ final int origPri = Process.getThreadPriority(Process.myTid());
+ Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE);
+
+ while (true) {
+ if (isCancelled()) {
+ break;
+ }
+ TaskDescription td = null;
+ while (td == null) {
+ try {
+ td = tasksWaitingForThumbnails.take();
+ } catch (InterruptedException e) {
+ }
+ }
+ if (td.isNull()) {
+ break;
+ }
+ loadThumbnailAndIcon(td);
+ synchronized(td) {
+ publishProgress(td);
+ }
+ }
+
+ Process.setThreadPriority(origPri);
+ return null;
+ }
+ };
+ mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
index f971d2d..4718a17 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
@@ -22,11 +22,18 @@
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
+import android.view.View.MeasureSpec;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
+import android.view.ViewConfiguration;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
@@ -34,6 +41,8 @@
import com.android.systemui.SwipeHelper;
import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
+import java.util.ArrayList;
+
public class RecentsHorizontalScrollView extends HorizontalScrollView
implements SwipeHelper.Callback {
private static final String TAG = RecentsPanelView.TAG;
@@ -44,6 +53,8 @@
protected int mLastScrollPosition;
private SwipeHelper mSwipeHelper;
private RecentsScrollViewPerformanceHelper mPerformanceHelper;
+ private ArrayList<View> mRecycledViews;
+ private int mNumItemsInOneScreenful;
public RecentsHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
@@ -51,6 +62,7 @@
float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop);
mPerformanceHelper = RecentsScrollViewPerformanceHelper.create(context, attrs, this, false);
+ mRecycledViews = new ArrayList<View>();
}
private int scrollPositionOfMostRecent() {
@@ -58,9 +70,23 @@
}
private void update() {
+ for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
+ View v = mLinearLayout.getChildAt(i);
+ mRecycledViews.add(v);
+ mAdapter.recycleView(v);
+ }
+ LayoutTransition transitioner = getLayoutTransition();
+ setLayoutTransition(null);
+
mLinearLayout.removeAllViews();
for (int i = 0; i < mAdapter.getCount(); i++) {
- final View view = mAdapter.getView(i, null, mLinearLayout);
+ View old = null;
+ if (mRecycledViews.size() != 0) {
+ old = mRecycledViews.remove(mRecycledViews.size() - 1);
+ old.setVisibility(VISIBLE);
+ }
+
+ final View view = mAdapter.getView(i, old, mLinearLayout);
if (mPerformanceHelper != null) {
mPerformanceHelper.addViewCallback(view);
@@ -87,7 +113,8 @@
}
};
- final View thumbnailView = view.findViewById(R.id.app_thumbnail);
+ RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag();
+ final View thumbnailView = holder.thumbnailView;
OnLongClickListener longClickListener = new OnLongClickListener() {
public boolean onLongClick(View v) {
final View anchorView = view.findViewById(R.id.app_description);
@@ -107,13 +134,21 @@
appTitle.setOnTouchListener(noOpListener);
mLinearLayout.addView(view);
}
+ setLayoutTransition(transitioner);
+
// Scroll to end after layout.
- post(new Runnable() {
- public void run() {
- mLastScrollPosition = scrollPositionOfMostRecent();
- scrollTo(mLastScrollPosition, 0);
- }
- });
+ final ViewTreeObserver observer = getViewTreeObserver();
+
+ final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() {
+ public void onGlobalLayout() {
+ mLastScrollPosition = scrollPositionOfMostRecent();
+ scrollTo(mLastScrollPosition, 0);
+ if (observer.isAlive()) {
+ observer.removeOnGlobalLayoutListener(this);
+ }
+ }
+ };
+ observer.addOnGlobalLayoutListener(updateScroll);
}
@Override
@@ -142,8 +177,10 @@
}
public void onChildDismissed(View v) {
+ mRecycledViews.add(v);
mLinearLayout.removeView(v);
mCallback.handleSwipe(v);
+ v.setActivated(false);
}
public void onBeginDrag(View v) {
@@ -315,6 +352,24 @@
update();
}
});
+ DisplayMetrics dm = getResources().getDisplayMetrics();
+ int childWidthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST);
+ int childheightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST);
+ View child = mAdapter.createView(mLinearLayout);
+ child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+ mNumItemsInOneScreenful =
+ (int) FloatMath.ceil(dm.widthPixels / (float) child.getMeasuredWidth());
+ mRecycledViews.add(child);
+
+ for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) {
+ mRecycledViews.add(mAdapter.createView(mLinearLayout));
+ }
+ }
+
+ public int numItemsInOneScreenful() {
+ return mNumItemsInOneScreenful;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index a10e363..8706f10 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -27,6 +27,7 @@
import android.graphics.Matrix;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.Settings;
import android.util.AttributeSet;
@@ -36,18 +37,18 @@
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.PopupMenu;
-import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
@@ -59,7 +60,7 @@
import java.util.ArrayList;
-public class RecentsPanelView extends RelativeLayout implements OnItemClickListener, RecentsCallback,
+public class RecentsPanelView extends FrameLayout implements OnItemClickListener, RecentsCallback,
StatusBarPanel, Animator.AnimatorListener, View.OnTouchListener {
static final String TAG = "RecentsPanelView";
static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
@@ -71,6 +72,10 @@
private StatusBarTouchProxy mStatusBarTouchProxy;
private boolean mShowing;
+ private boolean mWaitingToShow;
+ private boolean mWaitingToShowAnimated;
+ private boolean mReadyToShow;
+ private int mNumItemsWaitingForThumbnailsAndIcons;
private Choreographer mChoreo;
OnRecentsPanelVisibilityChangedListener mVisibilityChangedListener;
@@ -104,6 +109,7 @@
TextView labelView;
TextView descriptionView;
TaskDescription taskDescription;
+ boolean loadedThumbnailAndIcon;
}
/* package */ final class TaskDescriptionAdapter extends BaseAdapter {
@@ -125,42 +131,82 @@
return position; // we just need something unique for this position
}
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewHolder holder;
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.status_bar_recent_item, parent, false);
- holder = new ViewHolder();
- holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail);
- holder.thumbnailViewImage = (ImageView) convertView.findViewById(
- R.id.app_thumbnail_image);
- // If we set the default thumbnail now, we avoid an onLayout when we update
- // the thumbnail later (if they both have the same dimensions)
+ public View createView(ViewGroup parent) {
+ View convertView = mInflater.inflate(R.layout.status_bar_recent_item, parent, false);
+ ViewHolder holder = new ViewHolder();
+ holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail);
+ holder.thumbnailViewImage =
+ (ImageView) convertView.findViewById(R.id.app_thumbnail_image);
+ // If we set the default thumbnail now, we avoid an onLayout when we update
+ // the thumbnail later (if they both have the same dimensions)
+ if (mRecentTasksLoader != null) {
updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
-
- holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon);
- holder.labelView = (TextView) convertView.findViewById(R.id.app_label);
- holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description);
-
- convertView.setTag(holder);
- } else {
- holder = (ViewHolder) convertView.getTag();
}
+ holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon);
+ if (mRecentTasksLoader != null) {
+ holder.iconView.setImageBitmap(mRecentTasksLoader.getDefaultIcon());
+ }
+ holder.labelView = (TextView) convertView.findViewById(R.id.app_label);
+ holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description);
+
+ convertView.setTag(holder);
+ return convertView;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = createView(parent);
+ }
+ ViewHolder holder = (ViewHolder) convertView.getTag();
// index is reverse since most recent appears at the bottom...
final int index = mRecentTaskDescriptions.size() - position - 1;
final TaskDescription td = mRecentTaskDescriptions.get(index);
- holder.iconView.setImageDrawable(td.getIcon());
+
holder.labelView.setText(td.getLabel());
holder.thumbnailView.setContentDescription(td.getLabel());
- updateThumbnail(holder, td.getThumbnail(), true, false);
+ holder.loadedThumbnailAndIcon = td.isLoaded();
+ if (td.isLoaded()) {
+ updateThumbnail(holder, td.getThumbnail(), true, false);
+ updateIcon(holder, td.getIcon(), true, false);
+ mNumItemsWaitingForThumbnailsAndIcons--;
+ }
holder.thumbnailView.setTag(td);
holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView));
holder.taskDescription = td;
-
return convertView;
}
+
+ public void recycleView(View v) {
+ ViewHolder holder = (ViewHolder) v.getTag();
+ updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
+ holder.iconView.setImageBitmap(mRecentTasksLoader.getDefaultIcon());
+ holder.iconView.setVisibility(INVISIBLE);
+ holder.labelView.setText(null);
+ holder.thumbnailView.setContentDescription(null);
+ holder.thumbnailView.setTag(null);
+ holder.thumbnailView.setOnLongClickListener(null);
+ holder.thumbnailView.setVisibility(INVISIBLE);
+ holder.taskDescription = null;
+ holder.loadedThumbnailAndIcon = false;
+ }
+ }
+
+ public int numItemsInOneScreenful() {
+ if (mRecentsContainer instanceof RecentsHorizontalScrollView){
+ RecentsHorizontalScrollView scrollView
+ = (RecentsHorizontalScrollView) mRecentsContainer;
+ return scrollView.numItemsInOneScreenful();
+ } else if (mRecentsContainer instanceof RecentsVerticalScrollView){
+ RecentsVerticalScrollView scrollView
+ = (RecentsVerticalScrollView) mRecentsContainer;
+ return scrollView.numItemsInOneScreenful();
+ }
+ else {
+ throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
+ }
}
@Override
@@ -192,15 +238,31 @@
}
public void show(boolean show, boolean animate) {
- show(show, animate, null);
+ if (show) {
+ mWaitingToShow = true;
+ mWaitingToShowAnimated = animate;
+ showIfReady();
+ } else {
+ show(show, animate, null, false);
+ }
+ }
+
+ private void showIfReady() {
+ // mWaitingToShow = there was a touch up on the recents button
+ // mReadyToShow = we've created views for the first screenful of items
+ if (mWaitingToShow && mReadyToShow) { // && mNumItemsWaitingForThumbnailsAndIcons <= 0
+ show(true, mWaitingToShowAnimated, null, false);
+ }
}
public void show(boolean show, boolean animate,
- ArrayList<TaskDescription> recentTaskDescriptions) {
+ ArrayList<TaskDescription> recentTaskDescriptions, boolean firstScreenful) {
+ // For now, disable animations. We may want to re-enable in the future
+ animate = false;
if (show) {
// Need to update list of recent apps before we set visibility so this view's
// content description is updated before it gets focus for TalkBack mode
- refreshRecentTasksList(recentTaskDescriptions);
+ refreshRecentTasksList(recentTaskDescriptions, firstScreenful);
// if there are no apps, either bring up a "No recent apps" message, or just
// quit early
@@ -209,19 +271,24 @@
mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE);
} else {
if (noApps) {
- if (DEBUG) Log.v(TAG, "Nothing to show");
+ if (DEBUG) Log.v(TAG, "Nothing to show");
// Need to set recent tasks to dirty so that next time we load, we
// refresh the list of tasks
- mRecentTasksLoader.cancelLoadingThumbnails();
+ mRecentTasksLoader.cancelLoadingThumbnailsAndIcons();
mRecentTasksDirty = true;
+
+ mWaitingToShow = false;
+ mReadyToShow = false;
return;
}
}
} else {
// Need to set recent tasks to dirty so that next time we load, we
// refresh the list of tasks
- mRecentTasksLoader.cancelLoadingThumbnails();
+ mRecentTasksLoader.cancelLoadingThumbnailsAndIcons();
mRecentTasksDirty = true;
+ mWaitingToShow = false;
+ mReadyToShow = false;
}
if (animate) {
if (mShowing != show) {
@@ -385,7 +452,6 @@
throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
}
-
mRecentsScrim = findViewById(R.id.recents_bg_protect);
mRecentsNoApps = findViewById(R.id.recents_no_apps);
mChoreo = new Choreographer(this, mRecentsScrim, mRecentsContainer, mRecentsNoApps, this);
@@ -425,6 +491,20 @@
}
}
+
+ private void updateIcon(ViewHolder h, Drawable icon, boolean show, boolean anim) {
+ if (icon != null) {
+ h.iconView.setImageDrawable(icon);
+ if (show && h.iconView.getVisibility() != View.VISIBLE) {
+ if (anim) {
+ h.iconView.setAnimation(
+ AnimationUtils.loadAnimation(mContext, R.anim.recent_appear));
+ }
+ h.iconView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
private void updateThumbnail(ViewHolder h, Bitmap thumbnail, boolean show, boolean anim) {
if (thumbnail != null) {
// Should remove the default image in the frame
@@ -458,31 +538,36 @@
}
}
- void onTaskThumbnailLoaded(TaskDescription ad) {
- synchronized (ad) {
+ void onTaskThumbnailLoaded(TaskDescription td) {
+ synchronized (td) {
if (mRecentsContainer != null) {
ViewGroup container = mRecentsContainer;
if (container instanceof HorizontalScrollView
|| container instanceof ScrollView) {
- container = (ViewGroup)container.findViewById(
+ container = (ViewGroup) container.findViewById(
R.id.recents_linear_layout);
}
// Look for a view showing this thumbnail, to update.
- for (int i=0; i<container.getChildCount(); i++) {
+ for (int i=0; i < container.getChildCount(); i++) {
View v = container.getChildAt(i);
if (v.getTag() instanceof ViewHolder) {
ViewHolder h = (ViewHolder)v.getTag();
- if (h.taskDescription == ad) {
+ if (!h.loadedThumbnailAndIcon && h.taskDescription == td) {
// only fade in the thumbnail if recents is already visible-- we
// show it immediately otherwise
- boolean animateShow = mShowing &&
- mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD;
- updateThumbnail(h, ad.getThumbnail(), true, animateShow);
+ //boolean animateShow = mShowing &&
+ // mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD;
+ boolean animateShow = false;
+ updateIcon(h, td.getIcon(), true, animateShow);
+ updateThumbnail(h, td.getThumbnail(), true, animateShow);
+ h.loadedThumbnailAndIcon = true;
+ mNumItemsWaitingForThumbnailsAndIcons--;
}
}
}
}
- }
+ }
+ showIfReady();
}
// additional optimization when we have sofware system buttons - start loading the recent
@@ -516,7 +601,7 @@
public void clearRecentTasksList() {
// Clear memory used by screenshots
if (mRecentTaskDescriptions != null) {
- mRecentTasksLoader.cancelLoadingThumbnails();
+ mRecentTasksLoader.cancelLoadingThumbnailsAndIcons();
mRecentTaskDescriptions.clear();
mListAdapter.notifyDataSetInvalidated();
mRecentTasksDirty = true;
@@ -524,26 +609,50 @@
}
public void refreshRecentTasksList() {
- refreshRecentTasksList(null);
+ refreshRecentTasksList(null, false);
}
- private void refreshRecentTasksList(ArrayList<TaskDescription> recentTasksList) {
+ private void refreshRecentTasksList(
+ ArrayList<TaskDescription> recentTasksList, boolean firstScreenful) {
if (mRecentTasksDirty) {
if (recentTasksList != null) {
- mRecentTaskDescriptions = recentTasksList;
+ mFirstScreenful = true;
+ onTasksLoaded(recentTasksList);
} else {
- mRecentTaskDescriptions = mRecentTasksLoader.getRecentTasks();
+ mFirstScreenful = true;
+ mRecentTasksLoader.loadTasksInBackground();
}
- mListAdapter.notifyDataSetInvalidated();
- updateUiElements(getResources().getConfiguration());
mRecentTasksDirty = false;
}
}
+ boolean mFirstScreenful;
+ public void onTasksLoaded(ArrayList<TaskDescription> tasks) {
+ if (!mFirstScreenful && tasks.size() == 0) {
+ return;
+ }
+ mNumItemsWaitingForThumbnailsAndIcons =
+ mFirstScreenful ? tasks.size() : mRecentTaskDescriptions.size();
+ if (mRecentTaskDescriptions == null) {
+ mRecentTaskDescriptions = new ArrayList(tasks);
+ } else {
+ mRecentTaskDescriptions.addAll(tasks);
+ }
+ mListAdapter.notifyDataSetInvalidated();
+ updateUiElements(getResources().getConfiguration());
+ mReadyToShow = true;
+ mFirstScreenful = false;
+ showIfReady();
+ }
+
public ArrayList<TaskDescription> getRecentTasksList() {
return mRecentTaskDescriptions;
}
+ public boolean getFirstScreenful() {
+ return mFirstScreenful;
+ }
+
private void updateUiElements(Configuration config) {
final int items = mRecentTaskDescriptions.size();
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
index dc13092..0605c4c 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
@@ -22,10 +22,18 @@
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.LinearLayout;
import android.widget.ScrollView;
@@ -33,6 +41,8 @@
import com.android.systemui.SwipeHelper;
import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
+import java.util.ArrayList;
+
public class RecentsVerticalScrollView extends ScrollView implements SwipeHelper.Callback {
private static final String TAG = RecentsPanelView.TAG;
private static final boolean DEBUG = RecentsPanelView.DEBUG;
@@ -42,6 +52,8 @@
protected int mLastScrollPosition;
private SwipeHelper mSwipeHelper;
private RecentsScrollViewPerformanceHelper mPerformanceHelper;
+ private ArrayList<View> mRecycledViews;
+ private int mNumItemsInOneScreenful;
public RecentsVerticalScrollView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
@@ -50,6 +62,7 @@
mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
mPerformanceHelper = RecentsScrollViewPerformanceHelper.create(context, attrs, this, true);
+ mRecycledViews = new ArrayList<View>();
}
private int scrollPositionOfMostRecent() {
@@ -57,77 +70,91 @@
}
private void update() {
+ for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
+ View v = mLinearLayout.getChildAt(i);
+ mRecycledViews.add(v);
+ mAdapter.recycleView(v);
+ }
+ LayoutTransition transitioner = getLayoutTransition();
+ setLayoutTransition(null);
+
mLinearLayout.removeAllViews();
// Once we can clear the data associated with individual item views,
// we can get rid of the removeAllViews() and the code below will
// recycle them.
for (int i = 0; i < mAdapter.getCount(); i++) {
View old = null;
- if (i < mLinearLayout.getChildCount()) {
- old = mLinearLayout.getChildAt(i);
- old.setVisibility(View.VISIBLE);
+ if (mRecycledViews.size() != 0) {
+ old = mRecycledViews.remove(mRecycledViews.size() - 1);
+ old.setVisibility(VISIBLE);
}
+
final View view = mAdapter.getView(i, old, mLinearLayout);
if (mPerformanceHelper != null) {
mPerformanceHelper.addViewCallback(view);
}
- if (old == null) {
- OnTouchListener noOpListener = new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return true;
- }
- };
+ OnTouchListener noOpListener = new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return true;
+ }
+ };
- view.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- mCallback.dismiss();
- }
- });
- // We don't want a click sound when we dimiss recents
- view.setSoundEffectsEnabled(false);
+ view.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mCallback.dismiss();
+ }
+ });
+ // We don't want a click sound when we dimiss recents
+ view.setSoundEffectsEnabled(false);
- OnClickListener launchAppListener = new OnClickListener() {
- public void onClick(View v) {
- mCallback.handleOnClick(view);
- }
- };
+ OnClickListener launchAppListener = new OnClickListener() {
+ public void onClick(View v) {
+ mCallback.handleOnClick(view);
+ }
+ };
- final View thumbnailView = view.findViewById(R.id.app_thumbnail);
- OnLongClickListener longClickListener = new OnLongClickListener() {
- public boolean onLongClick(View v) {
- final View anchorView = view.findViewById(R.id.app_description);
- mCallback.handleLongPress(view, anchorView, thumbnailView);
- return true;
- }
- };
- thumbnailView.setClickable(true);
- thumbnailView.setOnClickListener(launchAppListener);
- thumbnailView.setOnLongClickListener(longClickListener);
+ RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag();
+ final View thumbnailView = holder.thumbnailView;
+ OnLongClickListener longClickListener = new OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ final View anchorView = view.findViewById(R.id.app_description);
+ mCallback.handleLongPress(view, anchorView, thumbnailView);
+ return true;
+ }
+ };
+ thumbnailView.setClickable(true);
+ thumbnailView.setOnClickListener(launchAppListener);
+ thumbnailView.setOnLongClickListener(longClickListener);
- // We don't want to dismiss recents if a user clicks on the app title
- // (we also don't want to launch the app either, though, because the
- // app title is a small target and doesn't have great click feedback)
- final View appTitle = view.findViewById(R.id.app_label);
- appTitle.setContentDescription(" ");
- appTitle.setOnTouchListener(noOpListener);
- final View calloutLine = view.findViewById(R.id.recents_callout_line);
- calloutLine.setOnTouchListener(noOpListener);
- mLinearLayout.addView(view);
- }
+ // We don't want to dismiss recents if a user clicks on the app title
+ // (we also don't want to launch the app either, though, because the
+ // app title is a small target and doesn't have great click feedback)
+ final View appTitle = view.findViewById(R.id.app_label);
+ appTitle.setContentDescription(" ");
+ appTitle.setOnTouchListener(noOpListener);
+ final View calloutLine = view.findViewById(R.id.recents_callout_line);
+ calloutLine.setOnTouchListener(noOpListener);
+
+ mLinearLayout.addView(view);
}
- for (int i = mAdapter.getCount(); i < mLinearLayout.getChildCount(); i++) {
- mLinearLayout.getChildAt(i).setVisibility(View.GONE);
- }
+ setLayoutTransition(transitioner);
+
// Scroll to end after layout.
- post(new Runnable() {
- public void run() {
- mLastScrollPosition = scrollPositionOfMostRecent();
- scrollTo(0, mLastScrollPosition);
- }
- });
+ final ViewTreeObserver observer = getViewTreeObserver();
+
+ final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() {
+ public void onGlobalLayout() {
+ mLastScrollPosition = scrollPositionOfMostRecent();
+ scrollTo(0, mLastScrollPosition);
+ if (observer.isAlive()) {
+ observer.removeOnGlobalLayoutListener(this);
+ }
+ }
+ };
+ observer.addOnGlobalLayoutListener(updateScroll);
}
@Override
@@ -156,8 +183,10 @@
}
public void onChildDismissed(View v) {
+ mRecycledViews.add(v);
mLinearLayout.removeView(v);
mCallback.handleSwipe(v);
+ v.setActivated(false);
}
public void onBeginDrag(View v) {
@@ -330,6 +359,25 @@
update();
}
});
+
+ DisplayMetrics dm = getResources().getDisplayMetrics();
+ int childWidthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST);
+ int childheightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST);
+ View child = mAdapter.createView(mLinearLayout);
+ child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+ mNumItemsInOneScreenful =
+ (int) FloatMath.ceil(dm.heightPixels / (float) child.getMeasuredHeight());
+ mRecycledViews.add(child);
+
+ for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) {
+ mRecycledViews.add(mAdapter.createView(mLinearLayout));
+ }
+ }
+
+ public int numItemsInOneScreenful() {
+ return mNumItemsInOneScreenful;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
index dcfd6d8..7e979b7 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
@@ -32,6 +32,7 @@
private Bitmap mThumbnail; // generated by Activity.onCreateThumbnail()
private Drawable mIcon; // application package icon
private CharSequence mLabel; // application package label
+ private boolean mLoaded;
public TaskDescription(int _taskId, int _persistentTaskId,
ResolveInfo _resolveInfo, Intent _intent,
@@ -45,6 +46,28 @@
packageName = _packageName;
}
+ public TaskDescription() {
+ resolveInfo = null;
+ intent = null;
+ taskId = -1;
+ persistentTaskId = -1;
+
+ description = null;
+ packageName = null;
+ }
+
+ public void setLoaded(boolean loaded) {
+ mLoaded = loaded;
+ }
+
+ public boolean isLoaded() {
+ return mLoaded;
+ }
+
+ public boolean isNull() {
+ return resolveInfo == null;
+ }
+
// mark all these as locked?
public CharSequence getLabel() {
return mLabel;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 5a1e3f4..b3cef90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -393,7 +393,7 @@
}
lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
lp.setTitle("RecentsPanel");
- lp.windowAnimations = R.style.Animation_RecentPanel;
+ lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
return lp;
@@ -403,11 +403,13 @@
// Recents Panel
boolean visible = false;
ArrayList<TaskDescription> recentTasksList = null;
+ boolean firstScreenful = false;
if (mRecentsPanel != null) {
visible = mRecentsPanel.isShowing();
WindowManagerImpl.getDefault().removeView(mRecentsPanel);
if (visible) {
recentTasksList = mRecentsPanel.getRecentTasksList();
+ firstScreenful = mRecentsPanel.getFirstScreenful();
}
}
@@ -425,7 +427,7 @@
WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
mRecentsPanel.setBar(this);
if (visible) {
- mRecentsPanel.show(true, false, recentTasksList);
+ mRecentsPanel.show(true, false, recentTasksList, firstScreenful);
}
}
diff --git a/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java b/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java
index 5f53a9b..1a2dcb9 100644
--- a/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java
+++ b/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java
@@ -19,12 +19,21 @@
import com.android.internal.os.RuntimeInit;
import android.app.ActivityManager;
+import android.app.ActivityManager.ProcessErrorStateInfo;
import android.content.Context;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.test.AndroidTestCase;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
/**
* This smoke test is designed to quickly sniff for any error conditions
@@ -32,53 +41,125 @@
*/
public class ProcessErrorsTest extends AndroidTestCase {
- private final String TAG = "ProcessErrorsTest";
+ private static final String TAG = "ProcessErrorsTest";
protected ActivityManager mActivityManager;
+ protected PackageManager mPackageManager;
@Override
public void setUp() throws Exception {
super.setUp();
- mActivityManager = (ActivityManager)
+ mActivityManager = (ActivityManager)
getContext().getSystemService(Context.ACTIVITY_SERVICE);
+ mPackageManager = getContext().getPackageManager();
}
public void testSetUpConditions() throws Exception {
assertNotNull(mActivityManager);
+ assertNotNull(mPackageManager);
}
public void testNoProcessErrors() throws Exception {
- List<ActivityManager.ProcessErrorStateInfo> errList;
+ final String reportMsg = checkForProcessErrors();
+ if (reportMsg != null) {
+ Log.w(TAG, reportMsg);
+ }
+
+ // report a non-empty list back to the test framework
+ assertNull(reportMsg, reportMsg);
+ }
+
+ private String checkForProcessErrors() throws Exception {
+ List<ProcessErrorStateInfo> errList;
errList = mActivityManager.getProcessesInErrorState();
// note: this contains information about each process that is currently in an error
// condition. if the list is empty (null) then "we're good".
// if the list is non-empty, then it's useful to report the contents of the list
- // we'll put a copy in the log, and we'll report it back to the framework via the assert.
final String reportMsg = reportListContents(errList);
- if (reportMsg != null) {
- Log.w(TAG, reportMsg);
- }
-
- // report a non-empty list back to the test framework
- assertNull(reportMsg, errList);
+ return reportMsg;
}
-
+
+ /**
+ * A test that runs all Launcher-launchable activities and verifies that no ANRs or crashes
+ * happened while doing so.
+ * <p />
+ * FIXME: Doesn't detect multiple crashing apps properly, since the crash dialog for the
+ * FIXME: first app doesn't go away.
+ */
+ public void testRunAllActivities() throws Exception {
+ final Intent home = new Intent(Intent.ACTION_MAIN);
+ home.addCategory(Intent.CATEGORY_HOME);
+ home.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final Intent launchable = new Intent(Intent.ACTION_MAIN);
+ launchable.addCategory(Intent.CATEGORY_LAUNCHER);
+ final List<ResolveInfo> activities = mPackageManager.queryIntentActivities(launchable, 0);
+ final Set<ProcessError> errSet = new HashSet<ProcessError>();
+
+ for (ResolveInfo info : activities) {
+ Log.i(TAG, String.format("Got %s/%s", info.activityInfo.packageName,
+ info.activityInfo.name));
+
+ // build an Intent to launch the app
+ final ComponentName component = new ComponentName(info.activityInfo.packageName,
+ info.activityInfo.name);
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(component);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // launch app, and wait 7 seconds for it to start/settle
+ getContext().startActivity(intent);
+ try {
+ Thread.sleep(7000);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+
+ // See if there are any errors
+ Collection<ProcessErrorStateInfo> procs = mActivityManager.getProcessesInErrorState();
+ if (procs != null) {
+ errSet.addAll(ProcessError.fromCollection(procs));
+ }
+
+ // Send the "home" intent and wait 2 seconds for us to get there
+ getContext().startActivity(home);
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+
+ if (!errSet.isEmpty()) {
+ fail(String.format("Got %d errors: %s", errSet.size(),
+ reportWrappedListContents(errSet)));
+ }
+ }
+
+ private String reportWrappedListContents(Collection<ProcessError> errList) {
+ List<ProcessErrorStateInfo> newList = new ArrayList<ProcessErrorStateInfo>(errList.size());
+ for (ProcessError err : errList) {
+ newList.add(err.info);
+ }
+ return reportListContents(newList);
+ }
+
/**
* This helper function will dump the actual error reports.
*
* @param errList The error report containing one or more error records.
* @return Returns a string containing all of the errors.
*/
- private String reportListContents(List<ActivityManager.ProcessErrorStateInfo> errList) {
+ private String reportListContents(Collection<ProcessErrorStateInfo> errList) {
if (errList == null) return null;
StringBuilder builder = new StringBuilder();
- Iterator<ActivityManager.ProcessErrorStateInfo> iter = errList.iterator();
+ Iterator<ProcessErrorStateInfo> iter = errList.iterator();
while (iter.hasNext()) {
- ActivityManager.ProcessErrorStateInfo entry = iter.next();
+ ProcessErrorStateInfo entry = iter.next();
String condition;
switch (entry.condition) {
@@ -96,8 +177,77 @@
builder.append("Process error ").append(condition).append(" ");
builder.append(" ").append(entry.shortMsg);
builder.append(" detected in ").append(entry.processName).append(" ").append(entry.tag);
+ builder.append("\n");
}
return builder.toString();
}
-
+
+ /**
+ * A {@link ProcessErrorStateInfo} wrapper class that hashes how we want (so that equivalent
+ * crashes are considered equal).
+ */
+ private static class ProcessError {
+ public final ProcessErrorStateInfo info;
+
+ public ProcessError(ProcessErrorStateInfo newInfo) {
+ info = newInfo;
+ }
+
+ public static Collection<ProcessError> fromCollection(Collection<ProcessErrorStateInfo> in)
+ {
+ List<ProcessError> out = new ArrayList<ProcessError>(in.size());
+ for (ProcessErrorStateInfo info : in) {
+ out.add(new ProcessError(info));
+ }
+ return out;
+ }
+
+ private boolean strEquals(String a, String b) {
+ if ((a == null) && (b == null)) {
+ return true;
+ } else if ((a == null) || (b == null)) {
+ return false;
+ } else {
+ return a.equals(b);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) return false;
+ if (!(other instanceof ProcessError)) return false;
+ ProcessError peOther = (ProcessError) other;
+
+ return (info.condition == peOther.info.condition)
+ && strEquals(info.longMsg, peOther.info.longMsg)
+ && (info.pid == peOther.info.pid)
+ && strEquals(info.processName, peOther.info.processName)
+ && strEquals(info.shortMsg, peOther.info.shortMsg)
+ && strEquals(info.stackTrace, peOther.info.stackTrace)
+ && strEquals(info.tag, peOther.info.tag)
+ && (info.uid == peOther.info.uid);
+ }
+
+ private int hash(Object obj) {
+ if (obj == null) {
+ return 13;
+ } else {
+ return obj.hashCode();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int code = 17;
+ code += info.condition;
+ code *= hash(info.longMsg);
+ code += info.pid;
+ code *= hash(info.processName);
+ code *= hash(info.shortMsg);
+ code *= hash(info.stackTrace);
+ code *= hash(info.tag);
+ code += info.uid;
+ return code;
+ }
+ }
}
diff --git a/tests/SmokeTestApps/Android.mk b/tests/SmokeTestApps/Android.mk
new file mode 100644
index 0000000..3f5f011
--- /dev/null
+++ b/tests/SmokeTestApps/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := SmokeTestTriggerApps
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/SmokeTestApps/AndroidManifest.xml b/tests/SmokeTestApps/AndroidManifest.xml
new file mode 100644
index 0000000..0f20107
--- /dev/null
+++ b/tests/SmokeTestApps/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.smoketest.triggers">
+
+ <application android:label="something">
+ <activity android:name=".CrashyApp"
+ android:label="Test Crashy App">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".CrashyApp2"
+ android:label="Test Crashy App2">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".UnresponsiveApp"
+ android:label="Test Unresponsive App">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/SmokeTestApps/README b/tests/SmokeTestApps/README
new file mode 100644
index 0000000..04aa366
--- /dev/null
+++ b/tests/SmokeTestApps/README
@@ -0,0 +1,3 @@
+The apps in this folder are intentionally bad-behaving apps that are intended
+to trigger the smoke tests to fail. They are otherwise not useful.
+
diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
new file mode 100644
index 0000000..c11b0f3
--- /dev/null
+++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 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.smoketest.triggers;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class CrashyApp extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ TextView tv = new TextView(this);
+ tv.setText("Hello, Crashy Android");
+ setContentView(tv);
+ }
+
+ @Override
+ public void onResume() {
+ ((String) null).length();
+ }
+}
diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp2.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp2.java
new file mode 100644
index 0000000..3ef5b2b
--- /dev/null
+++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp2.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.smoketest.triggers;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class CrashyApp2 extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ TextView tv = new TextView(this);
+ tv.setText("Hello, Other Crashy Android");
+ setContentView(tv);
+ }
+
+
+ @Override
+ public void onResume() {
+ throw new RuntimeException("Two drums and a cymbal fall off a cliff...");
+ }
+}
diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/UnresponsiveApp.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/UnresponsiveApp.java
new file mode 100644
index 0000000..1291897
--- /dev/null
+++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/UnresponsiveApp.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2012 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.smoketest.triggers;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class UnresponsiveApp extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ TextView tv = new TextView(this);
+ tv.setText("Hello, Unresponsive Android");
+ setContentView(tv);
+ }
+
+ @Override
+ public void onResume() {
+ // Attempt to provoke the ire of the ActivityManager
+ while (true) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index bbb74d1..d05e0b8 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -366,17 +366,6 @@
handleDriverEvent(eventData);
} else if (event == TERMINATING) {
/**
- * If monitor socket is closed, we have already
- * stopped the supplicant, simply exit the monitor thread
- */
- if (eventData.startsWith(MONITOR_SOCKET_CLOSED_STR)) {
- if (false) {
- Log.d(TAG, "Monitor socket is closed, exiting thread");
- }
- break;
- }
-
- /**
* Close the supplicant connection if we see
* too many recv errors
*/
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index 48a785c..e3dd3a6 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -39,6 +39,8 @@
*/
public class WifiNative {
+ private static final boolean DBG = false;
+ private final String mTAG;
private static final int DEFAULT_GROUP_OWNER_INTENT = 7;
static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED = 0;
@@ -53,9 +55,7 @@
public native static boolean unloadDriver();
- public native static boolean startSupplicant();
-
- public native static boolean startP2pSupplicant();
+ public native static boolean startSupplicant(boolean p2pSupported);
/* Sends a kill signal to supplicant. To be used when we have lost connection
or when the supplicant is hung */
@@ -79,6 +79,7 @@
public WifiNative(String iface) {
mInterface = iface;
+ mTAG = "WifiNative-" + iface;
}
public boolean connectToSupplicant() {
@@ -94,14 +95,17 @@
}
private boolean doBooleanCommand(String command) {
+ if (DBG) Log.d(mTAG, "doBoolean: " + command);
return doBooleanCommand(mInterface, command);
}
private int doIntCommand(String command) {
+ if (DBG) Log.d(mTAG, "doInt: " + command);
return doIntCommand(mInterface, command);
}
private String doStringCommand(String command) {
+ if (DBG) Log.d(mTAG, "doString: " + command);
return doStringCommand(mInterface, command);
}
@@ -437,6 +441,10 @@
return doBooleanCommand("P2P_FIND " + timeout);
}
+ public boolean p2pStopFind() {
+ return doBooleanCommand("P2P_STOP_FIND");
+ }
+
public boolean p2pListen() {
return doBooleanCommand("P2P_LISTEN");
}
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index fb9286e..0134456 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -45,6 +45,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.DhcpInfo;
import android.net.DhcpInfoInternal;
@@ -118,6 +119,8 @@
private INetworkManagementService mNwService;
private ConnectivityManager mCm;
+ private final boolean mP2pSupported;
+
/* Scan results handling */
private List<ScanResult> mScanResults;
private static final Pattern scanResultPattern = Pattern.compile("\t+");
@@ -361,9 +364,9 @@
/* 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;
+ /* P2p commands */
+ public static final int CMD_ENABLE_P2P = BASE + 131;
+ public static final int CMD_DISABLE_P2P = BASE + 132;
private static final int CONNECT_MODE = 1;
private static final int SCAN_ONLY_MODE = 2;
@@ -482,9 +485,6 @@
/* Waiting for untether confirmation to stop soft Ap */
private State mSoftApStoppingState = new SoftApStoppingState();
- /* Wait till p2p is disabled */
- private State mWaitForP2pDisableState = new WaitForP2pDisableState();
-
private class TetherStateChange {
ArrayList<String> available;
ArrayList<String> active;
@@ -556,6 +556,9 @@
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
mNwService = INetworkManagementService.Stub.asInterface(b);
+ mP2pSupported = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_DIRECT);
+
mWifiNative = new WifiNative(mInterfaceName);
mWifiConfigStore = new WifiConfigStore(context, mWifiNative);
mWifiMonitor = new WifiMonitor(this, mWifiNative);
@@ -639,7 +642,6 @@
addState(mTetheringState, mSoftApStartedState);
addState(mTetheredState, mSoftApStartedState);
addState(mSoftApStoppingState, mDefaultState);
- addState(mWaitForP2pDisableState, mDefaultState);
setInitialState(mInitialState);
@@ -1896,11 +1898,6 @@
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:
loge("Error! unhandled message" + message);
break;
@@ -2060,7 +2057,7 @@
loge("Unable to change interface settings: " + ie);
}
- if(mWifiNative.startSupplicant()) {
+ if(mWifiNative.startSupplicant(mP2pSupported)) {
if (DBG) log("Supplicant start successful");
mWifiMonitor.startMonitoring();
transitionTo(mSupplicantStartingState);
@@ -2172,11 +2169,7 @@
if (DBG) log(getName() + message.toString() + "\n");
switch (message.what) {
case CMD_LOAD_DRIVER:
- mWifiP2pChannel.sendMessage(WIFI_ENABLE_PENDING);
- transitionTo(mWaitForP2pDisableState);
- break;
- case WifiP2pService.P2P_ENABLE_PENDING:
- mReplyChannel.replyToMessage(message, P2P_ENABLE_PROCEED);
+ transitionTo(mDriverLoadingState);
break;
default:
return NOT_HANDLED;
@@ -2556,13 +2549,15 @@
mWifiNative.status();
transitionTo(mDisconnectedState);
}
+
+ if (mP2pSupported) mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P);
}
@Override
public boolean processMessage(Message message) {
if (DBG) log(getName() + message.toString() + "\n");
boolean eventLoggingEnabled = true;
switch(message.what) {
- case CMD_SET_SCAN_TYPE:
+ case CMD_SET_SCAN_TYPE:
mSetScanActive = (message.arg1 == SCAN_ACTIVE);
mWifiNative.setScanMode(mSetScanActive);
break;
@@ -2675,6 +2670,8 @@
mIsRunning = false;
updateBatteryWorkSource(null);
mScanResults = null;
+
+ if (mP2pSupported) mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P);
}
}
@@ -3348,7 +3345,6 @@
case CMD_START_PACKET_FILTERING:
case CMD_STOP_PACKET_FILTERING:
case CMD_TETHER_STATE_CHANGE:
- case WifiP2pService.P2P_ENABLE_PENDING:
deferMessage(message);
break;
case WifiStateMachine.CMD_RESPONSE_AP_CONFIG:
@@ -3412,55 +3408,6 @@
transitionTo(mTetheringState);
}
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;
- }
- EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
- return HANDLED;
- }
- }
-
- class WaitForP2pDisableState extends State {
- private int mSavedArg;
- @Override
- public void enter() {
- if (DBG) log(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(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;
}
@@ -3510,7 +3457,6 @@
case CMD_SET_FREQUENCY_BAND:
case CMD_START_PACKET_FILTERING:
case CMD_STOP_PACKET_FILTERING:
- case WifiP2pService.P2P_ENABLE_PENDING:
deferMessage(message);
break;
default:
@@ -3606,7 +3552,6 @@
case CMD_SET_FREQUENCY_BAND:
case CMD_START_PACKET_FILTERING:
case CMD_STOP_PACKET_FILTERING:
- case WifiP2pService.P2P_ENABLE_PENDING:
deferMessage(message);
break;
default:
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
index 7471a2d..b0cde64 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -301,7 +301,8 @@
private String trimQuotes(String str) {
str = str.trim();
if (str.startsWith("'") && str.endsWith("'")) {
- return str.substring(1, str.length()-1);
+ if (str.length() <= 2) return "";
+ else return str.substring(1, str.length()-1);
}
return str;
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 9205300..4fd0a57 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -199,68 +199,61 @@
private static final int BASE = Protocol.BASE_WIFI_P2P_MANAGER;
/** @hide */
- public static final int ENABLE_P2P = BASE + 1;
+ public static final int DISCOVER_PEERS = BASE + 1;
/** @hide */
- public static final int ENABLE_P2P_FAILED = BASE + 2;
+ public static final int DISCOVER_PEERS_FAILED = BASE + 2;
/** @hide */
- public static final int ENABLE_P2P_SUCCEEDED = BASE + 3;
+ public static final int DISCOVER_PEERS_SUCCEEDED = BASE + 3;
/** @hide */
- public static final int DISABLE_P2P = BASE + 4;
+ public static final int STOP_DISCOVERY = BASE + 4;
/** @hide */
- public static final int DISABLE_P2P_FAILED = BASE + 5;
+ public static final int STOP_DISCOVERY_FAILED = BASE + 5;
/** @hide */
- public static final int DISABLE_P2P_SUCCEEDED = BASE + 6;
+ public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 6;
/** @hide */
- public static final int DISCOVER_PEERS = BASE + 7;
+ public static final int CONNECT = BASE + 7;
/** @hide */
- public static final int DISCOVER_PEERS_FAILED = BASE + 8;
+ public static final int CONNECT_FAILED = BASE + 8;
/** @hide */
- public static final int DISCOVER_PEERS_SUCCEEDED = BASE + 9;
+ public static final int CONNECT_SUCCEEDED = BASE + 9;
/** @hide */
- public static final int CONNECT = BASE + 10;
+ public static final int CANCEL_CONNECT = BASE + 10;
/** @hide */
- public static final int CONNECT_FAILED = BASE + 11;
+ public static final int CANCEL_CONNECT_FAILED = BASE + 11;
/** @hide */
- public static final int CONNECT_SUCCEEDED = BASE + 12;
+ public static final int CANCEL_CONNECT_SUCCEEDED = BASE + 12;
/** @hide */
- public static final int CANCEL_CONNECT = BASE + 13;
+ public static final int CREATE_GROUP = BASE + 13;
/** @hide */
- public static final int CANCEL_CONNECT_FAILED = BASE + 14;
+ public static final int CREATE_GROUP_FAILED = BASE + 14;
/** @hide */
- public static final int CANCEL_CONNECT_SUCCEEDED = BASE + 15;
+ public static final int CREATE_GROUP_SUCCEEDED = BASE + 15;
/** @hide */
- public static final int CREATE_GROUP = BASE + 16;
+ public static final int REMOVE_GROUP = BASE + 16;
/** @hide */
- public static final int CREATE_GROUP_FAILED = BASE + 17;
+ public static final int REMOVE_GROUP_FAILED = BASE + 17;
/** @hide */
- public static final int CREATE_GROUP_SUCCEEDED = BASE + 18;
+ public static final int REMOVE_GROUP_SUCCEEDED = BASE + 18;
/** @hide */
- public static final int REMOVE_GROUP = BASE + 19;
+ public static final int REQUEST_PEERS = BASE + 19;
/** @hide */
- public static final int REMOVE_GROUP_FAILED = BASE + 20;
- /** @hide */
- public static final int REMOVE_GROUP_SUCCEEDED = BASE + 21;
+ public static final int RESPONSE_PEERS = BASE + 20;
/** @hide */
- public static final int REQUEST_PEERS = BASE + 22;
+ public static final int REQUEST_CONNECTION_INFO = BASE + 21;
/** @hide */
- public static final int RESPONSE_PEERS = BASE + 23;
+ public static final int RESPONSE_CONNECTION_INFO = BASE + 22;
/** @hide */
- public static final int REQUEST_CONNECTION_INFO = BASE + 24;
+ public static final int REQUEST_GROUP_INFO = BASE + 23;
/** @hide */
- public static final int RESPONSE_CONNECTION_INFO = BASE + 25;
-
- /** @hide */
- public static final int REQUEST_GROUP_INFO = BASE + 26;
- /** @hide */
- public static final int RESPONSE_GROUP_INFO = BASE + 27;
+ public static final int RESPONSE_GROUP_INFO = BASE + 24;
/**
* Create a new WifiP2pManager instance. Applications use
@@ -376,6 +369,7 @@
break;
/* ActionListeners grouped together */
case WifiP2pManager.DISCOVER_PEERS_FAILED:
+ case WifiP2pManager.STOP_DISCOVERY_FAILED:
case WifiP2pManager.CONNECT_FAILED:
case WifiP2pManager.CANCEL_CONNECT_FAILED:
case WifiP2pManager.CREATE_GROUP_FAILED:
@@ -386,6 +380,7 @@
break;
/* ActionListeners grouped together */
case WifiP2pManager.DISCOVER_PEERS_SUCCEEDED:
+ case WifiP2pManager.STOP_DISCOVERY_SUCCEEDED:
case WifiP2pManager.CONNECT_SUCCEEDED:
case WifiP2pManager.CANCEL_CONNECT_SUCCEEDED:
case WifiP2pManager.CREATE_GROUP_SUCCEEDED:
@@ -459,26 +454,6 @@
}
/**
- * 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.
- * @hide
- */
- public void enableP2p(Channel c) {
- if (c == null) return;
- c.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.
- * @hide
- */
- public void disableP2p(Channel c) {
- if (c == null) return;
- c.mAsyncChannel.sendMessage(DISABLE_P2P);
- }
-
- /**
* Initiate peer discovery. A discovery process involves scanning for available Wi-Fi peers
* for the purpose of establishing a connection.
*
@@ -503,6 +478,16 @@
}
/**
+ * TODO: Add more documentation before opening up
+ * Cancel peer discovery
+ * @hide
+ */
+ public void stopPeerDiscovery(Channel c, ActionListener listener) {
+ if (c == null) return;
+ c.mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, c.putListener(listener));
+ }
+
+ /**
* Start a p2p connection to a device with the specified configuration.
*
* <p> The function call immediately returns after sending a connection request
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 69cbb5c..5b0e424 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -49,6 +49,7 @@
import android.os.HandlerThread;
import android.os.Message;
import android.os.Messenger;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
@@ -84,7 +85,7 @@
*/
public class WifiP2pService extends IWifiP2pManager.Stub {
private static final String TAG = "WifiP2pService";
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
private static final String NETWORKTYPE = "WIFI_P2P";
private Context mContext;
@@ -94,11 +95,6 @@
INetworkManagementService mNwService;
private DhcpStateMachine mDhcpStateMachine;
- //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;
@@ -110,6 +106,9 @@
private static final int GROUP_CREATING_WAIT_TIME_MS = 120 * 1000;
private static int mGroupCreatingTimeoutIndex = 0;
+ /* Set a two minute discover timeout to avoid STA scans from being blocked */
+ private static final int DISCOVER_TIMEOUT_S = 120;
+
/**
* Delay between restarts upon failure to setup connection with supplicant
*/
@@ -124,28 +123,13 @@
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;
-
/* Delayed message to timeout group creation */
- public static final int GROUP_CREATING_TIMED_OUT = BASE + 3;
-
- /* User accepted to disable Wi-Fi in order to enable p2p */
- private static final int WIFI_DISABLE_USER_ACCEPT = BASE + 4;
- /* User rejected to disable Wi-Fi in order to enable p2p */
- private static final int WIFI_DISABLE_USER_REJECT = BASE + 5;
+ public static final int GROUP_CREATING_TIMED_OUT = BASE + 1;
/* User accepted a peer request */
- private static final int PEER_CONNECTION_USER_ACCEPT = BASE + 6;
+ private static final int PEER_CONNECTION_USER_ACCEPT = BASE + 2;
/* User rejected a peer request */
- private static final int PEER_CONNECTION_USER_REJECT = BASE + 7;
-
- /* Airplane mode changed */
- private static final int AIRPLANE_MODE_CHANGED = BASE + 8;
- /* Emergency callback mode */
- private static final int EMERGENCY_CALLBACK_MODE = BASE + 9;
+ private static final int PEER_CONNECTION_USER_REJECT = BASE + 3;
private final boolean mP2pSupported;
@@ -166,7 +150,7 @@
public WifiP2pService(Context context) {
mContext = context;
- //STOPSHIP: fix this
+ //STOPSHIP: get this from native side
mInterface = "p2p0";
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORKTYPE, "");
@@ -179,15 +163,6 @@
mP2pStateMachine = new P2pStateMachine(TAG, mP2pSupported);
mP2pStateMachine.start();
-
- // broadcasts
- IntentFilter filter = new IntentFilter();
- filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
- filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
- mContext.registerReceiver(new WifiStateReceiver(), filter);
-
}
public void connectivityServiceReady() {
@@ -195,26 +170,6 @@
mNwService = INetworkManagementService.Stub.asInterface(b);
}
- private class WifiStateReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
- mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
- WifiManager.WIFI_STATE_DISABLED);
- } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
- mWifiApState = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
- WifiManager.WIFI_AP_STATE_DISABLED);
- } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
- mP2pStateMachine.sendMessage(AIRPLANE_MODE_CHANGED);
- } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
- if (intent.getBooleanExtra("phoneinECMState", false) == true) {
- mP2pStateMachine.sendMessage(EMERGENCY_CALLBACK_MODE);
- }
- }
- }
- }
-
private void enforceAccessPermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
"WifiP2pService");
@@ -264,8 +219,6 @@
private P2pNotSupportedState mP2pNotSupportedState = new P2pNotSupportedState();
private P2pDisablingState mP2pDisablingState = new P2pDisablingState();
private P2pDisabledState mP2pDisabledState = new P2pDisabledState();
- private WaitForUserActionState mWaitForUserActionState = new WaitForUserActionState();
- private WaitForWifiDisableState mWaitForWifiDisableState = new WaitForWifiDisableState();
private P2pEnablingState mP2pEnablingState = new P2pEnablingState();
private P2pEnabledState mP2pEnabledState = new P2pEnabledState();
// Inactive is when p2p is enabled with no connectivity
@@ -299,8 +252,6 @@
addState(mP2pNotSupportedState, mDefaultState);
addState(mP2pDisablingState, mDefaultState);
addState(mP2pDisabledState, mDefaultState);
- addState(mWaitForUserActionState, mP2pDisabledState);
- addState(mWaitForWifiDisableState, mP2pDisabledState);
addState(mP2pEnablingState, mDefaultState);
addState(mP2pEnabledState, mDefaultState);
addState(mInactiveState, mP2pEnabledState);
@@ -346,23 +297,14 @@
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:
- replyToMessage(message, WifiP2pManager.ENABLE_P2P_FAILED,
- WifiP2pManager.BUSY);
- break;
- case WifiP2pManager.DISABLE_P2P:
- replyToMessage(message, WifiP2pManager.DISABLE_P2P_FAILED,
- WifiP2pManager.BUSY);
- break;
case WifiP2pManager.DISCOVER_PEERS:
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
WifiP2pManager.BUSY);
break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.BUSY);
+ break;
case WifiP2pManager.CONNECT:
replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
WifiP2pManager.BUSY);
@@ -388,16 +330,14 @@
case WifiP2pManager.REQUEST_GROUP_INFO:
replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO, mGroup);
break;
- case AIRPLANE_MODE_CHANGED:
- if (isAirplaneModeOn()) sendMessage(WifiP2pManager.DISABLE_P2P);
- break;
- case EMERGENCY_CALLBACK_MODE:
- sendMessage(WifiP2pManager.DISABLE_P2P);
- break;
// Ignore
case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
- case WIFI_DISABLE_USER_ACCEPT:
- case WIFI_DISABLE_USER_REJECT:
+ case WifiMonitor.SCAN_RESULTS_EVENT:
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
case PEER_CONNECTION_USER_ACCEPT:
case PEER_CONNECTION_USER_REJECT:
case GROUP_CREATING_TIMED_OUT:
@@ -414,22 +354,14 @@
@Override
public boolean processMessage(Message message) {
switch (message.what) {
- // Allow Wi-Fi to proceed
- case WifiStateMachine.WIFI_ENABLE_PENDING:
- replyToMessage(message, WIFI_ENABLE_PROCEED);
- break;
- case WifiP2pManager.ENABLE_P2P:
- replyToMessage(message, WifiP2pManager.ENABLE_P2P_FAILED,
- WifiP2pManager.P2P_UNSUPPORTED);
- break;
- case WifiP2pManager.DISABLE_P2P:
- replyToMessage(message, WifiP2pManager.DISABLE_P2P_FAILED,
- WifiP2pManager.P2P_UNSUPPORTED);
- break;
- case WifiP2pManager.DISCOVER_PEERS:
+ case WifiP2pManager.DISCOVER_PEERS:
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
case WifiP2pManager.CONNECT:
replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
@@ -438,7 +370,7 @@
replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
- case WifiP2pManager.CREATE_GROUP:
+ case WifiP2pManager.CREATE_GROUP:
replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
@@ -455,26 +387,15 @@
class P2pDisablingState extends State {
@Override
- public void enter() {
- if (DBG) logd(getName());
- logd("stopping supplicant");
- if (!mWifiNative.stopSupplicant()) {
- loge("Failed to stop supplicant, issue kill");
- mWifiNative.killSupplicant();
- }
- }
-
- @Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case WifiMonitor.SUP_DISCONNECTION_EVENT:
- logd("Supplicant connection lost");
- mWifiNative.closeSupplicantConnection();
+ if (DBG) logd("p2p socket connection lost");
transitionTo(mP2pDisabledState);
break;
- case WifiP2pManager.ENABLE_P2P:
- case WifiP2pManager.DISABLE_P2P:
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ case WifiStateMachine.CMD_DISABLE_P2P:
deferMessage(message);
break;
default:
@@ -484,7 +405,6 @@
}
}
-
class P2pDisabledState extends State {
@Override
public void enter() {
@@ -495,118 +415,19 @@
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
- case WifiP2pManager.ENABLE_P2P:
- OnClickListener listener = new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- sendMessage(WIFI_DISABLE_USER_ACCEPT);
- } else {
- sendMessage(WIFI_DISABLE_USER_REJECT);
- }
- }
- };
-
- // 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();
- transitionTo(mWaitForUserActionState);
- } else {
- mWifiChannel.sendMessage(P2P_ENABLE_PENDING);
- transitionTo(mWaitForWifiDisableState);
- }
- replyToMessage(message, WifiP2pManager.ENABLE_P2P_SUCCEEDED);
- break;
- case WifiP2pManager.DISABLE_P2P:
- replyToMessage(message, WifiP2pManager.DISABLE_P2P_SUCCEEDED);
- break;
- case WifiStateMachine.WIFI_ENABLE_PENDING:
- replyToMessage(message, WIFI_ENABLE_PROCEED);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- class WaitForUserActionState extends State {
- @Override
- public void enter() {
- if (DBG) logd(getName());
- }
-
- @Override
- public boolean processMessage(Message message) {
- if (DBG) logd(getName() + message.toString());
- switch (message.what) {
- case WIFI_DISABLE_USER_ACCEPT:
- mWifiChannel.sendMessage(P2P_ENABLE_PENDING);
- transitionTo(mWaitForWifiDisableState);
- break;
- case WIFI_DISABLE_USER_REJECT:
- logd("User rejected enabling p2p");
- sendP2pStateChangedBroadcast(false);
- transitionTo(mP2pDisabledState);
- break;
- case WifiP2pManager.ENABLE_P2P:
- case WifiP2pManager.DISABLE_P2P:
- deferMessage(message);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- class WaitForWifiDisableState extends State {
- @Override
- public void enter() {
- if (DBG) logd(getName());
- }
-
- @Override
- public boolean processMessage(Message message) {
- if (DBG) logd(getName() + message.toString());
- switch (message.what) {
- case WifiStateMachine.P2P_ENABLE_PROCEED:
+ case WifiStateMachine.CMD_ENABLE_P2P:
try {
- mNwService.wifiFirmwareReload(mInterface, "P2P");
- } catch (Exception e) {
- loge("Failed to reload p2p firmware " + e);
- // continue
+ mNwService.setInterfaceUp(mInterface);
+ } catch (RemoteException re) {
+ loge("Unable to change interface settings: " + re);
+ } catch (IllegalStateException ie) {
+ loge("Unable to change interface settings: " + ie);
}
-
- //A runtime crash can leave the interface up and
- //this affects p2p when supplicant starts up.
- //Ensure interface is down before a supplicant start.
- try {
- mNwService.setInterfaceDown(mInterface);
- } catch (Exception e) {
- if (DBG) Slog.w(TAG, "Unable to bring down wlan interface: " + e);
- }
-
- if (mWifiNative.startP2pSupplicant()) {
- mWifiMonitor.startMonitoring();
- transitionTo(mP2pEnablingState);
- } else {
- notifyP2pEnableFailure();
- transitionTo(mP2pDisabledState);
- }
+ mWifiMonitor.startMonitoring();
+ transitionTo(mP2pEnablingState);
break;
- case WifiP2pManager.ENABLE_P2P:
- case WifiP2pManager.DISABLE_P2P:
- deferMessage(message);
+ case WifiStateMachine.CMD_DISABLE_P2P:
+ //Nothing to do
break;
default:
return NOT_HANDLED;
@@ -626,22 +447,15 @@
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case WifiMonitor.SUP_CONNECTION_EVENT:
- logd("P2p start successful");
+ if (DBG) logd("P2p socket connection successful");
transitionTo(mInactiveState);
break;
case WifiMonitor.SUP_DISCONNECTION_EVENT:
- if (++mP2pRestartCount <= P2P_RESTART_TRIES) {
- loge("Failed to start p2p, retry");
- mWifiNative.killSupplicant();
- sendMessageDelayed(WifiP2pManager.ENABLE_P2P, P2P_RESTART_INTERVAL_MSECS);
- } else {
- loge("Failed " + mP2pRestartCount + " times to start p2p, quit ");
- mP2pRestartCount = 0;
- }
+ loge("P2p socket connection failed");
transitionTo(mP2pDisabledState);
break;
- case WifiP2pManager.ENABLE_P2P:
- case WifiP2pManager.DISABLE_P2P:
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ case WifiStateMachine.CMD_DISABLE_P2P:
deferMessage(message);
break;
default:
@@ -658,30 +472,36 @@
sendP2pStateChangedBroadcast(true);
mNetworkInfo.setIsAvailable(true);
initializeP2pSettings();
- showNotification();
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
- case WifiP2pManager.ENABLE_P2P:
- replyToMessage(message, WifiP2pManager.ENABLE_P2P_SUCCEEDED);
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ //Nothing to do
break;
- case WifiP2pManager.DISABLE_P2P:
+ case WifiStateMachine.CMD_DISABLE_P2P:
if (mPeers.clear()) sendP2pPeersChangedBroadcast();
- replyToMessage(message, WifiP2pManager.DISABLE_P2P_SUCCEEDED);
+ mWifiNative.closeSupplicantConnection();
transitionTo(mP2pDisablingState);
break;
case WifiP2pManager.DISCOVER_PEERS:
- int timeout = message.arg1;
- if (mWifiNative.p2pFind(timeout)) {
+ if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
WifiP2pManager.ERROR);
}
break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ if (mWifiNative.p2pStopFind()) {
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
WifiP2pDevice device = (WifiP2pDevice) message.obj;
if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
@@ -692,15 +512,7 @@
device = (WifiP2pDevice) message.obj;
if (mPeers.remove(device)) sendP2pPeersChangedBroadcast();
break;
- case WifiMonitor.SUP_DISCONNECTION_EVENT: /* Supplicant died */
- loge("Connection lost, restart p2p");
- mWifiNative.killSupplicant();
- mWifiNative.closeSupplicantConnection();
- if (mPeers.clear()) sendP2pPeersChangedBroadcast();
- transitionTo(mP2pDisabledState);
- sendMessageDelayed(WifiP2pManager.ENABLE_P2P, P2P_RESTART_INTERVAL_MSECS);
- break;
- default:
+ default:
return NOT_HANDLED;
}
return HANDLED;
@@ -710,7 +522,6 @@
public void exit() {
sendP2pStateChangedBroadcast(false);
mNetworkInfo.setIsAvailable(false);
- clearNotification();
}
}
@@ -719,7 +530,8 @@
public void enter() {
if (DBG) logd(getName());
//Start listening every time we get inactive
- mWifiNative.p2pListen();
+ //TODO: Fix listen after driver behavior is fixed
+ //mWifiNative.p2pListen();
}
@Override
@@ -737,6 +549,8 @@
//TODO: if failure, remove config and do a regular p2pConnect()
mWifiNative.p2pReinvoke(netId, mSavedPeerConfig.deviceAddress);
} else {
+ //Stop discovery before issuing connect
+ mWifiNative.p2pStopFind();
//If peer is a GO, we do not need to send provisional discovery,
//the supplicant takes care of it.
if (isGroupOwner(mSavedPeerConfig.deviceAddress)) {
@@ -1114,7 +928,7 @@
}
// Do the regular device lost handling
return NOT_HANDLED;
- case WifiP2pManager.DISABLE_P2P:
+ case WifiStateMachine.CMD_DISABLE_P2P:
sendMessage(WifiP2pManager.REMOVE_GROUP);
deferMessage(message);
break;
@@ -1494,54 +1308,5 @@
Slog.e(TAG, s);
}
- private void showNotification() {
- NotificationManager notificationManager =
- (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- if (notificationManager == null || mNotification != null) {
- return;
- }
-
- Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-
- PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
-
- Resources r = Resources.getSystem();
- CharSequence title = r.getText(R.string.wifi_p2p_enabled_notification_title);
- CharSequence message = r.getText(R.string.wifi_p2p_enabled_notification_message);
-
- mNotification = new Notification();
- mNotification.when = 0;
- //TODO: might change to be a seperate icon
- mNotification.icon = R.drawable.stat_sys_tether_wifi;
- mNotification.defaults &= ~Notification.DEFAULT_SOUND;
- mNotification.flags = Notification.FLAG_ONGOING_EVENT;
- mNotification.tickerText = title;
- mNotification.setLatestEventInfo(mContext, title, message, pi);
-
- notificationManager.notify(mNotification.icon, mNotification);
- }
-
- private void clearNotification() {
- NotificationManager notificationManager =
- (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- if (notificationManager != null && mNotification != null) {
- notificationManager.cancel(mNotification.icon);
- mNotification = null;
- }
- }
-
- private boolean isAirplaneSensitive() {
- String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(),
- Settings.System.AIRPLANE_MODE_RADIOS);
- return airplaneModeRadios == null
- || airplaneModeRadios.contains(Settings.System.RADIO_WIFI);
- }
-
- private boolean isAirplaneModeOn() {
- return isAirplaneSensitive() && Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.AIRPLANE_MODE_ON, 0) == 1;
- }
-
}
}