Created
July 3, 2012 07:51
-
-
Save zaki50/3038347 to your computer and use it in GitHub Desktop.
diff of android API15 to API16
This file has been truncated, but you can view the full file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff -Nur android-15/android/accessibilityservice/AccessibilityService.java android-16/android/accessibilityservice/AccessibilityService.java | |
--- android-15/android/accessibilityservice/AccessibilityService.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/accessibilityservice/AccessibilityService.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -17,8 +17,10 @@ | |
package android.accessibilityservice; | |
import android.app.Service; | |
+import android.content.Context; | |
import android.content.Intent; | |
import android.os.IBinder; | |
+import android.os.Looper; | |
import android.os.Message; | |
import android.os.RemoteException; | |
import android.util.Log; | |
@@ -35,6 +37,14 @@ | |
* etc. Such a service can optionally request the capability for querying the content | |
* of the active window. Development of an accessibility service requires extending this | |
* class and implementing its abstract methods. | |
+ * | |
+ * <div class="special reference"> | |
+ * <h3>Developer Guides</h3> | |
+ * <p>For more information about creating AccessibilityServices, read the | |
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a> | |
+ * developer guide.</p> | |
+ * </div> | |
+ * | |
* <h3>Lifecycle</h3> | |
* <p> | |
* The lifecycle of an accessibility service is managed exclusively by the system and | |
@@ -49,9 +59,14 @@ | |
* An accessibility is declared as any other service in an AndroidManifest.xml but it | |
* must also specify that it handles the "android.accessibilityservice.AccessibilityService" | |
* {@link android.content.Intent}. Failure to declare this intent will cause the system to | |
- * ignore the accessibility service. Following is an example declaration: | |
+ * ignore the accessibility service. Additionally an accessibility service must request the | |
+ * {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission to ensure | |
+ * that only the system | |
+ * can bind to it. Failure to declare this intent will cause the system to ignore the | |
+ * accessibility service. Following is an example declaration: | |
* </p> | |
- * <pre> <service android:name=".MyAccessibilityService"> | |
+ * <pre> <service android:name=".MyAccessibilityService" | |
+ * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE> | |
* <intent-filter> | |
* <action android:name="android.accessibilityservice.AccessibilityService" /> | |
* </intent-filter> | |
@@ -102,7 +117,7 @@ | |
* </ul> | |
* <h3>Retrieving window content</h3> | |
* <p> | |
- * An service can specify in its declaration that it can retrieve the active window | |
+ * A service can specify in its declaration that it can retrieve the active window | |
* content which is represented as a tree of {@link AccessibilityNodeInfo}. Note that | |
* declaring this capability requires that the service declares its configuration via | |
* an XML resource referenced by {@link #SERVICE_META_DATA}. | |
@@ -192,6 +207,87 @@ | |
* @see android.view.accessibility.AccessibilityManager | |
*/ | |
public abstract class AccessibilityService extends Service { | |
+ | |
+ /** | |
+ * The user has performed a swipe up gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_UP = 1; | |
+ | |
+ /** | |
+ * The user has performed a swipe down gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_DOWN = 2; | |
+ | |
+ /** | |
+ * The user has performed a swipe left gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_LEFT = 3; | |
+ | |
+ /** | |
+ * The user has performed a swipe right gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_RIGHT = 4; | |
+ | |
+ /** | |
+ * The user has performed a swipe left and right gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5; | |
+ | |
+ /** | |
+ * The user has performed a swipe right and left gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6; | |
+ | |
+ /** | |
+ * The user has performed a swipe up and down gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_UP_AND_DOWN = 7; | |
+ | |
+ /** | |
+ * The user has performed a swipe down and up gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_DOWN_AND_UP = 8; | |
+ | |
+ /** | |
+ * The user has performed a left and up gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_LEFT_AND_UP = 9; | |
+ | |
+ /** | |
+ * The user has performed a left and down gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 10; | |
+ | |
+ /** | |
+ * The user has performed a right and up gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_RIGHT_AND_UP = 11; | |
+ | |
+ /** | |
+ * The user has performed a right and down gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 12; | |
+ | |
+ /** | |
+ * The user has performed an up and left gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_UP_AND_LEFT = 13; | |
+ | |
+ /** | |
+ * The user has performed an up and right gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; | |
+ | |
+ /** | |
+ * The user has performed an down and left gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; | |
+ | |
+ /** | |
+ * The user has performed an down and right gesture on the touch screen. | |
+ */ | |
+ public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; | |
+ | |
/** | |
* The {@link Intent} that must be declared as handled by the service. | |
*/ | |
@@ -216,12 +312,40 @@ | |
*/ | |
public static final String SERVICE_META_DATA = "android.accessibilityservice"; | |
+ /** | |
+ * Action to go back. | |
+ */ | |
+ public static final int GLOBAL_ACTION_BACK = 1; | |
+ | |
+ /** | |
+ * Action to go home. | |
+ */ | |
+ public static final int GLOBAL_ACTION_HOME = 2; | |
+ | |
+ /** | |
+ * Action to open the recents. | |
+ */ | |
+ public static final int GLOBAL_ACTION_RECENTS = 3; | |
+ | |
+ /** | |
+ * Action to open the notifications. | |
+ */ | |
+ public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; | |
+ | |
private static final String LOG_TAG = "AccessibilityService"; | |
- private AccessibilityServiceInfo mInfo; | |
+ interface Callbacks { | |
+ public void onAccessibilityEvent(AccessibilityEvent event); | |
+ public void onInterrupt(); | |
+ public void onServiceConnected(); | |
+ public void onSetConnectionId(int connectionId); | |
+ public boolean onGesture(int gestureId); | |
+ } | |
private int mConnectionId; | |
+ private AccessibilityServiceInfo mInfo; | |
+ | |
/** | |
* Callback for {@link android.view.accessibility.AccessibilityEvent}s. | |
* | |
@@ -247,6 +371,100 @@ | |
} | |
/** | |
+ * Called by the system when the user performs a specific gesture on the | |
+ * touch screen. | |
+ * | |
+ * <strong>Note:</strong> To receive gestures an accessibility service must | |
+ * request that the device is in touch exploration mode by setting the | |
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} | |
+ * flag. | |
+ * | |
+ * @param gestureId The unique id of the performed gesture. | |
+ * | |
+ * @return Whether the gesture was handled. | |
+ * | |
+ * @see #GESTURE_SWIPE_UP | |
+ * @see #GESTURE_SWIPE_UP_AND_LEFT | |
+ * @see #GESTURE_SWIPE_UP_AND_DOWN | |
+ * @see #GESTURE_SWIPE_UP_AND_RIGHT | |
+ * @see #GESTURE_SWIPE_DOWN | |
+ * @see #GESTURE_SWIPE_DOWN_AND_LEFT | |
+ * @see #GESTURE_SWIPE_DOWN_AND_UP | |
+ * @see #GESTURE_SWIPE_DOWN_AND_RIGHT | |
+ * @see #GESTURE_SWIPE_LEFT | |
+ * @see #GESTURE_SWIPE_LEFT_AND_UP | |
+ * @see #GESTURE_SWIPE_LEFT_AND_RIGHT | |
+ * @see #GESTURE_SWIPE_LEFT_AND_DOWN | |
+ * @see #GESTURE_SWIPE_RIGHT | |
+ * @see #GESTURE_SWIPE_RIGHT_AND_UP | |
+ * @see #GESTURE_SWIPE_RIGHT_AND_LEFT | |
+ * @see #GESTURE_SWIPE_RIGHT_AND_DOWN | |
+ */ | |
+ protected boolean onGesture(int gestureId) { | |
+ return false; | |
+ } | |
+ | |
+ /** | |
+ * Gets the root node in the currently active window if this service | |
+ * can retrieve window content. | |
+ * | |
+ * @return The root node if this service can retrieve window content. | |
+ */ | |
+ public AccessibilityNodeInfo getRootInActiveWindow() { | |
+ return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId); | |
+ } | |
+ | |
+ /** | |
+ * Performs a global action. Such an action can be performed | |
+ * at any moment regardless of the current application or user | |
+ * location in that application. For example going back, going | |
+ * home, opening recents, etc. | |
+ * | |
+ * @param action The action to perform. | |
+ * @return Whether the action was successfully performed. | |
+ * | |
+ * @see #GLOBAL_ACTION_BACK | |
+ * @see #GLOBAL_ACTION_HOME | |
+ * @see #GLOBAL_ACTION_NOTIFICATIONS | |
+ * @see #GLOBAL_ACTION_RECENTS | |
+ */ | |
+ public final boolean performGlobalAction(int action) { | |
+ IAccessibilityServiceConnection connection = | |
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); | |
+ if (connection != null) { | |
+ try { | |
+ return connection.performGlobalAction(action); | |
+ } catch (RemoteException re) { | |
+ Log.w(LOG_TAG, "Error while calling performGlobalAction", re); | |
+ } | |
+ } | |
+ return false; | |
+ } | |
+ | |
+ /** | |
+ * Gets the an {@link AccessibilityServiceInfo} describing this | |
+ * {@link AccessibilityService}. This method is useful if one wants | |
+ * to change some of the dynamically configurable properties at | |
+ * runtime. | |
+ * | |
+ * @return The accessibility service info. | |
+ * | |
+ * @see AccessibilityNodeInfo | |
+ */ | |
+ public final AccessibilityServiceInfo getServiceInfo() { | |
+ IAccessibilityServiceConnection connection = | |
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); | |
+ if (connection != null) { | |
+ try { | |
+ return connection.getServiceInfo(); | |
+ } catch (RemoteException re) { | |
+ Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re); | |
+ } | |
+ } | |
+ return null; | |
+ } | |
+ | |
+ /** | |
* Sets the {@link AccessibilityServiceInfo} that describes this service. | |
* <p> | |
* Note: You can call this method any time but the info will be picked up after | |
@@ -270,6 +488,8 @@ | |
if (mInfo != null && connection != null) { | |
try { | |
connection.setServiceInfo(mInfo); | |
+ mInfo = null; | |
+ AccessibilityInteractionClient.getInstance().clearCache(); | |
} catch (RemoteException re) { | |
Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re); | |
} | |
@@ -282,27 +502,56 @@ | |
*/ | |
@Override | |
public final IBinder onBind(Intent intent) { | |
- return new IEventListenerWrapper(this); | |
+ return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() { | |
+ @Override | |
+ public void onServiceConnected() { | |
+ AccessibilityService.this.onServiceConnected(); | |
+ } | |
+ | |
+ @Override | |
+ public void onInterrupt() { | |
+ AccessibilityService.this.onInterrupt(); | |
+ } | |
+ | |
+ @Override | |
+ public void onAccessibilityEvent(AccessibilityEvent event) { | |
+ AccessibilityService.this.onAccessibilityEvent(event); | |
+ } | |
+ | |
+ @Override | |
+ public void onSetConnectionId( int connectionId) { | |
+ mConnectionId = connectionId; | |
+ } | |
+ | |
+ @Override | |
+ public boolean onGesture(int gestureId) { | |
+ return AccessibilityService.this.onGesture(gestureId); | |
+ } | |
+ }); | |
} | |
/** | |
- * Implements the internal {@link IEventListener} interface to convert | |
+ * Implements the internal {@link IAccessibilityServiceClient} interface to convert | |
* incoming calls to it back to calls on an {@link AccessibilityService}. | |
*/ | |
- class IEventListenerWrapper extends IEventListener.Stub | |
+ static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub | |
implements HandlerCaller.Callback { | |
+ static final int NO_ID = -1; | |
+ | |
private static final int DO_SET_SET_CONNECTION = 10; | |
private static final int DO_ON_INTERRUPT = 20; | |
private static final int DO_ON_ACCESSIBILITY_EVENT = 30; | |
+ private static final int DO_ON_GESTURE = 40; | |
private final HandlerCaller mCaller; | |
- private final AccessibilityService mTarget; | |
+ private final Callbacks mCallback; | |
- public IEventListenerWrapper(AccessibilityService context) { | |
- mTarget = context; | |
- mCaller = new HandlerCaller(context, this); | |
+ public IAccessibilityServiceClientWrapper(Context context, Looper looper, | |
+ Callbacks callback) { | |
+ mCallback = callback; | |
+ mCaller = new HandlerCaller(context, looper, this); | |
} | |
public void setConnection(IAccessibilityServiceConnection connection, int connectionId) { | |
@@ -321,17 +570,23 @@ | |
mCaller.sendMessage(message); | |
} | |
+ public void onGesture(int gestureId) { | |
+ Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId); | |
+ mCaller.sendMessage(message); | |
+ } | |
+ | |
public void executeMessage(Message message) { | |
switch (message.what) { | |
case DO_ON_ACCESSIBILITY_EVENT : | |
AccessibilityEvent event = (AccessibilityEvent) message.obj; | |
if (event != null) { | |
- mTarget.onAccessibilityEvent(event); | |
+ AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event); | |
+ mCallback.onAccessibilityEvent(event); | |
event.recycle(); | |
} | |
return; | |
case DO_ON_INTERRUPT : | |
- mTarget.onInterrupt(); | |
+ mCallback.onInterrupt(); | |
return; | |
case DO_SET_SET_CONNECTION : | |
final int connectionId = message.arg1; | |
@@ -340,14 +595,17 @@ | |
if (connection != null) { | |
AccessibilityInteractionClient.getInstance().addConnection(connectionId, | |
connection); | |
- mConnectionId = connectionId; | |
- mTarget.onServiceConnected(); | |
+ mCallback.onSetConnectionId(connectionId); | |
+ mCallback.onServiceConnected(); | |
} else { | |
AccessibilityInteractionClient.getInstance().removeConnection(connectionId); | |
- mConnectionId = AccessibilityInteractionClient.NO_ID; | |
- // TODO: Do we need a onServiceDisconnected callback? | |
+ mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID); | |
} | |
return; | |
+ case DO_ON_GESTURE : | |
+ final int gestureId = message.arg1; | |
+ mCallback.onGesture(gestureId); | |
+ return; | |
default : | |
Log.w(LOG_TAG, "Unknown message type " + message.what); | |
} | |
diff -Nur android-15/android/accessibilityservice/AccessibilityServiceInfo.java android-16/android/accessibilityservice/AccessibilityServiceInfo.java | |
--- android-15/android/accessibilityservice/AccessibilityServiceInfo.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/accessibilityservice/AccessibilityServiceInfo.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -25,10 +25,13 @@ | |
import android.content.res.Resources; | |
import android.content.res.TypedArray; | |
import android.content.res.XmlResourceParser; | |
+import android.os.Build; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
import android.util.AttributeSet; | |
+import android.util.TypedValue; | |
import android.util.Xml; | |
+import android.view.View; | |
import android.view.accessibility.AccessibilityEvent; | |
import org.xmlpull.v1.XmlPullParser; | |
@@ -41,6 +44,13 @@ | |
* {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s | |
* according to the information encapsulated in this class. | |
* | |
+ * <div class="special reference"> | |
+ * <h3>Developer Guides</h3> | |
+ * <p>For more information about creating AccessibilityServices, read the | |
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a> | |
+ * developer guide.</p> | |
+ * </div> | |
+ * | |
* @see AccessibilityService | |
* @see android.view.accessibility.AccessibilityEvent | |
* @see android.view.accessibility.AccessibilityManager | |
@@ -93,6 +103,49 @@ | |
public static final int DEFAULT = 0x0000001; | |
/** | |
+ * If this flag is set the system will regard views that are not important | |
+ * for accessibility in addition to the ones that are important for accessibility. | |
+ * That is, views that are marked as not important for accessibility via | |
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} and views that are marked as | |
+ * potentially important for accessibility via | |
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_AUTO} for which the system has determined | |
+ * that are not important for accessibility, are both reported while querying the | |
+ * window content and also the accessibility service will receive accessibility events | |
+ * from them. | |
+ * <p> | |
+ * <strong>Note:</strong> For accessibility services targeting API version | |
+ * {@link Build.VERSION_CODES#JELLY_BEAN} or higher this flag has to be explicitly | |
+ * set for the system to regard views that are not important for accessibility. For | |
+ * accessibility services targeting API version lower than | |
+ * {@link Build.VERSION_CODES#JELLY_BEAN} this flag is ignored and all views are | |
+ * regarded for accessibility purposes. | |
+ * </p> | |
+ * <p> | |
+ * Usually views not important for accessibility are layout managers that do not | |
+ * react to user actions, do not draw any content, and do not have any special | |
+ * semantics in the context of the screen content. For example, a three by three | |
+ * grid can be implemented as three horizontal linear layouts and one vertical, | |
+ * or three vertical linear layouts and one horizontal, or one grid layout, etc. | |
+ * In this context the actual layout mangers used to achieve the grid configuration | |
+ * are not important, rather it is important that there are nine evenly distributed | |
+ * elements. | |
+ * </p> | |
+ */ | |
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002; | |
+ | |
+ /** | |
+ * This flag requests that the system gets into touch exploration mode. | |
+ * In this mode a single finger moving on the screen behaves as a mouse | |
+ * pointer hovering over the user interface. The system will also detect | |
+ * certain gestures performed on the touch screen and notify this service. | |
+ * The system will enable touch exploration mode if there is at least one | |
+ * accessibility service that has this flag set. Hence, clearing this | |
+ * flag does not guarantee that the device will not be in touch exploration | |
+ * mode since there may be another enabled service that requested it. | |
+ */ | |
+ public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE= 0x0000004; | |
+ | |
+ /** | |
* The event types an {@link AccessibilityService} is interested in. | |
* <p> | |
* <strong>Can be dynamically set at runtime.</strong> | |
@@ -157,6 +210,8 @@ | |
* <strong>Can be dynamically set at runtime.</strong> | |
* </p> | |
* @see #DEFAULT | |
+ * @see #FLAG_INCLUDE_NOT_IMPORTANT_VIEWS | |
+ * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE | |
*/ | |
public int flags; | |
@@ -182,9 +237,14 @@ | |
private boolean mCanRetrieveWindowContent; | |
/** | |
- * Description of the accessibility service. | |
+ * Resource id of the description of the accessibility service. | |
*/ | |
- private String mDescription; | |
+ private int mDescriptionResId; | |
+ | |
+ /** | |
+ * Non localized description of the accessibility service. | |
+ */ | |
+ private String mNonLocalizedDescription; | |
/** | |
* Creates a new instance. | |
@@ -256,8 +316,15 @@ | |
mCanRetrieveWindowContent = asAttributes.getBoolean( | |
com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent, | |
false); | |
- mDescription = asAttributes.getString( | |
+ TypedValue peekedValue = asAttributes.peekValue( | |
com.android.internal.R.styleable.AccessibilityService_description); | |
+ if (peekedValue != null) { | |
+ mDescriptionResId = peekedValue.resourceId; | |
+ CharSequence nonLocalizedDescription = peekedValue.coerceToString(); | |
+ if (nonLocalizedDescription != null) { | |
+ mNonLocalizedDescription = nonLocalizedDescription.toString().trim(); | |
+ } | |
+ } | |
asAttributes.recycle(); | |
} catch (NameNotFoundException e) { | |
throw new XmlPullParserException( "Unable to create context for: " | |
@@ -324,22 +391,45 @@ | |
* <strong>Statically set from | |
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> | |
* </p> | |
- * @return True window content can be retrieved. | |
+ * @return True if window content can be retrieved. | |
*/ | |
public boolean getCanRetrieveWindowContent() { | |
return mCanRetrieveWindowContent; | |
} | |
/** | |
- * Description of the accessibility service. | |
+ * Gets the non-localized description of the accessibility service. | |
* <p> | |
* <strong>Statically set from | |
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> | |
* </p> | |
* @return The description. | |
+ * | |
+ * @deprecated Use {@link #loadDescription(PackageManager)}. | |
*/ | |
public String getDescription() { | |
- return mDescription; | |
+ return mNonLocalizedDescription; | |
+ } | |
+ | |
+ /** | |
+ * The localized description of the accessibility service. | |
+ * <p> | |
+ * <strong>Statically set from | |
+ * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> | |
+ * </p> | |
+ * @return The localized description. | |
+ */ | |
+ public String loadDescription(PackageManager packageManager) { | |
+ if (mDescriptionResId == 0) { | |
+ return mNonLocalizedDescription; | |
+ } | |
+ ServiceInfo serviceInfo = mResolveInfo.serviceInfo; | |
+ CharSequence description = packageManager.getText(serviceInfo.packageName, | |
+ mDescriptionResId, serviceInfo.applicationInfo); | |
+ if (description != null) { | |
+ return description.toString().trim(); | |
+ } | |
+ return null; | |
} | |
/** | |
@@ -359,7 +449,8 @@ | |
parcel.writeParcelable(mResolveInfo, 0); | |
parcel.writeString(mSettingsActivityName); | |
parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0); | |
- parcel.writeString(mDescription); | |
+ parcel.writeInt(mDescriptionResId); | |
+ parcel.writeString(mNonLocalizedDescription); | |
} | |
private void initFromParcel(Parcel parcel) { | |
@@ -372,7 +463,8 @@ | |
mResolveInfo = parcel.readParcelable(null); | |
mSettingsActivityName = parcel.readString(); | |
mCanRetrieveWindowContent = (parcel.readInt() == 1); | |
- mDescription = parcel.readString(); | |
+ mDescriptionResId = parcel.readInt(); | |
+ mNonLocalizedDescription = parcel.readString(); | |
} | |
@Override | |
@@ -465,26 +557,38 @@ | |
public static String feedbackTypeToString(int feedbackType) { | |
StringBuilder builder = new StringBuilder(); | |
builder.append("["); | |
- while (feedbackType > 0) { | |
+ while (feedbackType != 0) { | |
final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(feedbackType); | |
feedbackType &= ~feedbackTypeFlag; | |
- if (builder.length() > 1) { | |
- builder.append(", "); | |
- } | |
switch (feedbackTypeFlag) { | |
case FEEDBACK_AUDIBLE: | |
+ if (builder.length() > 1) { | |
+ builder.append(", "); | |
+ } | |
builder.append("FEEDBACK_AUDIBLE"); | |
break; | |
case FEEDBACK_HAPTIC: | |
+ if (builder.length() > 1) { | |
+ builder.append(", "); | |
+ } | |
builder.append("FEEDBACK_HAPTIC"); | |
break; | |
case FEEDBACK_GENERIC: | |
+ if (builder.length() > 1) { | |
+ builder.append(", "); | |
+ } | |
builder.append("FEEDBACK_GENERIC"); | |
break; | |
case FEEDBACK_SPOKEN: | |
+ if (builder.length() > 1) { | |
+ builder.append(", "); | |
+ } | |
builder.append("FEEDBACK_SPOKEN"); | |
break; | |
case FEEDBACK_VISUAL: | |
+ if (builder.length() > 1) { | |
+ builder.append(", "); | |
+ } | |
builder.append("FEEDBACK_VISUAL"); | |
break; | |
} | |
@@ -504,6 +608,10 @@ | |
switch (flag) { | |
case DEFAULT: | |
return "DEFAULT"; | |
+ case FLAG_INCLUDE_NOT_IMPORTANT_VIEWS: | |
+ return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS"; | |
+ case FLAG_REQUEST_TOUCH_EXPLORATION_MODE: | |
+ return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE"; | |
default: | |
return null; | |
} | |
diff -Nur android-15/android/accessibilityservice/InterrogationActivity.java android-16/android/accessibilityservice/InterrogationActivity.java | |
--- android-15/android/accessibilityservice/InterrogationActivity.java 2012-06-18 20:00:44.000000000 +0900 | |
+++ android-16/android/accessibilityservice/InterrogationActivity.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -14,12 +14,12 @@ | |
package android.accessibilityservice; | |
-import com.android.frameworks.coretests.R; | |
- | |
import android.app.Activity; | |
import android.os.Bundle; | |
import android.view.View; | |
+import com.android.frameworks.coretests.R; | |
+ | |
/** | |
* Activity for testing the accessibility APIs for "interrogation" of | |
* the screen content. These APIs allow exploring the screen and | |
diff -Nur android-15/android/accessibilityservice/InterrogationActivityTest.java android-16/android/accessibilityservice/InterrogationActivityTest.java | |
--- android-15/android/accessibilityservice/InterrogationActivityTest.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/accessibilityservice/InterrogationActivityTest.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -14,26 +14,21 @@ | |
package android.accessibilityservice; | |
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS; | |
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION; | |
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS; | |
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS; | |
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT; | |
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION; | |
-import android.content.Context; | |
import android.graphics.Rect; | |
-import android.os.ServiceManager; | |
import android.os.SystemClock; | |
import android.test.ActivityInstrumentationTestCase2; | |
import android.test.suitebuilder.annotation.LargeTest; | |
import android.util.Log; | |
-import android.view.View; | |
import android.view.accessibility.AccessibilityEvent; | |
-import android.view.accessibility.AccessibilityInteractionClient; | |
-import android.view.accessibility.AccessibilityManager; | |
import android.view.accessibility.AccessibilityNodeInfo; | |
-import android.view.accessibility.IAccessibilityManager; | |
import com.android.frameworks.coretests.R; | |
+import com.android.internal.util.Predicate; | |
import java.util.ArrayList; | |
import java.util.LinkedList; | |
@@ -48,21 +43,21 @@ | |
*/ | |
public class InterrogationActivityTest | |
extends ActivityInstrumentationTestCase2<InterrogationActivity> { | |
- private static final boolean DEBUG = true; | |
+ private static final boolean DEBUG = false; | |
private static String LOG_TAG = "InterrogationActivityTest"; | |
- // Timeout before give up wait for the system to process an accessibility setting change. | |
- private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000; | |
- | |
// Timeout for the accessibility state of an Activity to be fully initialized. | |
- private static final int TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS = 100; | |
+ private static final int TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS = 5000; | |
- // Handle to a connection to the AccessibilityManagerService | |
- private static int sConnectionId = View.NO_ID; | |
+ // Timeout for which non getting accessibility events considers the app idle. | |
+ private static final long IDLE_EVENT_TIME_DELTA_MILLIS = 200; | |
- // The last received accessibility event | |
- private volatile AccessibilityEvent mLastAccessibilityEvent; | |
+ // Timeout in which to wait for idle device. | |
+ private static final long GLOBAL_IDLE_DETECTION_TIMEOUT_MILLIS = 1000; | |
+ | |
+ // Handle to a connection to the AccessibilityManagerService | |
+ private UiTestAutomationBridge mUiTestAutomationBridge; | |
public InterrogationActivityTest() { | |
super(InterrogationActivity.class); | |
@@ -70,16 +65,41 @@ | |
@Override | |
public void setUp() throws Exception { | |
- ensureConnection(); | |
- bringUpActivityWithInitalizedAccessbility(); | |
+ super.setUp(); | |
+ mUiTestAutomationBridge = new UiTestAutomationBridge(); | |
+ mUiTestAutomationBridge.connect(); | |
+ mUiTestAutomationBridge.waitForIdle(IDLE_EVENT_TIME_DELTA_MILLIS, | |
+ GLOBAL_IDLE_DETECTION_TIMEOUT_MILLIS); | |
+ mUiTestAutomationBridge.executeCommandAndWaitForAccessibilityEvent(new Runnable() { | |
+ // wait for the first accessibility event | |
+ @Override | |
+ public void run() { | |
+ // bring up the activity | |
+ getActivity(); | |
+ } | |
+ }, | |
+ new Predicate<AccessibilityEvent>() { | |
+ @Override | |
+ public boolean apply(AccessibilityEvent event) { | |
+ return (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | |
+ && event.getPackageName().equals(getActivity().getPackageName())); | |
+ } | |
+ }, | |
+ TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS); | |
+ } | |
+ | |
+ @Override | |
+ public void tearDown() throws Exception { | |
+ mUiTestAutomationBridge.disconnect(); | |
+ super.tearDown(); | |
} | |
@LargeTest | |
public void testFindAccessibilityNodeInfoByViewId() throws Exception { | |
final long startTimeMillis = SystemClock.uptimeMillis(); | |
try { | |
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ AccessibilityNodeInfo button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertNotNull(button); | |
assertEquals(0, button.getChildCount()); | |
@@ -125,8 +145,8 @@ | |
final long startTimeMillis = SystemClock.uptimeMillis(); | |
try { | |
// find a view by text | |
- List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfosByViewTextInActiveWindow(sConnectionId, "butto"); | |
+ List<AccessibilityNodeInfo> buttons = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfosByTextInActiveWindow("butto"); | |
assertEquals(9, buttons.size()); | |
} finally { | |
if (DEBUG) { | |
@@ -141,12 +161,9 @@ | |
public void testFindAccessibilityNodeInfoByViewTextContentDescription() throws Exception { | |
final long startTimeMillis = SystemClock.uptimeMillis(); | |
try { | |
- bringUpActivityWithInitalizedAccessbility(); | |
- | |
// find a view by text | |
- List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfosByViewTextInActiveWindow(sConnectionId, | |
- "contentDescription"); | |
+ List<AccessibilityNodeInfo> buttons = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfosByTextInActiveWindow("contentDescription"); | |
assertEquals(1, buttons.size()); | |
} finally { | |
if (DEBUG) { | |
@@ -177,8 +194,8 @@ | |
classNameAndTextList.add("android.widget.ButtonButton8"); | |
classNameAndTextList.add("android.widget.ButtonButton9"); | |
- AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.root); | |
+ AccessibilityNodeInfo root = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root); | |
assertNotNull("We must find the existing root.", root); | |
Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); | |
@@ -216,16 +233,16 @@ | |
final long startTimeMillis = SystemClock.uptimeMillis(); | |
try { | |
// find a view and make sure it is not focused | |
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ AccessibilityNodeInfo button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertFalse(button.isFocused()); | |
// focus the view | |
assertTrue(button.performAction(ACTION_FOCUS)); | |
// find the view again and make sure it is focused | |
- button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertTrue(button.isFocused()); | |
} finally { | |
if (DEBUG) { | |
@@ -240,24 +257,24 @@ | |
final long startTimeMillis = SystemClock.uptimeMillis(); | |
try { | |
// find a view and make sure it is not focused | |
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ AccessibilityNodeInfo button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertFalse(button.isFocused()); | |
// focus the view | |
assertTrue(button.performAction(ACTION_FOCUS)); | |
// find the view again and make sure it is focused | |
- button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertTrue(button.isFocused()); | |
// unfocus the view | |
assertTrue(button.performAction(ACTION_CLEAR_FOCUS)); | |
// find the view again and make sure it is not focused | |
- button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertFalse(button.isFocused()); | |
} finally { | |
if (DEBUG) { | |
@@ -273,16 +290,16 @@ | |
final long startTimeMillis = SystemClock.uptimeMillis(); | |
try { | |
// find a view and make sure it is not selected | |
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ AccessibilityNodeInfo button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertFalse(button.isSelected()); | |
// select the view | |
assertTrue(button.performAction(ACTION_SELECT)); | |
// find the view again and make sure it is selected | |
- button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertTrue(button.isSelected()); | |
} finally { | |
if (DEBUG) { | |
@@ -297,24 +314,24 @@ | |
final long startTimeMillis = SystemClock.uptimeMillis(); | |
try { | |
// find a view and make sure it is not selected | |
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ AccessibilityNodeInfo button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertFalse(button.isSelected()); | |
// select the view | |
assertTrue(button.performAction(ACTION_SELECT)); | |
// find the view again and make sure it is selected | |
- button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertTrue(button.isSelected()); | |
// unselect the view | |
assertTrue(button.performAction(ACTION_CLEAR_SELECTION)); | |
// find the view again and make sure it is not selected | |
- button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
assertFalse(button.isSelected()); | |
} finally { | |
if (DEBUG) { | |
@@ -330,23 +347,33 @@ | |
final long startTimeMillis = SystemClock.uptimeMillis(); | |
try { | |
// find a view and make sure it is not focused | |
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
- assertFalse(button.isSelected()); | |
- | |
- // focus the view | |
- assertTrue(button.performAction(ACTION_FOCUS)); | |
+ final AccessibilityNodeInfo button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
+ assertFalse(button.isFocused()); | |
- synchronized (this) { | |
- try { | |
- wait(TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS); | |
- } catch (InterruptedException ie) { | |
- /* ignore */ | |
+ AccessibilityEvent event = mUiTestAutomationBridge | |
+ .executeCommandAndWaitForAccessibilityEvent(new Runnable() { | |
+ @Override | |
+ public void run() { | |
+ // focus the view | |
+ assertTrue(button.performAction(ACTION_FOCUS)); | |
} | |
- } | |
+ }, | |
+ new Predicate<AccessibilityEvent>() { | |
+ @Override | |
+ public boolean apply(AccessibilityEvent event) { | |
+ return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED | |
+ && event.getPackageName().equals(getActivity().getPackageName()) | |
+ && event.getText().get(0).equals(button.getText())); | |
+ } | |
+ }, | |
+ TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS); | |
+ | |
+ // check the last event | |
+ assertNotNull(event); | |
// check that last event source | |
- AccessibilityNodeInfo source = mLastAccessibilityEvent.getSource(); | |
+ AccessibilityNodeInfo source = event.getSource(); | |
assertNotNull(source); | |
// bounds | |
@@ -389,8 +416,9 @@ | |
final long startTimeMillis = SystemClock.uptimeMillis(); | |
try { | |
// find a view and make sure it is not focused | |
- AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() | |
- .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); | |
+ AccessibilityNodeInfo button = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5); | |
+ assertNotNull(button); | |
AccessibilityNodeInfo parent = button.getParent(); | |
final int childCount = parent.getChildCount(); | |
for (int i = 0; i < childCount; i++) { | |
@@ -411,69 +439,33 @@ | |
} | |
} | |
- private void bringUpActivityWithInitalizedAccessbility() { | |
- mLastAccessibilityEvent = null; | |
- // bring up the activity | |
- getActivity(); | |
- | |
+ @LargeTest | |
+ public void testGetRootAccessibilityNodeInfoInActiveWindow() throws Exception { | |
final long startTimeMillis = SystemClock.uptimeMillis(); | |
- while (true) { | |
- if (mLastAccessibilityEvent != null) { | |
- final int eventType = mLastAccessibilityEvent.getEventType(); | |
- if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { | |
- return; | |
- } | |
- } | |
- final long remainingTimeMillis = TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS | |
- - (SystemClock.uptimeMillis() - startTimeMillis); | |
- if (remainingTimeMillis <= 0) { | |
- return; | |
- } | |
- synchronized (this) { | |
- try { | |
- wait(remainingTimeMillis); | |
- } catch (InterruptedException e) { | |
- /* ignore */ | |
+ try { | |
+ // get the root via the designated API | |
+ AccessibilityNodeInfo fetched = mUiTestAutomationBridge | |
+ .getRootAccessibilityNodeInfoInActiveWindow(); | |
+ assertNotNull(fetched); | |
+ | |
+ // get the root via traversal | |
+ AccessibilityNodeInfo expected = mUiTestAutomationBridge | |
+ .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root); | |
+ while (true) { | |
+ AccessibilityNodeInfo parent = expected.getParent(); | |
+ if (parent == null) { | |
+ break; | |
} | |
+ expected = parent; | |
} | |
- } | |
- } | |
+ assertNotNull(expected); | |
- private void ensureConnection() throws Exception { | |
- if (sConnectionId == View.NO_ID) { | |
- IEventListener listener = new IEventListener.Stub() { | |
- public void setConnection(IAccessibilityServiceConnection connection, | |
- int connectionId) { | |
- sConnectionId = connectionId; | |
- if (connection != null) { | |
- AccessibilityInteractionClient.getInstance().addConnection(connectionId, | |
- connection); | |
- } else { | |
- AccessibilityInteractionClient.getInstance().removeConnection(connectionId); | |
- } | |
- synchronized (this) { | |
- notifyAll(); | |
- } | |
- } | |
- | |
- public void onInterrupt() {} | |
- | |
- public void onAccessibilityEvent(AccessibilityEvent event) { | |
- mLastAccessibilityEvent = AccessibilityEvent.obtain(event); | |
- synchronized (this) { | |
- notifyAll(); | |
- } | |
- } | |
- }; | |
- | |
- AccessibilityManager accessibilityManager = | |
- AccessibilityManager.getInstance(getInstrumentation().getContext()); | |
- | |
- synchronized (this) { | |
- IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( | |
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); | |
- manager.registerEventListener(listener); | |
- wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING); | |
+ assertEquals("The node with id \"root\" should be the root.", expected, fetched); | |
+ } finally { | |
+ if (DEBUG) { | |
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; | |
+ Log.i(LOG_TAG, "testGetRootAccessibilityNodeInfoInActiveWindow: " | |
+ + elapsedTimeMillis + "ms"); | |
} | |
} | |
} | |
diff -Nur android-15/android/accessibilityservice/UiTestAutomationBridge.java android-16/android/accessibilityservice/UiTestAutomationBridge.java | |
--- android-15/android/accessibilityservice/UiTestAutomationBridge.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/accessibilityservice/UiTestAutomationBridge.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -0,0 +1,496 @@ | |
+/* | |
+ * 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 android.accessibilityservice; | |
+ | |
+import android.accessibilityservice.AccessibilityService.Callbacks; | |
+import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper; | |
+import android.content.Context; | |
+import android.os.Bundle; | |
+import android.os.HandlerThread; | |
+import android.os.Looper; | |
+import android.os.RemoteException; | |
+import android.os.ServiceManager; | |
+import android.os.SystemClock; | |
+import android.util.Log; | |
+import android.view.accessibility.AccessibilityEvent; | |
+import android.view.accessibility.AccessibilityInteractionClient; | |
+import android.view.accessibility.AccessibilityNodeInfo; | |
+import android.view.accessibility.IAccessibilityManager; | |
+ | |
+import com.android.internal.util.Predicate; | |
+ | |
+import java.util.List; | |
+import java.util.concurrent.TimeoutException; | |
+ | |
+/** | |
+ * This class represents a bridge that can be used for UI test | |
+ * automation. It is responsible for connecting to the system, | |
+ * keeping track of the last accessibility event, and exposing | |
+ * window content querying APIs. This class is designed to be | |
+ * used from both an Android application and a Java program | |
+ * run from the shell. | |
+ * | |
+ * @hide | |
+ */ | |
+public class UiTestAutomationBridge { | |
+ | |
+ private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName(); | |
+ | |
+ private static final int TIMEOUT_REGISTER_SERVICE = 5000; | |
+ | |
+ public static final int ACTIVE_WINDOW_ID = AccessibilityNodeInfo.ACTIVE_WINDOW_ID; | |
+ | |
+ public static final long ROOT_NODE_ID = AccessibilityNodeInfo.ROOT_NODE_ID; | |
+ | |
+ public static final int UNDEFINED = -1; | |
+ | |
+ private static final int FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS = | |
+ AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS | |
+ | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS | |
+ | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS; | |
+ | |
+ private final Object mLock = new Object(); | |
+ | |
+ private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID; | |
+ | |
+ private IAccessibilityServiceClientWrapper mListener; | |
+ | |
+ private AccessibilityEvent mLastEvent; | |
+ | |
+ private volatile boolean mWaitingForEventDelivery; | |
+ | |
+ private volatile boolean mUnprocessedEventAvailable; | |
+ | |
+ private HandlerThread mHandlerThread; | |
+ | |
+ /** | |
+ * Gets the last received {@link AccessibilityEvent}. | |
+ * | |
+ * @return The event. | |
+ */ | |
+ public AccessibilityEvent getLastAccessibilityEvent() { | |
+ return mLastEvent; | |
+ } | |
+ | |
+ /** | |
+ * Callback for receiving an {@link AccessibilityEvent}. | |
+ * | |
+ * <strong>Note:</strong> This method is <strong>NOT</strong> | |
+ * executed on the application main thread. The client are | |
+ * responsible for proper synchronization. | |
+ * | |
+ * @param event The received event. | |
+ */ | |
+ public void onAccessibilityEvent(AccessibilityEvent event) { | |
+ /* hook - do nothing */ | |
+ } | |
+ | |
+ /** | |
+ * Callback for requests to stop feedback. | |
+ * | |
+ * <strong>Note:</strong> This method is <strong>NOT</strong> | |
+ * executed on the application main thread. The client are | |
+ * responsible for proper synchronization. | |
+ */ | |
+ public void onInterrupt() { | |
+ /* hook - do nothing */ | |
+ } | |
+ | |
+ /** | |
+ * Connects this service. | |
+ * | |
+ * @throws IllegalStateException If already connected. | |
+ */ | |
+ public void connect() { | |
+ if (isConnected()) { | |
+ throw new IllegalStateException("Already connected."); | |
+ } | |
+ | |
+ // Serialize binder calls to a handler on a dedicated thread | |
+ // different from the main since we expose APIs that block | |
+ // the main thread waiting for a result the deliver of which | |
+ // on the main thread will prevent that thread from waking up. | |
+ // The serialization is needed also to ensure that events are | |
+ // examined in delivery order. Otherwise, a fair locking | |
+ // is needed for making sure the binder calls are interleaved | |
+ // with check for the expected event and also to make sure the | |
+ // binder threads are allowed to proceed in the received order. | |
+ mHandlerThread = new HandlerThread("UiTestAutomationBridge"); | |
+ mHandlerThread.setDaemon(true); | |
+ mHandlerThread.start(); | |
+ Looper looper = mHandlerThread.getLooper(); | |
+ | |
+ mListener = new IAccessibilityServiceClientWrapper(null, looper, new Callbacks() { | |
+ @Override | |
+ public void onServiceConnected() { | |
+ /* do nothing */ | |
+ } | |
+ | |
+ @Override | |
+ public void onInterrupt() { | |
+ UiTestAutomationBridge.this.onInterrupt(); | |
+ } | |
+ | |
+ @Override | |
+ public void onAccessibilityEvent(AccessibilityEvent event) { | |
+ synchronized (mLock) { | |
+ while (true) { | |
+ mLastEvent = AccessibilityEvent.obtain(event); | |
+ if (!mWaitingForEventDelivery) { | |
+ mLock.notifyAll(); | |
+ break; | |
+ } | |
+ if (!mUnprocessedEventAvailable) { | |
+ mUnprocessedEventAvailable = true; | |
+ mLock.notifyAll(); | |
+ break; | |
+ } | |
+ try { | |
+ mLock.wait(); | |
+ } catch (InterruptedException ie) { | |
+ /* ignore */ | |
+ } | |
+ } | |
+ } | |
+ UiTestAutomationBridge.this.onAccessibilityEvent(event); | |
+ } | |
+ | |
+ @Override | |
+ public void onSetConnectionId(int connectionId) { | |
+ synchronized (mLock) { | |
+ mConnectionId = connectionId; | |
+ mLock.notifyAll(); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public boolean onGesture(int gestureId) { | |
+ return false; | |
+ } | |
+ }); | |
+ | |
+ final IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( | |
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); | |
+ | |
+ final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); | |
+ info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; | |
+ info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; | |
+ info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; | |
+ | |
+ try { | |
+ manager.registerUiTestAutomationService(mListener, info); | |
+ } catch (RemoteException re) { | |
+ throw new IllegalStateException("Cound not register UiAutomationService.", re); | |
+ } | |
+ | |
+ synchronized (mLock) { | |
+ final long startTimeMillis = SystemClock.uptimeMillis(); | |
+ while (true) { | |
+ if (isConnected()) { | |
+ return; | |
+ } | |
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; | |
+ final long remainingTimeMillis = TIMEOUT_REGISTER_SERVICE - elapsedTimeMillis; | |
+ if (remainingTimeMillis <= 0) { | |
+ throw new IllegalStateException("Cound not register UiAutomationService."); | |
+ } | |
+ try { | |
+ mLock.wait(remainingTimeMillis); | |
+ } catch (InterruptedException ie) { | |
+ /* ignore */ | |
+ } | |
+ } | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Disconnects this service. | |
+ * | |
+ * @throws IllegalStateException If already disconnected. | |
+ */ | |
+ public void disconnect() { | |
+ if (!isConnected()) { | |
+ throw new IllegalStateException("Already disconnected."); | |
+ } | |
+ | |
+ mHandlerThread.quit(); | |
+ | |
+ IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( | |
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); | |
+ | |
+ try { | |
+ manager.unregisterUiTestAutomationService(mListener); | |
+ } catch (RemoteException re) { | |
+ Log.e(LOG_TAG, "Error while unregistering UiTestAutomationService", re); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Gets whether this service is connected. | |
+ * | |
+ * @return True if connected. | |
+ */ | |
+ public boolean isConnected() { | |
+ return (mConnectionId != AccessibilityInteractionClient.NO_ID); | |
+ } | |
+ | |
+ /** | |
+ * Executes a command and waits for a specific accessibility event type up | |
+ * to a given timeout. | |
+ * | |
+ * @param command The command to execute before starting to wait for the event. | |
+ * @param predicate Predicate for recognizing the awaited event. | |
+ * @param timeoutMillis The max wait time in milliseconds. | |
+ */ | |
+ public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command, | |
+ Predicate<AccessibilityEvent> predicate, long timeoutMillis) | |
+ throws TimeoutException, Exception { | |
+ // TODO: This is broken - remove from here when finalizing this as public APIs. | |
+ synchronized (mLock) { | |
+ // Prepare to wait for an event. | |
+ mWaitingForEventDelivery = true; | |
+ mUnprocessedEventAvailable = false; | |
+ if (mLastEvent != null) { | |
+ mLastEvent.recycle(); | |
+ mLastEvent = null; | |
+ } | |
+ // Execute the command. | |
+ command.run(); | |
+ // Wait for the event. | |
+ final long startTimeMillis = SystemClock.uptimeMillis(); | |
+ while (true) { | |
+ // If the expected event is received, that's it. | |
+ if ((mUnprocessedEventAvailable && predicate.apply(mLastEvent))) { | |
+ mWaitingForEventDelivery = false; | |
+ mUnprocessedEventAvailable = false; | |
+ mLock.notifyAll(); | |
+ return mLastEvent; | |
+ } | |
+ // Ask for another event. | |
+ mWaitingForEventDelivery = true; | |
+ mUnprocessedEventAvailable = false; | |
+ mLock.notifyAll(); | |
+ // Check if timed out and if not wait. | |
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; | |
+ final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; | |
+ if (remainingTimeMillis <= 0) { | |
+ mWaitingForEventDelivery = false; | |
+ mUnprocessedEventAvailable = false; | |
+ mLock.notifyAll(); | |
+ throw new TimeoutException("Expacted event not received within: " | |
+ + timeoutMillis + " ms."); | |
+ } | |
+ try { | |
+ mLock.wait(remainingTimeMillis); | |
+ } catch (InterruptedException ie) { | |
+ /* ignore */ | |
+ } | |
+ } | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Waits for the accessibility event stream to become idle, which is not to | |
+ * have received a new accessibility event within <code>idleTimeout</code>, | |
+ * and do so within a maximal global timeout as specified by | |
+ * <code>globalTimeout</code>. | |
+ * | |
+ * @param idleTimeout The timeout between two event to consider the device idle. | |
+ * @param globalTimeout The maximal global timeout in which to wait for idle. | |
+ */ | |
+ public void waitForIdle(long idleTimeout, long globalTimeout) { | |
+ final long startTimeMillis = SystemClock.uptimeMillis(); | |
+ long lastEventTime = (mLastEvent != null) | |
+ ? mLastEvent.getEventTime() : SystemClock.uptimeMillis(); | |
+ synchronized (mLock) { | |
+ while (true) { | |
+ final long currentTimeMillis = SystemClock.uptimeMillis(); | |
+ final long sinceLastEventTimeMillis = currentTimeMillis - lastEventTime; | |
+ if (sinceLastEventTimeMillis > idleTimeout) { | |
+ return; | |
+ } | |
+ if (mLastEvent != null) { | |
+ lastEventTime = mLastEvent.getEventTime(); | |
+ } | |
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; | |
+ final long remainingTimeMillis = globalTimeout - elapsedTimeMillis; | |
+ if (remainingTimeMillis <= 0) { | |
+ return; | |
+ } | |
+ try { | |
+ mLock.wait(idleTimeout); | |
+ } catch (InterruptedException e) { | |
+ /* ignore */ | |
+ } | |
+ } | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Finds an {@link AccessibilityNodeInfo} by accessibility id in the active | |
+ * window. The search is performed from the root node. | |
+ * | |
+ * @param accessibilityNodeId A unique view id or virtual descendant id for | |
+ * which to search. | |
+ * @return The current window scale, where zero means a failure. | |
+ */ | |
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityIdInActiveWindow( | |
+ long accessibilityNodeId) { | |
+ return findAccessibilityNodeInfoByAccessibilityId(ACTIVE_WINDOW_ID, accessibilityNodeId); | |
+ } | |
+ | |
+ /** | |
+ * Finds an {@link AccessibilityNodeInfo} by accessibility id. | |
+ * | |
+ * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} to query | |
+ * the currently active window. | |
+ * @param accessibilityNodeId A unique view id or virtual descendant id for | |
+ * which to search. | |
+ * @return The current window scale, where zero means a failure. | |
+ */ | |
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId( | |
+ int accessibilityWindowId, long accessibilityNodeId) { | |
+ // Cache the id to avoid locking | |
+ final int connectionId = mConnectionId; | |
+ ensureValidConnection(connectionId); | |
+ return AccessibilityInteractionClient.getInstance() | |
+ .findAccessibilityNodeInfoByAccessibilityId(mConnectionId, | |
+ accessibilityWindowId, accessibilityNodeId, | |
+ FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS); | |
+ } | |
+ | |
+ /** | |
+ * Finds an {@link AccessibilityNodeInfo} by View id in the active | |
+ * window. The search is performed from the root node. | |
+ * | |
+ * @param viewId The id of a View. | |
+ * @return The current window scale, where zero means a failure. | |
+ */ | |
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) { | |
+ return findAccessibilityNodeInfoByViewId(ACTIVE_WINDOW_ID, ROOT_NODE_ID, viewId); | |
+ } | |
+ | |
+ /** | |
+ * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in | |
+ * the window whose id is specified and starts from the node whose accessibility | |
+ * id is specified. | |
+ * | |
+ * @param accessibilityWindowId A unique window id. Use | |
+ * {@link #ACTIVE_WINDOW_ID} to query the currently active window. | |
+ * @param accessibilityNodeId A unique view id or virtual descendant id from | |
+ * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root. | |
+ * @param viewId The id of a View. | |
+ * @return The current window scale, where zero means a failure. | |
+ */ | |
+ public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId, | |
+ long accessibilityNodeId, int viewId) { | |
+ // Cache the id to avoid locking | |
+ final int connectionId = mConnectionId; | |
+ ensureValidConnection(connectionId); | |
+ return AccessibilityInteractionClient.getInstance() | |
+ .findAccessibilityNodeInfoByViewId(connectionId, accessibilityWindowId, | |
+ accessibilityNodeId, viewId); | |
+ } | |
+ | |
+ /** | |
+ * Finds {@link AccessibilityNodeInfo}s by View text in the active | |
+ * window. The search is performed from the root node. | |
+ * | |
+ * @param text The searched text. | |
+ * @return The current window scale, where zero means a failure. | |
+ */ | |
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(String text) { | |
+ return findAccessibilityNodeInfosByText(ACTIVE_WINDOW_ID, ROOT_NODE_ID, text); | |
+ } | |
+ | |
+ /** | |
+ * Finds {@link AccessibilityNodeInfo}s by View text. The match is case | |
+ * insensitive containment. The search is performed in the window whose | |
+ * id is specified and starts from the node whose accessibility id is | |
+ * specified. | |
+ * | |
+ * @param accessibilityWindowId A unique window id. Use | |
+ * {@link #ACTIVE_WINDOW_ID} to query the currently active window. | |
+ * @param accessibilityNodeId A unique view id or virtual descendant id from | |
+ * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root. | |
+ * @param text The searched text. | |
+ * @return The current window scale, where zero means a failure. | |
+ */ | |
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int accessibilityWindowId, | |
+ long accessibilityNodeId, String text) { | |
+ // Cache the id to avoid locking | |
+ final int connectionId = mConnectionId; | |
+ ensureValidConnection(connectionId); | |
+ return AccessibilityInteractionClient.getInstance() | |
+ .findAccessibilityNodeInfosByText(connectionId, accessibilityWindowId, | |
+ accessibilityNodeId, text); | |
+ } | |
+ | |
+ /** | |
+ * Performs an accessibility action on an {@link AccessibilityNodeInfo} | |
+ * in the active window. | |
+ * | |
+ * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id). | |
+ * @param action The action to perform. | |
+ * @param arguments Optional action arguments. | |
+ * @return Whether the action was performed. | |
+ */ | |
+ public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId, int action, | |
+ Bundle arguments) { | |
+ return performAccessibilityAction(ACTIVE_WINDOW_ID, accessibilityNodeId, action, arguments); | |
+ } | |
+ | |
+ /** | |
+ * Performs an accessibility action on an {@link AccessibilityNodeInfo}. | |
+ * | |
+ * @param accessibilityWindowId A unique window id. Use | |
+ * {@link #ACTIVE_WINDOW_ID} to query the currently active window. | |
+ * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id). | |
+ * @param action The action to perform. | |
+ * @param arguments Optional action arguments. | |
+ * @return Whether the action was performed. | |
+ */ | |
+ public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, | |
+ int action, Bundle arguments) { | |
+ // Cache the id to avoid locking | |
+ final int connectionId = mConnectionId; | |
+ ensureValidConnection(connectionId); | |
+ return AccessibilityInteractionClient.getInstance().performAccessibilityAction(connectionId, | |
+ accessibilityWindowId, accessibilityNodeId, action, arguments); | |
+ } | |
+ | |
+ /** | |
+ * Gets the root {@link AccessibilityNodeInfo} in the active window. | |
+ * | |
+ * @return The root info. | |
+ */ | |
+ public AccessibilityNodeInfo getRootAccessibilityNodeInfoInActiveWindow() { | |
+ // Cache the id to avoid locking | |
+ final int connectionId = mConnectionId; | |
+ ensureValidConnection(connectionId); | |
+ return AccessibilityInteractionClient.getInstance() | |
+ .findAccessibilityNodeInfoByAccessibilityId(connectionId, ACTIVE_WINDOW_ID, | |
+ ROOT_NODE_ID, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS); | |
+ } | |
+ | |
+ private void ensureValidConnection(int connectionId) { | |
+ if (connectionId == UNDEFINED) { | |
+ throw new IllegalStateException("UiAutomationService not connected." | |
+ + " Did you call #register()?"); | |
+ } | |
+ } | |
+} | |
diff -Nur android-15/android/accounts/AbstractAccountAuthenticator.java android-16/android/accounts/AbstractAccountAuthenticator.java | |
--- android-15/android/accounts/AbstractAccountAuthenticator.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/accounts/AbstractAccountAuthenticator.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -59,7 +59,7 @@ | |
* "Account & Sync" settings page and one user of the android:smallIcon is the Contact Application's | |
* tab panels. | |
* <p> | |
- * The preferences attribute points to an PreferenceScreen xml hierarchy that contains | |
+ * The preferences attribute points to a PreferenceScreen xml hierarchy that contains | |
* a list of PreferenceScreens that can be invoked to manage the authenticator. An example is: | |
* <pre> | |
* <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> | |
diff -Nur android-15/android/accounts/AccountAndUser.java android-16/android/accounts/AccountAndUser.java | |
--- android-15/android/accounts/AccountAndUser.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/accounts/AccountAndUser.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -0,0 +1,49 @@ | |
+/* | |
+ * 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 android.accounts; | |
+ | |
+/** | |
+ * Used to store the Account and the UserId this account is associated with. | |
+ * | |
+ * @hide | |
+ */ | |
+public class AccountAndUser { | |
+ public Account account; | |
+ public int userId; | |
+ | |
+ public AccountAndUser(Account account, int userId) { | |
+ this.account = account; | |
+ this.userId = userId; | |
+ } | |
+ | |
+ public boolean equals(Object o) { | |
+ if (this == o) return true; | |
+ if (!(o instanceof AccountAndUser)) return false; | |
+ final AccountAndUser other = (AccountAndUser) o; | |
+ return this.account.equals(other.account) | |
+ && this.userId == other.userId; | |
+ } | |
+ | |
+ @Override | |
+ public int hashCode() { | |
+ return account.hashCode() + userId; | |
+ } | |
+ | |
+ public String toString() { | |
+ return account.toString() + " u" + userId; | |
+ } | |
+} | |
diff -Nur android-15/android/accounts/AccountManager.java android-16/android/accounts/AccountManager.java | |
--- android-15/android/accounts/AccountManager.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/accounts/AccountManager.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -405,6 +405,55 @@ | |
} | |
/** | |
+ * Change whether or not an app (identified by its uid) is allowed to retrieve an authToken | |
+ * for an account. | |
+ * <p> | |
+ * This is only meant to be used by system activities and is not in the SDK. | |
+ * @param account The account whose permissions are being modified | |
+ * @param authTokenType The type of token whose permissions are being modified | |
+ * @param uid The uid that identifies the app which is being granted or revoked permission. | |
+ * @param value true is permission is being granted, false for revoked | |
+ * @hide | |
+ */ | |
+ public void updateAppPermission(Account account, String authTokenType, int uid, boolean value) { | |
+ try { | |
+ mService.updateAppPermission(account, authTokenType, uid, value); | |
+ } catch (RemoteException e) { | |
+ // won't ever happen | |
+ throw new RuntimeException(e); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Get the user-friendly label associated with an authenticator's auth token. | |
+ * @param accountType the type of the authenticator. must not be null. | |
+ * @param authTokenType the token type. must not be null. | |
+ * @param callback callback to invoke when the result is available. may be null. | |
+ * @param handler the handler on which to invoke the callback, or null for the main thread | |
+ * @return a future containing the label string | |
+ * @hide | |
+ */ | |
+ public AccountManagerFuture<String> getAuthTokenLabel( | |
+ final String accountType, final String authTokenType, | |
+ AccountManagerCallback<String> callback, Handler handler) { | |
+ if (accountType == null) throw new IllegalArgumentException("accountType is null"); | |
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); | |
+ return new Future2Task<String>(handler, callback) { | |
+ public void doWork() throws RemoteException { | |
+ mService.getAuthTokenLabel(mResponse, accountType, authTokenType); | |
+ } | |
+ | |
+ @Override | |
+ public String bundleToResult(Bundle bundle) throws AuthenticatorException { | |
+ if (!bundle.containsKey(KEY_AUTH_TOKEN_LABEL)) { | |
+ throw new AuthenticatorException("no result in response"); | |
+ } | |
+ return bundle.getString(KEY_AUTH_TOKEN_LABEL); | |
+ } | |
+ }.start(); | |
+ } | |
+ | |
+ /** | |
* Finds out whether a particular account has all the specified features. | |
* Account features are authenticator-specific string tokens identifying | |
* boolean account properties. For example, features are used to tell | |
@@ -1867,7 +1916,8 @@ | |
* | |
* <p>It is safe to call this method from the main thread. | |
* | |
- * <p>No permission is required to call this method. | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#GET_ACCOUNTS}. | |
* | |
* @param listener The listener to send notifications to | |
* @param handler {@link Handler} identifying the thread to use | |
diff -Nur android-15/android/accounts/AccountManagerService.java android-16/android/accounts/AccountManagerService.java | |
--- android-15/android/accounts/AccountManagerService.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/accounts/AccountManagerService.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -18,6 +18,7 @@ | |
import android.Manifest; | |
import android.app.ActivityManager; | |
+import android.app.AppGlobals; | |
import android.app.Notification; | |
import android.app.NotificationManager; | |
import android.app.PendingIntent; | |
@@ -33,6 +34,7 @@ | |
import android.content.pm.PackageManager; | |
import android.content.pm.RegisteredServicesCache; | |
import android.content.pm.RegisteredServicesCacheListener; | |
+import android.content.pm.UserInfo; | |
import android.content.res.Resources; | |
import android.database.Cursor; | |
import android.database.DatabaseUtils; | |
@@ -48,11 +50,14 @@ | |
import android.os.Message; | |
import android.os.RemoteException; | |
import android.os.SystemClock; | |
+import android.os.UserId; | |
import android.text.TextUtils; | |
import android.util.Log; | |
import android.util.Pair; | |
+import android.util.SparseArray; | |
import com.android.internal.R; | |
+import com.android.internal.util.IndentingPrintWriter; | |
import java.io.File; | |
import java.io.FileDescriptor; | |
@@ -62,6 +67,8 @@ | |
import java.util.Collection; | |
import java.util.HashMap; | |
import java.util.LinkedHashMap; | |
+import java.util.List; | |
+import java.util.Map; | |
import java.util.concurrent.atomic.AtomicInteger; | |
import java.util.concurrent.atomic.AtomicReference; | |
@@ -93,7 +100,6 @@ | |
private static final int MESSAGE_TIMED_OUT = 3; | |
private final IAccountAuthenticatorCache mAuthenticatorCache; | |
- private final DatabaseHelper mOpenHelper; | |
private static final String TABLE_ACCOUNTS = "accounts"; | |
private static final String ACCOUNTS_ID = "_id"; | |
@@ -147,14 +153,37 @@ | |
private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>(); | |
private final AtomicInteger mNotificationIds = new AtomicInteger(1); | |
- private final HashMap<Pair<Pair<Account, String>, Integer>, Integer> | |
- mCredentialsPermissionNotificationIds = | |
- new HashMap<Pair<Pair<Account, String>, Integer>, Integer>(); | |
- private final HashMap<Account, Integer> mSigninRequiredNotificationIds = | |
- new HashMap<Account, Integer>(); | |
+ static class UserAccounts { | |
+ private final int userId; | |
+ private final DatabaseHelper openHelper; | |
+ private final HashMap<Pair<Pair<Account, String>, Integer>, Integer> | |
+ credentialsPermissionNotificationIds = | |
+ new HashMap<Pair<Pair<Account, String>, Integer>, Integer>(); | |
+ private final HashMap<Account, Integer> signinRequiredNotificationIds = | |
+ new HashMap<Account, Integer>(); | |
+ private final Object cacheLock = new Object(); | |
+ /** protected by the {@link #cacheLock} */ | |
+ private final HashMap<String, Account[]> accountCache = | |
+ new LinkedHashMap<String, Account[]>(); | |
+ /** protected by the {@link #cacheLock} */ | |
+ private HashMap<Account, HashMap<String, String>> userDataCache = | |
+ new HashMap<Account, HashMap<String, String>>(); | |
+ /** protected by the {@link #cacheLock} */ | |
+ private HashMap<Account, HashMap<String, String>> authTokenCache = | |
+ new HashMap<Account, HashMap<String, String>>(); | |
+ | |
+ UserAccounts(Context context, int userId) { | |
+ this.userId = userId; | |
+ synchronized (cacheLock) { | |
+ openHelper = new DatabaseHelper(context, userId); | |
+ } | |
+ } | |
+ } | |
+ | |
+ private final SparseArray<UserAccounts> mUsers = new SparseArray<UserAccounts>(); | |
+ | |
private static AtomicReference<AccountManagerService> sThis = | |
new AtomicReference<AccountManagerService>(); | |
- | |
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{}; | |
static { | |
@@ -162,15 +191,6 @@ | |
ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); | |
} | |
- private final Object mCacheLock = new Object(); | |
- /** protected by the {@link #mCacheLock} */ | |
- private final HashMap<String, Account[]> mAccountCache = new HashMap<String, Account[]>(); | |
- /** protected by the {@link #mCacheLock} */ | |
- private HashMap<Account, HashMap<String, String>> mUserDataCache = | |
- new HashMap<Account, HashMap<String, String>>(); | |
- /** protected by the {@link #mCacheLock} */ | |
- private HashMap<Account, HashMap<String, String>> mAuthTokenCache = | |
- new HashMap<Account, HashMap<String, String>>(); | |
/** | |
* This should only be called by system code. One should only call this after the service | |
@@ -191,10 +211,6 @@ | |
mContext = context; | |
mPackageManager = packageManager; | |
- synchronized (mCacheLock) { | |
- mOpenHelper = new DatabaseHelper(mContext); | |
- } | |
- | |
mMessageThread = new HandlerThread("AccountManagerService"); | |
mMessageThread.start(); | |
mMessageHandler = new MessageHandler(mMessageThread.getLooper()); | |
@@ -204,23 +220,52 @@ | |
sThis.set(this); | |
+ UserAccounts accounts = initUser(0); | |
+ | |
IntentFilter intentFilter = new IntentFilter(); | |
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); | |
intentFilter.addDataScheme("package"); | |
mContext.registerReceiver(new BroadcastReceiver() { | |
@Override | |
public void onReceive(Context context1, Intent intent) { | |
- purgeOldGrants(); | |
+ purgeOldGrantsAll(); | |
} | |
}, intentFilter); | |
- purgeOldGrants(); | |
- validateAccountsAndPopulateCache(); | |
+ IntentFilter userFilter = new IntentFilter(); | |
+ userFilter.addAction(Intent.ACTION_USER_REMOVED); | |
+ mContext.registerReceiver(new BroadcastReceiver() { | |
+ @Override | |
+ public void onReceive(Context context, Intent intent) { | |
+ onUserRemoved(intent); | |
+ } | |
+ }, userFilter); | |
+ } | |
+ | |
+ private UserAccounts initUser(int userId) { | |
+ synchronized (mUsers) { | |
+ UserAccounts accounts = mUsers.get(userId); | |
+ if (accounts == null) { | |
+ accounts = new UserAccounts(mContext, userId); | |
+ mUsers.append(userId, accounts); | |
+ purgeOldGrants(accounts); | |
+ validateAccountsAndPopulateCache(accounts); | |
+ } | |
+ return accounts; | |
+ } | |
+ } | |
+ | |
+ private void purgeOldGrantsAll() { | |
+ synchronized (mUsers) { | |
+ for (int i = 0; i < mUsers.size(); i++) { | |
+ purgeOldGrants(mUsers.valueAt(i)); | |
+ } | |
+ } | |
} | |
- private void purgeOldGrants() { | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); | |
+ private void purgeOldGrants(UserAccounts accounts) { | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); | |
final Cursor cursor = db.query(TABLE_GRANTS, | |
new String[]{GRANTS_GRANTEE_UID}, | |
null, null, GRANTS_GRANTEE_UID, null, null); | |
@@ -242,17 +287,17 @@ | |
} | |
} | |
- private void validateAccountsAndPopulateCache() { | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); | |
+ private void validateAccountsAndPopulateCache(UserAccounts accounts) { | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); | |
boolean accountDeleted = false; | |
Cursor cursor = db.query(TABLE_ACCOUNTS, | |
new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME}, | |
null, null, null, null, null); | |
try { | |
- mAccountCache.clear(); | |
+ accounts.accountCache.clear(); | |
final HashMap<String, ArrayList<String>> accountNamesByType = | |
- new HashMap<String, ArrayList<String>>(); | |
+ new LinkedHashMap<String, ArrayList<String>>(); | |
while (cursor.moveToNext()) { | |
final long accountId = cursor.getLong(0); | |
final String accountType = cursor.getString(1); | |
@@ -264,8 +309,8 @@ | |
db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null); | |
accountDeleted = true; | |
final Account account = new Account(accountName, accountType); | |
- mUserDataCache.remove(account); | |
- mAuthTokenCache.remove(account); | |
+ accounts.userDataCache.remove(account); | |
+ accounts.authTokenCache.remove(account); | |
} else { | |
ArrayList<String> accountNames = accountNamesByType.get(accountType); | |
if (accountNames == null) { | |
@@ -275,7 +320,7 @@ | |
accountNames.add(accountName); | |
} | |
} | |
- for (HashMap.Entry<String, ArrayList<String>> cur | |
+ for (Map.Entry<String, ArrayList<String>> cur | |
: accountNamesByType.entrySet()) { | |
final String accountType = cur.getKey(); | |
final ArrayList<String> accountNames = cur.getValue(); | |
@@ -285,19 +330,73 @@ | |
accountsForType[i] = new Account(accountName, accountType); | |
++i; | |
} | |
- mAccountCache.put(accountType, accountsForType); | |
+ accounts.accountCache.put(accountType, accountsForType); | |
} | |
} finally { | |
cursor.close(); | |
if (accountDeleted) { | |
- sendAccountsChangedBroadcast(); | |
+ sendAccountsChangedBroadcast(accounts.userId); | |
} | |
} | |
} | |
} | |
+ private UserAccounts getUserAccountsForCaller() { | |
+ return getUserAccounts(UserId.getCallingUserId()); | |
+ } | |
+ | |
+ protected UserAccounts getUserAccounts(int userId) { | |
+ synchronized (mUsers) { | |
+ UserAccounts accounts = mUsers.get(userId); | |
+ if (accounts == null) { | |
+ accounts = initUser(userId); | |
+ mUsers.append(userId, accounts); | |
+ } | |
+ return accounts; | |
+ } | |
+ } | |
+ | |
+ private void onUserRemoved(Intent intent) { | |
+ int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1); | |
+ if (userId < 1) return; | |
+ | |
+ UserAccounts accounts; | |
+ synchronized (mUsers) { | |
+ accounts = mUsers.get(userId); | |
+ mUsers.remove(userId); | |
+ } | |
+ if (accounts == null) { | |
+ File dbFile = new File(getDatabaseName(userId)); | |
+ dbFile.delete(); | |
+ return; | |
+ } | |
+ | |
+ synchronized (accounts.cacheLock) { | |
+ accounts.openHelper.close(); | |
+ File dbFile = new File(getDatabaseName(userId)); | |
+ dbFile.delete(); | |
+ } | |
+ } | |
+ | |
+ private List<UserInfo> getAllUsers() { | |
+ try { | |
+ return AppGlobals.getPackageManager().getUsers(); | |
+ } catch (RemoteException re) { | |
+ // Local to system process, shouldn't happen | |
+ } | |
+ return null; | |
+ } | |
+ | |
public void onServiceChanged(AuthenticatorDescription desc, boolean removed) { | |
- validateAccountsAndPopulateCache(); | |
+ // Validate accounts for all users | |
+ List<UserInfo> users = getAllUsers(); | |
+ if (users == null) { | |
+ validateAccountsAndPopulateCache(getUserAccountsForCaller()); | |
+ } else { | |
+ for (UserInfo user : users) { | |
+ validateAccountsAndPopulateCache(getUserAccounts(user.id)); | |
+ } | |
+ } | |
} | |
public String getPassword(Account account) { | |
@@ -309,21 +408,22 @@ | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
checkAuthenticateAccountsPermission(account); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- return readPasswordInternal(account); | |
+ return readPasswordInternal(accounts, account); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
} | |
- private String readPasswordInternal(Account account) { | |
+ private String readPasswordInternal(UserAccounts accounts, Account account) { | |
if (account == null) { | |
return null; | |
} | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); | |
Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD}, | |
ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", | |
new String[]{account.name, account.type}, null, null, null); | |
@@ -348,9 +448,10 @@ | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
if (key == null) throw new IllegalArgumentException("key is null"); | |
checkAuthenticateAccountsPermission(account); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- return readUserDataInternal(account, key); | |
+ return readUserDataInternal(accounts, account, key); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
@@ -389,21 +490,23 @@ | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
checkAuthenticateAccountsPermission(account); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
// fails if the account already exists | |
long identityToken = clearCallingIdentity(); | |
try { | |
- return addAccountInternal(account, password, extras); | |
+ return addAccountInternal(accounts, account, password, extras); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
} | |
- private boolean addAccountInternal(Account account, String password, Bundle extras) { | |
+ private boolean addAccountInternal(UserAccounts accounts, Account account, String password, | |
+ Bundle extras) { | |
if (account == null) { | |
return false; | |
} | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); | |
db.beginTransaction(); | |
try { | |
long numMatches = DatabaseUtils.longForQuery(db, | |
@@ -436,11 +539,11 @@ | |
} | |
} | |
db.setTransactionSuccessful(); | |
- insertAccountIntoCacheLocked(account); | |
+ insertAccountIntoCacheLocked(accounts, account); | |
} finally { | |
db.endTransaction(); | |
} | |
- sendAccountsChangedBroadcast(); | |
+ sendAccountsChangedBroadcast(accounts.userId); | |
return true; | |
} | |
} | |
@@ -466,9 +569,10 @@ | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
if (features == null) throw new IllegalArgumentException("features is null"); | |
checkReadAccountsPermission(); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- new TestFeaturesSession(response, account, features).bind(); | |
+ new TestFeaturesSession(accounts, response, account, features).bind(); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
@@ -478,9 +582,9 @@ | |
private final String[] mFeatures; | |
private final Account mAccount; | |
- public TestFeaturesSession(IAccountManagerResponse response, | |
+ public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response, | |
Account account, String[] features) { | |
- super(response, account.type, false /* expectActivityLaunch */, | |
+ super(accounts, response, account.type, false /* expectActivityLaunch */, | |
true /* stripAuthTokenFromResult */); | |
mFeatures = features; | |
mAccount = account; | |
@@ -536,21 +640,22 @@ | |
if (response == null) throw new IllegalArgumentException("response is null"); | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
checkManageAccountsPermission(); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
- cancelNotification(getSigninRequiredNotificationId(account)); | |
- synchronized(mCredentialsPermissionNotificationIds) { | |
+ cancelNotification(getSigninRequiredNotificationId(accounts, account)); | |
+ synchronized(accounts.credentialsPermissionNotificationIds) { | |
for (Pair<Pair<Account, String>, Integer> pair: | |
- mCredentialsPermissionNotificationIds.keySet()) { | |
+ accounts.credentialsPermissionNotificationIds.keySet()) { | |
if (account.equals(pair.first.first)) { | |
- int id = mCredentialsPermissionNotificationIds.get(pair); | |
+ int id = accounts.credentialsPermissionNotificationIds.get(pair); | |
cancelNotification(id); | |
} | |
} | |
} | |
try { | |
- new RemoveAccountSession(response, account).bind(); | |
+ new RemoveAccountSession(accounts, response, account).bind(); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
@@ -558,8 +663,9 @@ | |
private class RemoveAccountSession extends Session { | |
final Account mAccount; | |
- public RemoveAccountSession(IAccountManagerResponse response, Account account) { | |
- super(response, account.type, false /* expectActivityLaunch */, | |
+ public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response, | |
+ Account account) { | |
+ super(accounts, response, account.type, false /* expectActivityLaunch */, | |
true /* stripAuthTokenFromResult */); | |
mAccount = account; | |
} | |
@@ -578,7 +684,7 @@ | |
&& !result.containsKey(AccountManager.KEY_INTENT)) { | |
final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); | |
if (removalAllowed) { | |
- removeAccountInternal(mAccount); | |
+ removeAccountInternal(mAccounts, mAccount); | |
} | |
IAccountManagerResponse response = getResponseAndClose(); | |
if (response != null) { | |
@@ -599,13 +705,18 @@ | |
} | |
} | |
+ /* For testing */ | |
protected void removeAccountInternal(Account account) { | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); | |
+ removeAccountInternal(getUserAccountsForCaller(), account); | |
+ } | |
+ | |
+ private void removeAccountInternal(UserAccounts accounts, Account account) { | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); | |
db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", | |
new String[]{account.name, account.type}); | |
- removeAccountFromCacheLocked(account); | |
- sendAccountsChangedBroadcast(); | |
+ removeAccountFromCacheLocked(accounts, account); | |
+ sendAccountsChangedBroadcast(accounts.userId); | |
} | |
} | |
@@ -618,13 +729,14 @@ | |
if (accountType == null) throw new IllegalArgumentException("accountType is null"); | |
if (authToken == null) throw new IllegalArgumentException("authToken is null"); | |
checkManageAccountsOrUseCredentialsPermissions(); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); | |
db.beginTransaction(); | |
try { | |
- invalidateAuthTokenLocked(db, accountType, authToken); | |
+ invalidateAuthTokenLocked(accounts, db, accountType, authToken); | |
db.setTransactionSuccessful(); | |
} finally { | |
db.endTransaction(); | |
@@ -635,7 +747,8 @@ | |
} | |
} | |
- private void invalidateAuthTokenLocked(SQLiteDatabase db, String accountType, String authToken) { | |
+ private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db, | |
+ String accountType, String authToken) { | |
if (authToken == null || accountType == null) { | |
return; | |
} | |
@@ -656,7 +769,7 @@ | |
String accountName = cursor.getString(1); | |
String authTokenType = cursor.getString(2); | |
db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null); | |
- writeAuthTokenIntoCacheLocked(db, new Account(accountName, accountType), | |
+ writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType), | |
authTokenType, null); | |
} | |
} finally { | |
@@ -664,13 +777,14 @@ | |
} | |
} | |
- private boolean saveAuthTokenToDatabase(Account account, String type, String authToken) { | |
+ private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type, | |
+ String authToken) { | |
if (account == null || type == null) { | |
return false; | |
} | |
- cancelNotification(getSigninRequiredNotificationId(account)); | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); | |
+ cancelNotification(getSigninRequiredNotificationId(accounts, account)); | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); | |
db.beginTransaction(); | |
try { | |
long accountId = getAccountIdLocked(db, account); | |
@@ -686,7 +800,7 @@ | |
values.put(AUTHTOKENS_AUTHTOKEN, authToken); | |
if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) { | |
db.setTransactionSuccessful(); | |
- writeAuthTokenIntoCacheLocked(db, account, type, authToken); | |
+ writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken); | |
return true; | |
} | |
return false; | |
@@ -706,9 +820,10 @@ | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); | |
checkAuthenticateAccountsPermission(account); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- return readAuthTokenInternal(account, authTokenType); | |
+ return readAuthTokenInternal(accounts, account, authTokenType); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
@@ -724,9 +839,10 @@ | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); | |
checkAuthenticateAccountsPermission(account); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- saveAuthTokenToDatabase(account, authTokenType, authToken); | |
+ saveAuthTokenToDatabase(accounts, account, authTokenType, authToken); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
@@ -740,20 +856,21 @@ | |
} | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
checkAuthenticateAccountsPermission(account); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- setPasswordInternal(account, password); | |
+ setPasswordInternal(accounts, account, password); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
} | |
- private void setPasswordInternal(Account account, String password) { | |
+ private void setPasswordInternal(UserAccounts accounts, Account account, String password) { | |
if (account == null) { | |
return; | |
} | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); | |
db.beginTransaction(); | |
try { | |
final ContentValues values = new ContentValues(); | |
@@ -763,20 +880,20 @@ | |
final String[] argsAccountId = {String.valueOf(accountId)}; | |
db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId); | |
db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId); | |
- mAuthTokenCache.remove(account); | |
+ accounts.authTokenCache.remove(account); | |
db.setTransactionSuccessful(); | |
} | |
} finally { | |
db.endTransaction(); | |
} | |
- sendAccountsChangedBroadcast(); | |
+ sendAccountsChangedBroadcast(accounts.userId); | |
} | |
} | |
- private void sendAccountsChangedBroadcast() { | |
+ private void sendAccountsChangedBroadcast(int userId) { | |
Log.i(TAG, "the accounts changed, sending broadcast of " | |
+ ACCOUNTS_CHANGED_INTENT.getAction()); | |
- mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT); | |
+ mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT, userId); | |
} | |
public void clearPassword(Account account) { | |
@@ -787,9 +904,10 @@ | |
} | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
checkManageAccountsPermission(); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- setPasswordInternal(account, null); | |
+ setPasswordInternal(accounts, account, null); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
@@ -805,20 +923,22 @@ | |
if (key == null) throw new IllegalArgumentException("key is null"); | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
checkAuthenticateAccountsPermission(account); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- setUserdataInternal(account, key, value); | |
+ setUserdataInternal(accounts, account, key, value); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
} | |
- private void setUserdataInternal(Account account, String key, String value) { | |
+ private void setUserdataInternal(UserAccounts accounts, Account account, String key, | |
+ String value) { | |
if (account == null || key == null) { | |
return; | |
} | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); | |
db.beginTransaction(); | |
try { | |
long accountId = getAccountIdLocked(db, account); | |
@@ -839,7 +959,7 @@ | |
} | |
} | |
- writeUserDataIntoCacheLocked(db, account, key, value); | |
+ writeUserDataIntoCacheLocked(accounts, db, account, key, value); | |
db.setTransactionSuccessful(); | |
} finally { | |
db.endTransaction(); | |
@@ -866,20 +986,25 @@ | |
} | |
} | |
- void getAuthTokenLabel(final IAccountManagerResponse response, | |
- final Account account, final String authTokenType) { | |
- if (account == null) throw new IllegalArgumentException("account is null"); | |
+ public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType, | |
+ final String authTokenType) | |
+ throws RemoteException { | |
+ if (accountType == null) throw new IllegalArgumentException("accountType is null"); | |
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); | |
- checkBinderPermission(Manifest.permission.USE_CREDENTIALS); | |
- | |
+ final int callingUid = getCallingUid(); | |
+ clearCallingIdentity(); | |
+ if (callingUid != android.os.Process.SYSTEM_UID) { | |
+ throw new SecurityException("can only call from system"); | |
+ } | |
+ UserAccounts accounts = getUserAccounts(UserId.getUserId(callingUid)); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- new Session(response, account.type, false, | |
+ new Session(accounts, response, accountType, false, | |
false /* stripAuthTokenFromResult */) { | |
protected String toDebugString(long now) { | |
return super.toDebugString(now) + ", getAuthTokenLabel" | |
- + ", " + account | |
+ + ", " + accountType | |
+ ", authTokenType " + authTokenType; | |
} | |
@@ -920,6 +1045,7 @@ | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); | |
checkBinderPermission(Manifest.permission.USE_CREDENTIALS); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo = | |
mAuthenticatorCache.getServiceInfo( | |
AuthenticatorDescription.newKey(account.type)); | |
@@ -945,7 +1071,7 @@ | |
// if the caller has permission, do the peek. otherwise go the more expensive | |
// route of starting a Session | |
if (!customTokens && permissionGranted) { | |
- String authToken = readAuthTokenInternal(account, authTokenType); | |
+ String authToken = readAuthTokenInternal(accounts, account, authTokenType); | |
if (authToken != null) { | |
Bundle result = new Bundle(); | |
result.putString(AccountManager.KEY_AUTHTOKEN, authToken); | |
@@ -956,7 +1082,7 @@ | |
} | |
} | |
- new Session(response, account.type, expectActivityLaunch, | |
+ new Session(accounts, response, account.type, expectActivityLaunch, | |
false /* stripAuthTokenFromResult */) { | |
protected String toDebugString(long now) { | |
if (loginOptions != null) loginOptions.keySet(); | |
@@ -999,14 +1125,14 @@ | |
return; | |
} | |
if (!customTokens) { | |
- saveAuthTokenToDatabase(new Account(name, type), | |
+ saveAuthTokenToDatabase(mAccounts, new Account(name, type), | |
authTokenType, authToken); | |
} | |
} | |
Intent intent = result.getParcelable(AccountManager.KEY_INTENT); | |
if (intent != null && notifyOnAuthFailure && !customTokens) { | |
- doNotification( | |
+ doNotification(mAccounts, | |
account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE), | |
intent); | |
} | |
@@ -1089,26 +1215,27 @@ | |
private Integer getCredentialPermissionNotificationId(Account account, String authTokenType, | |
int uid) { | |
Integer id; | |
- synchronized(mCredentialsPermissionNotificationIds) { | |
+ UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); | |
+ synchronized (accounts.credentialsPermissionNotificationIds) { | |
final Pair<Pair<Account, String>, Integer> key = | |
new Pair<Pair<Account, String>, Integer>( | |
new Pair<Account, String>(account, authTokenType), uid); | |
- id = mCredentialsPermissionNotificationIds.get(key); | |
+ id = accounts.credentialsPermissionNotificationIds.get(key); | |
if (id == null) { | |
id = mNotificationIds.incrementAndGet(); | |
- mCredentialsPermissionNotificationIds.put(key, id); | |
+ accounts.credentialsPermissionNotificationIds.put(key, id); | |
} | |
} | |
return id; | |
} | |
- private Integer getSigninRequiredNotificationId(Account account) { | |
+ private Integer getSigninRequiredNotificationId(UserAccounts accounts, Account account) { | |
Integer id; | |
- synchronized(mSigninRequiredNotificationIds) { | |
- id = mSigninRequiredNotificationIds.get(account); | |
+ synchronized (accounts.signinRequiredNotificationIds) { | |
+ id = accounts.signinRequiredNotificationIds.get(account); | |
if (id == null) { | |
id = mNotificationIds.incrementAndGet(); | |
- mSigninRequiredNotificationIds.put(account, id); | |
+ accounts.signinRequiredNotificationIds.put(account, id); | |
} | |
} | |
return id; | |
@@ -1130,6 +1257,7 @@ | |
if (accountType == null) throw new IllegalArgumentException("accountType is null"); | |
checkManageAccountsPermission(); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
final int pid = Binder.getCallingPid(); | |
final int uid = Binder.getCallingUid(); | |
final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn; | |
@@ -1138,7 +1266,7 @@ | |
long identityToken = clearCallingIdentity(); | |
try { | |
- new Session(response, accountType, expectActivityLaunch, | |
+ new Session(accounts, response, accountType, expectActivityLaunch, | |
true /* stripAuthTokenFromResult */) { | |
public void run() throws RemoteException { | |
mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, | |
@@ -1171,9 +1299,10 @@ | |
if (response == null) throw new IllegalArgumentException("response is null"); | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
checkManageAccountsPermission(); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- new Session(response, account.type, expectActivityLaunch, | |
+ new Session(accounts, response, account.type, expectActivityLaunch, | |
true /* stripAuthTokenFromResult */) { | |
public void run() throws RemoteException { | |
mAuthenticator.confirmCredentials(this, account, options); | |
@@ -1203,9 +1332,10 @@ | |
if (account == null) throw new IllegalArgumentException("account is null"); | |
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); | |
checkManageAccountsPermission(); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- new Session(response, account.type, expectActivityLaunch, | |
+ new Session(accounts, response, account.type, expectActivityLaunch, | |
true /* stripAuthTokenFromResult */) { | |
public void run() throws RemoteException { | |
mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions); | |
@@ -1235,9 +1365,10 @@ | |
if (response == null) throw new IllegalArgumentException("response is null"); | |
if (accountType == null) throw new IllegalArgumentException("accountType is null"); | |
checkManageAccountsPermission(); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- new Session(response, accountType, expectActivityLaunch, | |
+ new Session(accounts, response, accountType, expectActivityLaunch, | |
true /* stripAuthTokenFromResult */) { | |
public void run() throws RemoteException { | |
mAuthenticator.editProperties(this, mAccountType); | |
@@ -1258,16 +1389,16 @@ | |
private volatile ArrayList<Account> mAccountsWithFeatures = null; | |
private volatile int mCurrentAccount = 0; | |
- public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response, | |
- String type, String[] features) { | |
- super(response, type, false /* expectActivityLaunch */, | |
+ public GetAccountsByTypeAndFeatureSession(UserAccounts accounts, | |
+ IAccountManagerResponse response, String type, String[] features) { | |
+ super(accounts, response, type, false /* expectActivityLaunch */, | |
true /* stripAuthTokenFromResult */); | |
mFeatures = features; | |
} | |
public void run() throws RemoteException { | |
- synchronized (mCacheLock) { | |
- mAccountsOfType = getAccountsFromCacheLocked(mAccountType); | |
+ synchronized (mAccounts.cacheLock) { | |
+ mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType); | |
} | |
// check whether each account matches the requested features | |
mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length); | |
@@ -1345,6 +1476,48 @@ | |
} | |
} | |
+ /** | |
+ * Returns the accounts for a specific user | |
+ * @hide | |
+ */ | |
+ public Account[] getAccounts(int userId) { | |
+ checkReadAccountsPermission(); | |
+ UserAccounts accounts = getUserAccounts(userId); | |
+ long identityToken = clearCallingIdentity(); | |
+ try { | |
+ synchronized (accounts.cacheLock) { | |
+ return getAccountsFromCacheLocked(accounts, null); | |
+ } | |
+ } finally { | |
+ restoreCallingIdentity(identityToken); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Returns all the accounts qualified by user. | |
+ * @hide | |
+ */ | |
+ public AccountAndUser[] getAllAccounts() { | |
+ ArrayList<AccountAndUser> allAccounts = new ArrayList<AccountAndUser>(); | |
+ List<UserInfo> users = getAllUsers(); | |
+ if (users == null) return new AccountAndUser[0]; | |
+ | |
+ synchronized(mUsers) { | |
+ for (UserInfo user : users) { | |
+ UserAccounts userAccounts = getUserAccounts(user.id); | |
+ if (userAccounts == null) continue; | |
+ synchronized (userAccounts.cacheLock) { | |
+ Account[] accounts = getAccountsFromCacheLocked(userAccounts, null); | |
+ for (int a = 0; a < accounts.length; a++) { | |
+ allAccounts.add(new AccountAndUser(accounts[a], user.id)); | |
+ } | |
+ } | |
+ } | |
+ } | |
+ AccountAndUser[] accountsArray = new AccountAndUser[allAccounts.size()]; | |
+ return allAccounts.toArray(accountsArray); | |
+ } | |
+ | |
public Account[] getAccounts(String type) { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "getAccounts: accountType " + type | |
@@ -1352,10 +1525,11 @@ | |
+ ", pid " + Binder.getCallingPid()); | |
} | |
checkReadAccountsPermission(); | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
- synchronized (mCacheLock) { | |
- return getAccountsFromCacheLocked(type); | |
+ synchronized (accounts.cacheLock) { | |
+ return getAccountsFromCacheLocked(accounts, type); | |
} | |
} finally { | |
restoreCallingIdentity(identityToken); | |
@@ -1374,19 +1548,20 @@ | |
if (response == null) throw new IllegalArgumentException("response is null"); | |
if (type == null) throw new IllegalArgumentException("accountType is null"); | |
checkReadAccountsPermission(); | |
+ UserAccounts userAccounts = getUserAccountsForCaller(); | |
long identityToken = clearCallingIdentity(); | |
try { | |
if (features == null || features.length == 0) { | |
Account[] accounts; | |
- synchronized (mCacheLock) { | |
- accounts = getAccountsFromCacheLocked(type); | |
+ synchronized (userAccounts.cacheLock) { | |
+ accounts = getAccountsFromCacheLocked(userAccounts, type); | |
} | |
Bundle result = new Bundle(); | |
result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); | |
onResult(response, result); | |
return; | |
} | |
- new GetAccountsByTypeAndFeatureSession(response, type, features).bind(); | |
+ new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind(); | |
} finally { | |
restoreCallingIdentity(identityToken); | |
} | |
@@ -1434,12 +1609,14 @@ | |
IAccountAuthenticator mAuthenticator = null; | |
private final boolean mStripAuthTokenFromResult; | |
+ protected final UserAccounts mAccounts; | |
- public Session(IAccountManagerResponse response, String accountType, | |
+ public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType, | |
boolean expectActivityLaunch, boolean stripAuthTokenFromResult) { | |
super(); | |
if (response == null) throw new IllegalArgumentException("response is null"); | |
if (accountType == null) throw new IllegalArgumentException("accountType is null"); | |
+ mAccounts = accounts; | |
mStripAuthTokenFromResult = stripAuthTokenFromResult; | |
mResponse = response; | |
mAccountType = accountType; | |
@@ -1577,7 +1754,7 @@ | |
String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE); | |
if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) { | |
Account account = new Account(accountName, accountType); | |
- cancelNotification(getSigninRequiredNotificationId(account)); | |
+ cancelNotification(getSigninRequiredNotificationId(mAccounts, account)); | |
} | |
} | |
IAccountManagerResponse response; | |
@@ -1663,7 +1840,7 @@ | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName); | |
} | |
- if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) { | |
+ if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE, mAccounts.userId)) { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed"); | |
} | |
@@ -1693,20 +1870,35 @@ | |
} | |
} | |
- private static String getDatabaseName() { | |
- if(Environment.isEncryptedFilesystemEnabled()) { | |
- // Hard-coded path in case of encrypted file system | |
- return Environment.getSystemSecureDirectory().getPath() + File.separator + DATABASE_NAME; | |
- } else { | |
- // Regular path in case of non-encrypted file system | |
- return DATABASE_NAME; | |
+ private static String getDatabaseName(int userId) { | |
+ File systemDir = Environment.getSystemSecureDirectory(); | |
+ File databaseFile = new File(systemDir, "users/" + userId + "/" + DATABASE_NAME); | |
+ if (userId == 0) { | |
+ // Migrate old file, if it exists, to the new location. | |
+ // Make sure the new file doesn't already exist. A dummy file could have been | |
+ // accidentally created in the old location, causing the new one to become corrupted | |
+ // as well. | |
+ File oldFile = new File(systemDir, DATABASE_NAME); | |
+ if (oldFile.exists() && !databaseFile.exists()) { | |
+ // Check for use directory; create if it doesn't exist, else renameTo will fail | |
+ File userDir = new File(systemDir, "users/" + userId); | |
+ if (!userDir.exists()) { | |
+ if (!userDir.mkdirs()) { | |
+ throw new IllegalStateException("User dir cannot be created: " + userDir); | |
+ } | |
+ } | |
+ if (!oldFile.renameTo(databaseFile)) { | |
+ throw new IllegalStateException("User dir cannot be migrated: " + databaseFile); | |
+ } | |
+ } | |
} | |
+ return databaseFile.getPath(); | |
} | |
- private class DatabaseHelper extends SQLiteOpenHelper { | |
+ static class DatabaseHelper extends SQLiteOpenHelper { | |
- public DatabaseHelper(Context context) { | |
- super(context, AccountManagerService.getDatabaseName(), null, DATABASE_VERSION); | |
+ public DatabaseHelper(Context context, int userId) { | |
+ super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION); | |
} | |
/** | |
@@ -1798,15 +1990,6 @@ | |
} | |
} | |
- private void setMetaValue(String key, String value) { | |
- ContentValues values = new ContentValues(); | |
- values.put(META_KEY, key); | |
- values.put(META_VALUE, value); | |
- synchronized (mCacheLock) { | |
- mOpenHelper.getWritableDatabase().replace(TABLE_META, META_KEY, values); | |
- } | |
- } | |
- | |
public IBinder onBind(Intent intent) { | |
return asBinder(); | |
} | |
@@ -1836,11 +2019,25 @@ | |
+ " without permission " + android.Manifest.permission.DUMP); | |
return; | |
} | |
+ final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c"); | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); | |
+ fout = new IndentingPrintWriter(fout, " "); | |
+ int size = mUsers.size(); | |
+ for (int i = 0; i < size; i++) { | |
+ fout.println("User " + mUsers.keyAt(i) + ":"); | |
+ ((IndentingPrintWriter) fout).increaseIndent(); | |
+ dumpUser(mUsers.valueAt(i), fd, fout, args, isCheckinRequest); | |
+ ((IndentingPrintWriter) fout).decreaseIndent(); | |
+ if (i < size - 1) { | |
+ fout.println(); | |
+ } | |
+ } | |
+ } | |
- final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c"); | |
+ private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout, | |
+ String[] args, boolean isCheckinRequest) { | |
+ synchronized (userAccounts.cacheLock) { | |
+ final SQLiteDatabase db = userAccounts.openHelper.getReadableDatabase(); | |
if (isCheckinRequest) { | |
// This is a checkin request. *Only* upload the account types and the count of each. | |
@@ -1857,7 +2054,7 @@ | |
} | |
} | |
} else { | |
- Account[] accounts = getAccountsFromCacheLocked(null /* type */); | |
+ Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */); | |
fout.println("Accounts: " + accounts.length); | |
for (Account account : accounts) { | |
fout.println(" " + account); | |
@@ -1878,7 +2075,8 @@ | |
} | |
} | |
- private void doNotification(Account account, CharSequence message, Intent intent) { | |
+ private void doNotification(UserAccounts accounts, Account account, CharSequence message, | |
+ Intent intent) { | |
long identityToken = clearCallingIdentity(); | |
try { | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
@@ -1890,7 +2088,7 @@ | |
intent.getComponent().getClassName())) { | |
createNoCredentialsPermissionNotification(account, intent); | |
} else { | |
- final Integer notificationId = getSigninRequiredNotificationId(account); | |
+ final Integer notificationId = getSigninRequiredNotificationId(accounts, account); | |
intent.addCategory(String.valueOf(notificationId)); | |
Notification n = new Notification(android.R.drawable.stat_sys_warning, null, | |
0 /* when */); | |
@@ -1961,7 +2159,7 @@ | |
final boolean fromAuthenticator = account != null | |
&& hasAuthenticatorUid(account.type, callerUid); | |
final boolean hasExplicitGrants = account != null | |
- && hasExplicitlyGrantedPermission(account, authTokenType); | |
+ && hasExplicitlyGrantedPermission(account, authTokenType, callerUid); | |
if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid " | |
+ callerUid + ", " + account | |
@@ -1983,13 +2181,15 @@ | |
return false; | |
} | |
- private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType) { | |
- if (Binder.getCallingUid() == android.os.Process.SYSTEM_UID) { | |
+ private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType, | |
+ int callerUid) { | |
+ if (callerUid == android.os.Process.SYSTEM_UID) { | |
return true; | |
} | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); | |
- String[] args = {String.valueOf(Binder.getCallingUid()), authTokenType, | |
+ UserAccounts accounts = getUserAccountsForCaller(); | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); | |
+ String[] args = { String.valueOf(callerUid), authTokenType, | |
account.name, account.type}; | |
final boolean permissionGranted = | |
DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0; | |
@@ -1997,7 +2197,7 @@ | |
// TODO: Skip this check when running automated tests. Replace this | |
// with a more general solution. | |
Log.d(TAG, "no credentials permission for usage of " + account + ", " | |
- + authTokenType + " by uid " + Binder.getCallingUid() | |
+ + authTokenType + " by uid " + callerUid | |
+ " but ignoring since device is in test harness."); | |
return true; | |
} | |
@@ -2035,6 +2235,21 @@ | |
Manifest.permission.USE_CREDENTIALS); | |
} | |
+ public void updateAppPermission(Account account, String authTokenType, int uid, boolean value) | |
+ throws RemoteException { | |
+ final int callingUid = getCallingUid(); | |
+ | |
+ if (callingUid != android.os.Process.SYSTEM_UID) { | |
+ throw new SecurityException(); | |
+ } | |
+ | |
+ if (value) { | |
+ grantAppPermission(account, authTokenType, uid); | |
+ } else { | |
+ revokeAppPermission(account, authTokenType, uid); | |
+ } | |
+ } | |
+ | |
/** | |
* Allow callers with the given uid permission to get credentials for account/authTokenType. | |
* <p> | |
@@ -2042,13 +2257,14 @@ | |
* which is in the system. This means we don't need to protect it with permissions. | |
* @hide | |
*/ | |
- public void grantAppPermission(Account account, String authTokenType, int uid) { | |
+ private void grantAppPermission(Account account, String authTokenType, int uid) { | |
if (account == null || authTokenType == null) { | |
Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception()); | |
return; | |
} | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); | |
+ UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); | |
db.beginTransaction(); | |
try { | |
long accountId = getAccountIdLocked(db, account); | |
@@ -2075,13 +2291,14 @@ | |
* which is in the system. This means we don't need to protect it with permissions. | |
* @hide | |
*/ | |
- public void revokeAppPermission(Account account, String authTokenType, int uid) { | |
+ private void revokeAppPermission(Account account, String authTokenType, int uid) { | |
if (account == null || authTokenType == null) { | |
Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception()); | |
return; | |
} | |
- synchronized (mCacheLock) { | |
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); | |
+ UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); | |
+ synchronized (accounts.cacheLock) { | |
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); | |
db.beginTransaction(); | |
try { | |
long accountId = getAccountIdLocked(db, account); | |
@@ -2104,8 +2321,8 @@ | |
return value != null ? ("[" + TextUtils.join(",", value) + "]") : null; | |
} | |
- private void removeAccountFromCacheLocked(Account account) { | |
- final Account[] oldAccountsForType = mAccountCache.get(account.type); | |
+ private void removeAccountFromCacheLocked(UserAccounts accounts, Account account) { | |
+ final Account[] oldAccountsForType = accounts.accountCache.get(account.type); | |
if (oldAccountsForType != null) { | |
ArrayList<Account> newAccountsList = new ArrayList<Account>(); | |
for (Account curAccount : oldAccountsForType) { | |
@@ -2114,34 +2331,34 @@ | |
} | |
} | |
if (newAccountsList.isEmpty()) { | |
- mAccountCache.remove(account.type); | |
+ accounts.accountCache.remove(account.type); | |
} else { | |
Account[] newAccountsForType = new Account[newAccountsList.size()]; | |
newAccountsForType = newAccountsList.toArray(newAccountsForType); | |
- mAccountCache.put(account.type, newAccountsForType); | |
+ accounts.accountCache.put(account.type, newAccountsForType); | |
} | |
} | |
- mUserDataCache.remove(account); | |
- mAuthTokenCache.remove(account); | |
+ accounts.userDataCache.remove(account); | |
+ accounts.authTokenCache.remove(account); | |
} | |
/** | |
* This assumes that the caller has already checked that the account is not already present. | |
*/ | |
- private void insertAccountIntoCacheLocked(Account account) { | |
- Account[] accountsForType = mAccountCache.get(account.type); | |
+ private void insertAccountIntoCacheLocked(UserAccounts accounts, Account account) { | |
+ Account[] accountsForType = accounts.accountCache.get(account.type); | |
int oldLength = (accountsForType != null) ? accountsForType.length : 0; | |
Account[] newAccountsForType = new Account[oldLength + 1]; | |
if (accountsForType != null) { | |
System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength); | |
} | |
newAccountsForType[oldLength] = account; | |
- mAccountCache.put(account.type, newAccountsForType); | |
+ accounts.accountCache.put(account.type, newAccountsForType); | |
} | |
- protected Account[] getAccountsFromCacheLocked(String accountType) { | |
+ protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) { | |
if (accountType != null) { | |
- final Account[] accounts = mAccountCache.get(accountType); | |
+ final Account[] accounts = userAccounts.accountCache.get(accountType); | |
if (accounts == null) { | |
return EMPTY_ACCOUNT_ARRAY; | |
} else { | |
@@ -2149,7 +2366,7 @@ | |
} | |
} else { | |
int totalLength = 0; | |
- for (Account[] accounts : mAccountCache.values()) { | |
+ for (Account[] accounts : userAccounts.accountCache.values()) { | |
totalLength += accounts.length; | |
} | |
if (totalLength == 0) { | |
@@ -2157,7 +2374,7 @@ | |
} | |
Account[] accounts = new Account[totalLength]; | |
totalLength = 0; | |
- for (Account[] accountsOfType : mAccountCache.values()) { | |
+ for (Account[] accountsOfType : userAccounts.accountCache.values()) { | |
System.arraycopy(accountsOfType, 0, accounts, totalLength, | |
accountsOfType.length); | |
totalLength += accountsOfType.length; | |
@@ -2166,12 +2383,12 @@ | |
} | |
} | |
- protected void writeUserDataIntoCacheLocked(final SQLiteDatabase db, Account account, | |
- String key, String value) { | |
- HashMap<String, String> userDataForAccount = mUserDataCache.get(account); | |
+ protected void writeUserDataIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db, | |
+ Account account, String key, String value) { | |
+ HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account); | |
if (userDataForAccount == null) { | |
userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); | |
- mUserDataCache.put(account, userDataForAccount); | |
+ accounts.userDataCache.put(account, userDataForAccount); | |
} | |
if (value == null) { | |
userDataForAccount.remove(key); | |
@@ -2180,12 +2397,12 @@ | |
} | |
} | |
- protected void writeAuthTokenIntoCacheLocked(final SQLiteDatabase db, Account account, | |
- String key, String value) { | |
- HashMap<String, String> authTokensForAccount = mAuthTokenCache.get(account); | |
+ protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db, | |
+ Account account, String key, String value) { | |
+ HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account); | |
if (authTokensForAccount == null) { | |
authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account); | |
- mAuthTokenCache.put(account, authTokensForAccount); | |
+ accounts.authTokenCache.put(account, authTokensForAccount); | |
} | |
if (value == null) { | |
authTokensForAccount.remove(key); | |
@@ -2194,27 +2411,28 @@ | |
} | |
} | |
- protected String readAuthTokenInternal(Account account, String authTokenType) { | |
- synchronized (mCacheLock) { | |
- HashMap<String, String> authTokensForAccount = mAuthTokenCache.get(account); | |
+ protected String readAuthTokenInternal(UserAccounts accounts, Account account, | |
+ String authTokenType) { | |
+ synchronized (accounts.cacheLock) { | |
+ HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account); | |
if (authTokensForAccount == null) { | |
// need to populate the cache for this account | |
- final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); | |
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); | |
authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account); | |
- mAuthTokenCache.put(account, authTokensForAccount); | |
+ accounts.authTokenCache.put(account, authTokensForAccount); | |
} | |
return authTokensForAccount.get(authTokenType); | |
} | |
} | |
- protected String readUserDataInternal(Account account, String key) { | |
- synchronized (mCacheLock) { | |
- HashMap<String, String> userDataForAccount = mUserDataCache.get(account); | |
+ protected String readUserDataInternal(UserAccounts accounts, Account account, String key) { | |
+ synchronized (accounts.cacheLock) { | |
+ HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account); | |
if (userDataForAccount == null) { | |
// need to populate the cache for this account | |
- final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); | |
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); | |
userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); | |
- mUserDataCache.put(account, userDataForAccount); | |
+ accounts.userDataCache.put(account, userDataForAccount); | |
} | |
return userDataForAccount.get(key); | |
} | |
diff -Nur android-15/android/accounts/ChooseTypeAndAccountActivity.java android-16/android/accounts/ChooseTypeAndAccountActivity.java | |
--- android-15/android/accounts/ChooseTypeAndAccountActivity.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/accounts/ChooseTypeAndAccountActivity.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -16,24 +16,18 @@ | |
package android.accounts; | |
import android.app.Activity; | |
-import android.content.Context; | |
import android.content.Intent; | |
-import android.content.pm.PackageManager; | |
-import android.content.res.Resources; | |
-import android.graphics.drawable.Drawable; | |
import android.os.Bundle; | |
import android.os.Parcelable; | |
import android.text.TextUtils; | |
import android.util.Log; | |
-import android.view.LayoutInflater; | |
import android.view.View; | |
-import android.view.ViewGroup; | |
import android.widget.AdapterView; | |
import android.widget.ArrayAdapter; | |
import android.widget.Button; | |
-import android.widget.ImageView; | |
import android.widget.ListView; | |
import android.widget.TextView; | |
+ | |
import com.android.internal.R; | |
import java.io.IOException; | |
@@ -106,10 +100,16 @@ | |
private static final String KEY_INSTANCE_STATE_PENDING_REQUEST = "pendingRequest"; | |
private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts"; | |
+ private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName"; | |
+ private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount"; | |
+ | |
+ private static final int SELECTED_ITEM_NONE = -1; | |
- private ArrayList<AccountInfo> mAccountInfos; | |
+ private ArrayList<Account> mAccounts; | |
private int mPendingRequest = REQUEST_NULL; | |
private Parcelable[] mExistingAccounts = null; | |
+ private int mSelectedItemIndex; | |
+ private Button mOkButton; | |
@Override | |
public void onCreate(Bundle savedInstanceState) { | |
@@ -119,33 +119,39 @@ | |
+ savedInstanceState + ")"); | |
} | |
- setContentView(R.layout.choose_type_and_account); | |
+ // save some items we use frequently | |
+ final AccountManager accountManager = AccountManager.get(this); | |
+ final Intent intent = getIntent(); | |
+ | |
+ String selectedAccountName = null; | |
+ boolean selectedAddNewAccount = false; | |
if (savedInstanceState != null) { | |
mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST); | |
mExistingAccounts = | |
savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS); | |
+ | |
+ // Makes sure that any user selection is preserved across orientation changes. | |
+ selectedAccountName = savedInstanceState.getString( | |
+ KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME); | |
+ | |
+ selectedAddNewAccount = savedInstanceState.getBoolean( | |
+ KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false); | |
} else { | |
mPendingRequest = REQUEST_NULL; | |
mExistingAccounts = null; | |
+ // If the selected account as specified in the intent matches one in the list we will | |
+ // show is as pre-selected. | |
+ Account selectedAccount = (Account) intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT); | |
+ if (selectedAccount != null) { | |
+ selectedAccountName = selectedAccount.name; | |
+ } | |
} | |
- // save some items we use frequently | |
- final AccountManager accountManager = AccountManager.get(this); | |
- final Intent intent = getIntent(); | |
- | |
- // override the description text if supplied | |
- final String descriptionOverride = | |
- intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); | |
- if (!TextUtils.isEmpty(descriptionOverride)) { | |
- ((TextView)findViewById(R.id.description)).setText(descriptionOverride); | |
+ if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
+ Log.v(TAG, "selected account name is " + selectedAccountName); | |
} | |
- // If the selected account matches one in the list we will place a | |
- // checkmark next to it. | |
- final Account selectedAccount = | |
- (Account)intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT); | |
- | |
// build an efficiently queryable map of account types to authenticator descriptions | |
final HashMap<String, AuthenticatorDescription> typeToAuthDescription = | |
new HashMap<String, AuthenticatorDescription>(); | |
@@ -164,14 +170,29 @@ | |
} | |
} | |
- // Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes | |
- Set<String> setOfAllowableAccountTypes = null; | |
- final String[] validAccountTypes = | |
+ // An account type is relevant iff it is allowed by the caller and supported by the account | |
+ // manager. | |
+ Set<String> setOfRelevantAccountTypes = null; | |
+ final String[] allowedAccountTypes = | |
intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY); | |
- if (validAccountTypes != null) { | |
- setOfAllowableAccountTypes = new HashSet<String>(validAccountTypes.length); | |
- for (String type : validAccountTypes) { | |
- setOfAllowableAccountTypes.add(type); | |
+ if (allowedAccountTypes != null) { | |
+ | |
+ setOfRelevantAccountTypes = new HashSet<String>(allowedAccountTypes.length); | |
+ Set<String> setOfAllowedAccountTypes = new HashSet<String>(allowedAccountTypes.length); | |
+ for (String type : allowedAccountTypes) { | |
+ setOfAllowedAccountTypes.add(type); | |
+ } | |
+ | |
+ AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes(); | |
+ Set<String> supportedAccountTypes = new HashSet<String>(descs.length); | |
+ for (AuthenticatorDescription desc : descs) { | |
+ supportedAccountTypes.add(desc.type); | |
+ } | |
+ | |
+ for (String acctType : setOfAllowedAccountTypes) { | |
+ if (supportedAccountTypes.contains(acctType)) { | |
+ setOfRelevantAccountTypes.add(acctType); | |
+ } | |
} | |
} | |
@@ -179,56 +200,95 @@ | |
// accounts that don't match the allowable types, if provided, or that don't match the | |
// allowable accounts, if provided. | |
final Account[] accounts = accountManager.getAccounts(); | |
- mAccountInfos = new ArrayList<AccountInfo>(accounts.length); | |
+ mAccounts = new ArrayList<Account>(accounts.length); | |
+ mSelectedItemIndex = SELECTED_ITEM_NONE; | |
for (Account account : accounts) { | |
if (setOfAllowableAccounts != null | |
&& !setOfAllowableAccounts.contains(account)) { | |
continue; | |
} | |
- if (setOfAllowableAccountTypes != null | |
- && !setOfAllowableAccountTypes.contains(account.type)) { | |
+ if (setOfRelevantAccountTypes != null | |
+ && !setOfRelevantAccountTypes.contains(account.type)) { | |
continue; | |
} | |
- mAccountInfos.add(new AccountInfo(account, | |
- getDrawableForType(typeToAuthDescription, account.type), | |
- account.equals(selectedAccount))); | |
- } | |
- | |
- // there is more than one allowable account. initialize the list adapter to allow | |
- // the user to select an account. | |
- ListView list = (ListView) findViewById(android.R.id.list); | |
- list.setAdapter(new AccountArrayAdapter(this, | |
- android.R.layout.simple_list_item_1, mAccountInfos)); | |
- list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); | |
- list.setOnItemClickListener(new AdapterView.OnItemClickListener() { | |
- public void onItemClick(AdapterView<?> parent, View v, int position, long id) { | |
- onListItemClick((ListView)parent, v, position, id); | |
+ if (account.name.equals(selectedAccountName)) { | |
+ mSelectedItemIndex = mAccounts.size(); | |
} | |
- }); | |
- | |
- // set the listener for the addAccount button | |
- Button addAccountButton = (Button) findViewById(R.id.addAccount); | |
- addAccountButton.setOnClickListener(new View.OnClickListener() { | |
- public void onClick(final View v) { | |
- startChooseAccountTypeActivity(); | |
- } | |
- }); | |
+ mAccounts.add(account); | |
+ } | |
if (mPendingRequest == REQUEST_NULL) { | |
- // If there are no allowable accounts go directly to add account | |
- if (mAccountInfos.isEmpty()) { | |
- startChooseAccountTypeActivity(); | |
+ // If there are no relevant accounts and only one relevant account type go directly to | |
+ // add account. Otherwise let the user choose. | |
+ if (mAccounts.isEmpty()) { | |
+ if (setOfRelevantAccountTypes.size() == 1) { | |
+ runAddAccountForAuthenticator(setOfRelevantAccountTypes.iterator().next()); | |
+ } else { | |
+ startChooseAccountTypeActivity(); | |
+ } | |
return; | |
} | |
// if there is only one allowable account return it | |
if (!intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false) | |
- && mAccountInfos.size() == 1) { | |
- Account account = mAccountInfos.get(0).account; | |
+ && mAccounts.size() == 1) { | |
+ Account account = mAccounts.get(0); | |
setResultAndFinish(account.name, account.type); | |
return; | |
} | |
} | |
+ | |
+ // Cannot set content view until we know that mPendingRequest is not null, otherwise | |
+ // would cause screen flicker. | |
+ setContentView(R.layout.choose_type_and_account); | |
+ | |
+ // Override the description text if supplied | |
+ final String descriptionOverride = | |
+ intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE); | |
+ TextView descriptionView = (TextView) findViewById(R.id.description); | |
+ if (!TextUtils.isEmpty(descriptionOverride)) { | |
+ descriptionView.setText(descriptionOverride); | |
+ } else { | |
+ descriptionView.setVisibility(View.GONE); | |
+ } | |
+ | |
+ // List of options includes all accounts found together with "Add new account" as the | |
+ // last item in the list. | |
+ String[] listItems = new String[mAccounts.size() + 1]; | |
+ for (int i = 0; i < mAccounts.size(); i++) { | |
+ listItems[i] = mAccounts.get(i).name; | |
+ } | |
+ listItems[mAccounts.size()] = getResources().getString( | |
+ R.string.add_account_button_label); | |
+ | |
+ ListView list = (ListView) findViewById(android.R.id.list); | |
+ list.setAdapter(new ArrayAdapter<String>(this, | |
+ android.R.layout.simple_list_item_single_choice, listItems)); | |
+ list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); | |
+ list.setItemsCanFocus(false); | |
+ list.setOnItemClickListener(new AdapterView.OnItemClickListener() { | |
+ @Override | |
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) { | |
+ mSelectedItemIndex = position; | |
+ mOkButton.setEnabled(true); | |
+ } | |
+ }); | |
+ | |
+ // If "Add account" option was previously selected by user, preserve it across | |
+ // orientation changes. | |
+ if (selectedAddNewAccount) { | |
+ mSelectedItemIndex = mAccounts.size(); | |
+ } | |
+ if (mSelectedItemIndex != SELECTED_ITEM_NONE) { | |
+ list.setItemChecked(mSelectedItemIndex, true); | |
+ if (Log.isLoggable(TAG, Log.VERBOSE)) { | |
+ Log.v(TAG, "List item " + mSelectedItemIndex + " should be selected"); | |
+ } | |
+ } | |
+ | |
+ // Only enable "OK" button if something has been selected. | |
+ mOkButton = (Button) findViewById(android.R.id.button2); | |
+ mOkButton.setEnabled(mSelectedItemIndex != SELECTED_ITEM_NONE); | |
} | |
@Override | |
@@ -246,6 +306,28 @@ | |
if (mPendingRequest == REQUEST_ADD_ACCOUNT) { | |
outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts); | |
} | |
+ if (mSelectedItemIndex != SELECTED_ITEM_NONE) { | |
+ if (mSelectedItemIndex == mAccounts.size()) { | |
+ outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true); | |
+ } else { | |
+ outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false); | |
+ outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME, | |
+ mAccounts.get(mSelectedItemIndex).name); | |
+ } | |
+ } | |
+ } | |
+ | |
+ public void onCancelButtonClicked(View view) { | |
+ onBackPressed(); | |
+ } | |
+ | |
+ public void onOkButtonClicked(View view) { | |
+ if (mSelectedItemIndex == mAccounts.size()) { | |
+ // Selected "Add New Account" option | |
+ startChooseAccountTypeActivity(); | |
+ } else if (mSelectedItemIndex != SELECTED_ITEM_NONE) { | |
+ onAccountSelected(mAccounts.get(mSelectedItemIndex)); | |
+ } | |
} | |
// Called when the choose account type activity (for adding an account) returns. | |
@@ -265,6 +347,12 @@ | |
mPendingRequest = REQUEST_NULL; | |
if (resultCode == RESULT_CANCELED) { | |
+ // if canceling out of addAccount and the original state caused us to skip this, | |
+ // finish this activity | |
+ if (mAccounts.isEmpty()) { | |
+ setResult(Activity.RESULT_CANCELED); | |
+ finish(); | |
+ } | |
return; | |
} | |
@@ -332,6 +420,7 @@ | |
options, null /* activity */, this /* callback */, null /* Handler */); | |
} | |
+ @Override | |
public void run(final AccountManagerFuture<Bundle> accountManagerFuture) { | |
try { | |
final Bundle accountManagerResult = accountManagerFuture.getResult(); | |
@@ -357,34 +446,9 @@ | |
finish(); | |
} | |
- private Drawable getDrawableForType( | |
- final HashMap<String, AuthenticatorDescription> typeToAuthDescription, | |
- String accountType) { | |
- Drawable icon = null; | |
- if (typeToAuthDescription.containsKey(accountType)) { | |
- try { | |
- AuthenticatorDescription desc = typeToAuthDescription.get(accountType); | |
- Context authContext = createPackageContext(desc.packageName, 0); | |
- icon = authContext.getResources().getDrawable(desc.iconId); | |
- } catch (PackageManager.NameNotFoundException e) { | |
- // Nothing we can do much here, just log | |
- if (Log.isLoggable(TAG, Log.WARN)) { | |
- Log.w(TAG, "No icon name for account type " + accountType); | |
- } | |
- } catch (Resources.NotFoundException e) { | |
- // Nothing we can do much here, just log | |
- if (Log.isLoggable(TAG, Log.WARN)) { | |
- Log.w(TAG, "No icon resource for account type " + accountType); | |
- } | |
- } | |
- } | |
- return icon; | |
- } | |
- | |
- protected void onListItemClick(ListView l, View v, int position, long id) { | |
- AccountInfo accountInfo = mAccountInfos.get(position); | |
- Log.d(TAG, "selected account " + accountInfo.account); | |
- setResultAndFinish(accountInfo.account.name, accountInfo.account.type); | |
+ private void onAccountSelected(Account account) { | |
+ Log.d(TAG, "selected account " + account); | |
+ setResultAndFinish(account.name, account.type); | |
} | |
private void setResultAndFinish(final String accountName, final String accountType) { | |
@@ -416,58 +480,4 @@ | |
startActivityForResult(intent, REQUEST_CHOOSE_TYPE); | |
mPendingRequest = REQUEST_CHOOSE_TYPE; | |
} | |
- | |
- private static class AccountInfo { | |
- final Account account; | |
- final Drawable drawable; | |
- private final boolean checked; | |
- | |
- AccountInfo(Account account, Drawable drawable, boolean checked) { | |
- this.account = account; | |
- this.drawable = drawable; | |
- this.checked = checked; | |
- } | |
- } | |
- | |
- private static class ViewHolder { | |
- ImageView icon; | |
- TextView text; | |
- ImageView checkmark; | |
- } | |
- | |
- private static class AccountArrayAdapter extends ArrayAdapter<AccountInfo> { | |
- private LayoutInflater mLayoutInflater; | |
- private ArrayList<AccountInfo> mInfos; | |
- | |
- public AccountArrayAdapter(Context context, int textViewResourceId, | |
- ArrayList<AccountInfo> infos) { | |
- super(context, textViewResourceId, infos); | |
- mInfos = infos; | |
- mLayoutInflater = (LayoutInflater) context.getSystemService( | |
- Context.LAYOUT_INFLATER_SERVICE); | |
- } | |
- | |
- @Override | |
- public View getView(int position, View convertView, ViewGroup parent) { | |
- ViewHolder holder; | |
- | |
- if (convertView == null) { | |
- convertView = mLayoutInflater.inflate(R.layout.choose_selected_account_row, null); | |
- holder = new ViewHolder(); | |
- holder.text = (TextView) convertView.findViewById(R.id.account_row_text); | |
- holder.icon = (ImageView) convertView.findViewById(R.id.account_row_icon); | |
- holder.checkmark = (ImageView) convertView.findViewById(R.id.account_row_checkmark); | |
- convertView.setTag(holder); | |
- } else { | |
- holder = (ViewHolder) convertView.getTag(); | |
- } | |
- | |
- holder.text.setText(mInfos.get(position).account.name); | |
- holder.icon.setImageDrawable(mInfos.get(position).drawable); | |
- final int displayCheckmark = | |
- mInfos.get(position).checked ? View.VISIBLE : View.INVISIBLE; | |
- holder.checkmark.setVisibility(displayCheckmark); | |
- return convertView; | |
- } | |
- } | |
} | |
diff -Nur android-15/android/accounts/GrantCredentialsPermissionActivity.java android-16/android/accounts/GrantCredentialsPermissionActivity.java | |
--- android-15/android/accounts/GrantCredentialsPermissionActivity.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/accounts/GrantCredentialsPermissionActivity.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -16,22 +16,22 @@ | |
package android.accounts; | |
import android.app.Activity; | |
+import android.content.pm.RegisteredServicesCache; | |
+import android.content.res.Resources; | |
import android.os.Bundle; | |
-import android.os.RemoteException; | |
import android.widget.TextView; | |
import android.widget.LinearLayout; | |
-import android.widget.ImageView; | |
import android.view.View; | |
import android.view.LayoutInflater; | |
-import android.view.Window; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.content.pm.PackageManager; | |
-import android.content.pm.RegisteredServicesCache; | |
import android.text.TextUtils; | |
-import android.graphics.drawable.Drawable; | |
import com.android.internal.R; | |
+import java.io.IOException; | |
+import java.net.Authenticator; | |
+ | |
/** | |
* @hide | |
*/ | |
@@ -48,7 +48,6 @@ | |
private int mUid; | |
private Bundle mResultBundle = null; | |
protected LayoutInflater mInflater; | |
- private final AccountManagerService accountManagerService = AccountManagerService.getSingleton(); | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
@@ -81,7 +80,7 @@ | |
String accountTypeLabel; | |
try { | |
- accountTypeLabel = accountManagerService.getAccountLabel(mAccount.type); | |
+ accountTypeLabel = getAccountLabel(mAccount); | |
} catch (IllegalArgumentException e) { | |
// label or resource was missing. abort the activity. | |
setResult(Activity.RESULT_CANCELED); | |
@@ -92,29 +91,27 @@ | |
final TextView authTokenTypeView = (TextView) findViewById(R.id.authtoken_type); | |
authTokenTypeView.setVisibility(View.GONE); | |
- /** Handles the responses from the AccountManager */ | |
- IAccountManagerResponse response = new IAccountManagerResponse.Stub() { | |
- public void onResult(Bundle bundle) { | |
- final String authTokenLabel = | |
- bundle.getString(AccountManager.KEY_AUTH_TOKEN_LABEL); | |
- if (!TextUtils.isEmpty(authTokenLabel)) { | |
- runOnUiThread(new Runnable() { | |
- public void run() { | |
- if (!isFinishing()) { | |
- authTokenTypeView.setText(authTokenLabel); | |
- authTokenTypeView.setVisibility(View.VISIBLE); | |
+ final AccountManagerCallback<String> callback = new AccountManagerCallback<String>() { | |
+ public void run(AccountManagerFuture<String> future) { | |
+ try { | |
+ final String authTokenLabel = future.getResult(); | |
+ if (!TextUtils.isEmpty(authTokenLabel)) { | |
+ runOnUiThread(new Runnable() { | |
+ public void run() { | |
+ if (!isFinishing()) { | |
+ authTokenTypeView.setText(authTokenLabel); | |
+ authTokenTypeView.setVisibility(View.VISIBLE); | |
+ } | |
} | |
- } | |
- }); | |
+ }); | |
+ } | |
+ } catch (OperationCanceledException e) { | |
+ } catch (IOException e) { | |
+ } catch (AuthenticatorException e) { | |
} | |
} | |
- | |
- public void onError(int code, String message) { | |
- } | |
}; | |
- | |
- accountManagerService.getAuthTokenLabel( | |
- response, mAccount, mAuthTokenType); | |
+ AccountManager.get(this).getAuthTokenLabel(mAccount.type, mAuthTokenType, callback, null); | |
findViewById(R.id.allow_button).setOnClickListener(this); | |
findViewById(R.id.deny_button).setOnClickListener(this); | |
@@ -135,6 +132,24 @@ | |
((TextView) findViewById(R.id.account_type)).setText(accountTypeLabel); | |
} | |
+ private String getAccountLabel(Account account) { | |
+ final AuthenticatorDescription[] authenticatorTypes = | |
+ AccountManager.get(this).getAuthenticatorTypes(); | |
+ for (int i = 0, N = authenticatorTypes.length; i < N; i++) { | |
+ final AuthenticatorDescription desc = authenticatorTypes[i]; | |
+ if (desc.type.equals(account.type)) { | |
+ try { | |
+ return createPackageContext(desc.packageName, 0).getString(desc.labelId); | |
+ } catch (PackageManager.NameNotFoundException e) { | |
+ return account.type; | |
+ } catch (Resources.NotFoundException e) { | |
+ return account.type; | |
+ } | |
+ } | |
+ } | |
+ return account.type; | |
+ } | |
+ | |
private View newPackageView(String packageLabel) { | |
View view = mInflater.inflate(R.layout.permissions_package_list_item, null); | |
((TextView) view.findViewById(R.id.package_label)).setText(packageLabel); | |
@@ -144,7 +159,7 @@ | |
public void onClick(View v) { | |
switch (v.getId()) { | |
case R.id.allow_button: | |
- accountManagerService.grantAppPermission(mAccount, mAuthTokenType, mUid); | |
+ AccountManager.get(this).updateAppPermission(mAccount, mAuthTokenType, mUid, true); | |
Intent result = new Intent(); | |
result.putExtra("retry", true); | |
setResult(RESULT_OK, result); | |
@@ -152,7 +167,7 @@ | |
break; | |
case R.id.deny_button: | |
- accountManagerService.revokeAppPermission(mAccount, mAuthTokenType, mUid); | |
+ AccountManager.get(this).updateAppPermission(mAccount, mAuthTokenType, mUid, false); | |
setResult(RESULT_CANCELED); | |
break; | |
} | |
diff -Nur android-15/android/accounts/OnAccountsUpdateListener.java android-16/android/accounts/OnAccountsUpdateListener.java | |
--- android-15/android/accounts/OnAccountsUpdateListener.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/accounts/OnAccountsUpdateListener.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -17,11 +17,11 @@ | |
package android.accounts; | |
/** | |
- * An interface that contains the callback used by the AccountMonitor | |
+ * An interface that contains the callback used by the AccountManager | |
*/ | |
public interface OnAccountsUpdateListener { | |
/** | |
- * This invoked when the AccountMonitor starts up and whenever the account | |
+ * This invoked when the AccountManager starts up and whenever the account | |
* set changes. | |
* @param accounts the current accounts | |
*/ | |
diff -Nur android-15/android/animation/AnimationThread.java android-16/android/animation/AnimationThread.java | |
--- android-15/android/animation/AnimationThread.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/animation/AnimationThread.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -23,11 +23,10 @@ | |
import com.android.layoutlib.bridge.Bridge; | |
import com.android.layoutlib.bridge.impl.RenderSessionImpl; | |
-import android.animation.ValueAnimator; | |
import android.os.Handler; | |
import android.os.Handler_Delegate; | |
-import android.os.Message; | |
import android.os.Handler_Delegate.IHandlerCallback; | |
+import android.os.Message; | |
import java.util.PriorityQueue; | |
import java.util.Queue; | |
@@ -57,6 +56,7 @@ | |
mUptimeMillis = uptimeMillis; | |
} | |
+ @Override | |
public int compareTo(MessageBundle bundle) { | |
if (mUptimeMillis < bundle.mUptimeMillis) { | |
return -1; | |
@@ -84,7 +84,12 @@ | |
public void run() { | |
Bridge.prepareThread(); | |
try { | |
+ /* FIXME: The ANIMATION_FRAME message no longer exists. Instead, the | |
+ * animation timing loop is completely based on a Choreographer objects | |
+ * that schedules animation and drawing frames. The animation handler is | |
+ * no longer even a handler; it is just a Runnable enqueued on the Choreographer. | |
Handler_Delegate.setCallback(new IHandlerCallback() { | |
+ @Override | |
public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) { | |
if (msg.what == ValueAnimator.ANIMATION_START || | |
msg.what == ValueAnimator.ANIMATION_FRAME) { | |
@@ -94,6 +99,7 @@ | |
} | |
} | |
}); | |
+ */ | |
// call out to the pre-animation work, which should start an animation or more. | |
Result result = preAnimation(); | |
diff -Nur android-15/android/animation/Animator.java android-16/android/animation/Animator.java | |
--- android-15/android/animation/Animator.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/animation/Animator.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -208,7 +208,7 @@ | |
* this call to its child objects to tell them to set up the values. A | |
* ObjectAnimator object will use the information it has about its target object | |
* and PropertyValuesHolder objects to get the start values for its properties. | |
- * An ValueAnimator object will ignore the request since it does not have enough | |
+ * A ValueAnimator object will ignore the request since it does not have enough | |
* information (such as a target object) to gather these values. | |
*/ | |
public void setupStartValues() { | |
@@ -220,7 +220,7 @@ | |
* this call to its child objects to tell them to set up the values. A | |
* ObjectAnimator object will use the information it has about its target object | |
* and PropertyValuesHolder objects to get the start values for its properties. | |
- * An ValueAnimator object will ignore the request since it does not have enough | |
+ * A ValueAnimator object will ignore the request since it does not have enough | |
* information (such as a target object) to gather these values. | |
*/ | |
public void setupEndValues() { | |
diff -Nur android-15/android/animation/AnimatorSet.java android-16/android/animation/AnimatorSet.java | |
--- android-15/android/animation/AnimatorSet.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/animation/AnimatorSet.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -420,11 +420,7 @@ | |
if (duration < 0) { | |
throw new IllegalArgumentException("duration must be a value of zero or greater"); | |
} | |
- for (Node node : mNodes) { | |
- // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to | |
- // insert "play-after" delays | |
- node.animation.setDuration(duration); | |
- } | |
+ // Just record the value for now - it will be used later when the AnimatorSet starts | |
mDuration = duration; | |
return this; | |
} | |
@@ -456,6 +452,14 @@ | |
mTerminated = false; | |
mStarted = true; | |
+ if (mDuration >= 0) { | |
+ // If the duration was set on this AnimatorSet, pass it along to all child animations | |
+ for (Node node : mNodes) { | |
+ // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to | |
+ // insert "play-after" delays | |
+ node.animation.setDuration(mDuration); | |
+ } | |
+ } | |
// First, sort the nodes (if necessary). This will ensure that sortedNodes | |
// contains the animation nodes in the correct order. | |
sortNodes(); | |
diff -Nur android-15/android/animation/EventsTest.java android-16/android/animation/EventsTest.java | |
--- android-15/android/animation/EventsTest.java 2012-06-18 20:00:44.000000000 +0900 | |
+++ android-16/android/animation/EventsTest.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -173,8 +173,7 @@ | |
// This should only be called on an animation that has been started and not | |
// yet canceled or ended | |
assertFalse(mCanceled); | |
- assertTrue(mRunning); | |
- assertTrue(mStarted); | |
+ assertTrue(mRunning || mStarted); | |
mCanceled = true; | |
} | |
@@ -182,8 +181,7 @@ | |
public void onAnimationEnd(Animator animation) { | |
// This should only be called on an animation that has been started and not | |
// yet ended | |
- assertTrue(mRunning); | |
- assertTrue(mStarted); | |
+ assertTrue(mRunning || mStarted); | |
mRunning = false; | |
mStarted = false; | |
super.onAnimationEnd(animation); | |
@@ -210,11 +208,12 @@ | |
} | |
/** | |
- * Verify that calling end on an unstarted animator does nothing. | |
+ * Verify that calling end on an unstarted animator starts/ends an animator. | |
*/ | |
@UiThreadTest | |
@SmallTest | |
public void testEnd() throws Exception { | |
+ mRunning = true; // end() implicitly starts an unstarted animator | |
mAnimator.end(); | |
} | |
@@ -496,6 +495,7 @@ | |
mRunning = true; | |
mAnimator.start(); | |
mAnimator.end(); | |
+ mRunning = true; // end() implicitly starts an unstarted animator | |
mAnimator.end(); | |
mFuture.release(); | |
} catch (junit.framework.AssertionFailedError e) { | |
@@ -544,6 +544,7 @@ | |
mRunning = true; | |
mAnimator.start(); | |
mAnimator.end(); | |
+ mRunning = true; // end() implicitly starts an unstarted animator | |
mAnimator.end(); | |
mFuture.release(); | |
} catch (junit.framework.AssertionFailedError e) { | |
diff -Nur android-15/android/animation/LayoutTransition.java android-16/android/animation/LayoutTransition.java | |
--- android-15/android/animation/LayoutTransition.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/animation/LayoutTransition.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -119,6 +119,24 @@ | |
public static final int DISAPPEARING = 3; | |
/** | |
+ * A flag indicating the animation that runs on those items that are changing | |
+ * due to a layout change not caused by items being added to or removed | |
+ * from the container. This transition type is not enabled by default; it can be | |
+ * enabled via {@link #enableTransitionType(int)}. | |
+ */ | |
+ public static final int CHANGING = 4; | |
+ | |
+ /** | |
+ * Private bit fields used to set the collection of enabled transition types for | |
+ * mTransitionTypes. | |
+ */ | |
+ private static final int FLAG_APPEARING = 0x01; | |
+ private static final int FLAG_DISAPPEARING = 0x02; | |
+ private static final int FLAG_CHANGE_APPEARING = 0x04; | |
+ private static final int FLAG_CHANGE_DISAPPEARING = 0x08; | |
+ private static final int FLAG_CHANGING = 0x10; | |
+ | |
+ /** | |
* These variables hold the animations that are currently used to run the transition effects. | |
* These animations are set to defaults, but can be changed to custom animations by | |
* calls to setAnimator(). | |
@@ -127,11 +145,13 @@ | |
private Animator mAppearingAnim = null; | |
private Animator mChangingAppearingAnim = null; | |
private Animator mChangingDisappearingAnim = null; | |
+ private Animator mChangingAnim = null; | |
/** | |
* These are the default animations, defined in the constructor, that will be used | |
* unless the user specifies custom animations. | |
*/ | |
+ private static ObjectAnimator defaultChange; | |
private static ObjectAnimator defaultChangeIn; | |
private static ObjectAnimator defaultChangeOut; | |
private static ObjectAnimator defaultFadeIn; | |
@@ -143,15 +163,16 @@ | |
private static long DEFAULT_DURATION = 300; | |
/** | |
- * The durations of the four different animations | |
+ * The durations of the different animations | |
*/ | |
private long mChangingAppearingDuration = DEFAULT_DURATION; | |
private long mChangingDisappearingDuration = DEFAULT_DURATION; | |
+ private long mChangingDuration = DEFAULT_DURATION; | |
private long mAppearingDuration = DEFAULT_DURATION; | |
private long mDisappearingDuration = DEFAULT_DURATION; | |
/** | |
- * The start delays of the four different animations. Note that the default behavior of | |
+ * The start delays of the different animations. Note that the default behavior of | |
* the appearing item is the default duration, since it should wait for the items to move | |
* before fading it. Same for the changing animation when disappearing; it waits for the item | |
* to fade out before moving the other items. | |
@@ -160,12 +181,14 @@ | |
private long mDisappearingDelay = 0; | |
private long mChangingAppearingDelay = 0; | |
private long mChangingDisappearingDelay = DEFAULT_DURATION; | |
+ private long mChangingDelay = 0; | |
/** | |
- * The inter-animation delays used on the two changing animations | |
+ * The inter-animation delays used on the changing animations | |
*/ | |
private long mChangingAppearingStagger = 0; | |
private long mChangingDisappearingStagger = 0; | |
+ private long mChangingStagger = 0; | |
/** | |
* The default interpolators used for the animations | |
@@ -174,6 +197,7 @@ | |
private TimeInterpolator mDisappearingInterpolator = new AccelerateDecelerateInterpolator(); | |
private TimeInterpolator mChangingAppearingInterpolator = new DecelerateInterpolator(); | |
private TimeInterpolator mChangingDisappearingInterpolator = new DecelerateInterpolator(); | |
+ private TimeInterpolator mChangingInterpolator = new DecelerateInterpolator(); | |
/** | |
* These hashmaps are used to store the animations that are currently running as part of | |
@@ -212,6 +236,13 @@ | |
private long staggerDelay; | |
/** | |
+ * These are the types of transition animations that the LayoutTransition is reacting | |
+ * to. By default, appearing/disappearing and the change animations related to them are | |
+ * enabled (not CHANGING). | |
+ */ | |
+ private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING | | |
+ FLAG_APPEARING | FLAG_DISAPPEARING; | |
+ /** | |
* The set of listeners that should be notified when APPEARING/DISAPPEARING transitions | |
* start and end. | |
*/ | |
@@ -248,6 +279,9 @@ | |
defaultChangeOut = defaultChangeIn.clone(); | |
defaultChangeOut.setStartDelay(mChangingDisappearingDelay); | |
defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator); | |
+ defaultChange = defaultChangeIn.clone(); | |
+ defaultChange.setStartDelay(mChangingDelay); | |
+ defaultChange.setInterpolator(mChangingInterpolator); | |
defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f); | |
defaultFadeIn.setDuration(DEFAULT_DURATION); | |
@@ -260,6 +294,7 @@ | |
} | |
mChangingAppearingAnim = defaultChangeIn; | |
mChangingDisappearingAnim = defaultChangeOut; | |
+ mChangingAnim = defaultChange; | |
mAppearingAnim = defaultFadeIn; | |
mDisappearingAnim = defaultFadeOut; | |
} | |
@@ -275,18 +310,101 @@ | |
public void setDuration(long duration) { | |
mChangingAppearingDuration = duration; | |
mChangingDisappearingDuration = duration; | |
+ mChangingDuration = duration; | |
mAppearingDuration = duration; | |
mDisappearingDuration = duration; | |
} | |
/** | |
+ * Enables the specified transitionType for this LayoutTransition object. | |
+ * By default, a LayoutTransition listens for changes in children being | |
+ * added/remove/hidden/shown in the container, and runs the animations associated with | |
+ * those events. That is, all transition types besides {@link #CHANGING} are enabled by default. | |
+ * You can also enable {@link #CHANGING} animations by calling this method with the | |
+ * {@link #CHANGING} transitionType. | |
+ * | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}. | |
+ */ | |
+ public void enableTransitionType(int transitionType) { | |
+ switch (transitionType) { | |
+ case APPEARING: | |
+ mTransitionTypes |= FLAG_APPEARING; | |
+ break; | |
+ case DISAPPEARING: | |
+ mTransitionTypes |= FLAG_DISAPPEARING; | |
+ break; | |
+ case CHANGE_APPEARING: | |
+ mTransitionTypes |= FLAG_CHANGE_APPEARING; | |
+ break; | |
+ case CHANGE_DISAPPEARING: | |
+ mTransitionTypes |= FLAG_CHANGE_DISAPPEARING; | |
+ break; | |
+ case CHANGING: | |
+ mTransitionTypes |= FLAG_CHANGING; | |
+ break; | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Disables the specified transitionType for this LayoutTransition object. | |
+ * By default, all transition types except {@link #CHANGING} are enabled. | |
+ * | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}. | |
+ */ | |
+ public void disableTransitionType(int transitionType) { | |
+ switch (transitionType) { | |
+ case APPEARING: | |
+ mTransitionTypes &= ~FLAG_APPEARING; | |
+ break; | |
+ case DISAPPEARING: | |
+ mTransitionTypes &= ~FLAG_DISAPPEARING; | |
+ break; | |
+ case CHANGE_APPEARING: | |
+ mTransitionTypes &= ~FLAG_CHANGE_APPEARING; | |
+ break; | |
+ case CHANGE_DISAPPEARING: | |
+ mTransitionTypes &= ~FLAG_CHANGE_DISAPPEARING; | |
+ break; | |
+ case CHANGING: | |
+ mTransitionTypes &= ~FLAG_CHANGING; | |
+ break; | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Returns whether the specified transitionType is enabled for this LayoutTransition object. | |
+ * By default, all transition types except {@link #CHANGING} are enabled. | |
+ * | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}. | |
+ * @return true if the specified transitionType is currently enabled, false otherwise. | |
+ */ | |
+ public boolean isTransitionTypeEnabled(int transitionType) { | |
+ switch (transitionType) { | |
+ case APPEARING: | |
+ return (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING; | |
+ case DISAPPEARING: | |
+ return (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING; | |
+ case CHANGE_APPEARING: | |
+ return (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING; | |
+ case CHANGE_DISAPPEARING: | |
+ return (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING; | |
+ case CHANGING: | |
+ return (mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING; | |
+ } | |
+ return false; | |
+ } | |
+ | |
+ /** | |
* Sets the start delay on one of the animation objects used by this transition. The | |
* <code>transitionType</code> parameter determines the animation whose start delay | |
* is being set. | |
* | |
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start | |
- * delay is being set. | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines | |
+ * the animation whose start delay is being set. | |
* @param delay The length of time, in milliseconds, to delay before starting the animation. | |
* @see Animator#setStartDelay(long) | |
*/ | |
@@ -298,6 +416,9 @@ | |
case CHANGE_DISAPPEARING: | |
mChangingDisappearingDelay = delay; | |
break; | |
+ case CHANGING: | |
+ mChangingDelay = delay; | |
+ break; | |
case APPEARING: | |
mAppearingDelay = delay; | |
break; | |
@@ -312,22 +433,24 @@ | |
* <code>transitionType</code> parameter determines the animation whose start delay | |
* is returned. | |
* | |
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose start | |
- * delay is returned. | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines | |
+ * the animation whose start delay is returned. | |
* @return long The start delay of the specified animation. | |
* @see Animator#getStartDelay() | |
*/ | |
public long getStartDelay(int transitionType) { | |
switch (transitionType) { | |
case CHANGE_APPEARING: | |
- return mChangingAppearingDuration; | |
+ return mChangingAppearingDelay; | |
case CHANGE_DISAPPEARING: | |
- return mChangingDisappearingDuration; | |
+ return mChangingDisappearingDelay; | |
+ case CHANGING: | |
+ return mChangingDelay; | |
case APPEARING: | |
- return mAppearingDuration; | |
+ return mAppearingDelay; | |
case DISAPPEARING: | |
- return mDisappearingDuration; | |
+ return mDisappearingDelay; | |
} | |
// shouldn't reach here | |
return 0; | |
@@ -338,9 +461,9 @@ | |
* <code>transitionType</code> parameter determines the animation whose duration | |
* is being set. | |
* | |
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose | |
- * duration is being set. | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines | |
+ * the animation whose duration is being set. | |
* @param duration The length of time, in milliseconds, that the specified animation should run. | |
* @see Animator#setDuration(long) | |
*/ | |
@@ -352,6 +475,9 @@ | |
case CHANGE_DISAPPEARING: | |
mChangingDisappearingDuration = duration; | |
break; | |
+ case CHANGING: | |
+ mChangingDuration = duration; | |
+ break; | |
case APPEARING: | |
mAppearingDuration = duration; | |
break; | |
@@ -366,9 +492,9 @@ | |
* <code>transitionType</code> parameter determines the animation whose duration | |
* is returned. | |
* | |
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose | |
- * duration is returned. | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines | |
+ * the animation whose duration is returned. | |
* @return long The duration of the specified animation. | |
* @see Animator#getDuration() | |
*/ | |
@@ -378,6 +504,8 @@ | |
return mChangingAppearingDuration; | |
case CHANGE_DISAPPEARING: | |
return mChangingDisappearingDuration; | |
+ case CHANGING: | |
+ return mChangingDuration; | |
case APPEARING: | |
return mAppearingDuration; | |
case DISAPPEARING: | |
@@ -389,9 +517,10 @@ | |
/** | |
* Sets the length of time to delay between starting each animation during one of the | |
- * CHANGE animations. | |
+ * change animations. | |
* | |
- * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}. | |
+ * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or | |
+ * {@link #CHANGING}. | |
* @param duration The length of time, in milliseconds, to delay before launching the next | |
* animation in the sequence. | |
*/ | |
@@ -403,15 +532,19 @@ | |
case CHANGE_DISAPPEARING: | |
mChangingDisappearingStagger = duration; | |
break; | |
+ case CHANGING: | |
+ mChangingStagger = duration; | |
+ break; | |
// noop other cases | |
} | |
} | |
/** | |
- * Tets the length of time to delay between starting each animation during one of the | |
- * CHANGE animations. | |
+ * Gets the length of time to delay between starting each animation during one of the | |
+ * change animations. | |
* | |
- * @param transitionType A value of {@link #CHANGE_APPEARING} or @link #CHANGE_DISAPPEARING}. | |
+ * @param transitionType A value of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, or | |
+ * {@link #CHANGING}. | |
* @return long The length of time, in milliseconds, to delay before launching the next | |
* animation in the sequence. | |
*/ | |
@@ -421,6 +554,8 @@ | |
return mChangingAppearingStagger; | |
case CHANGE_DISAPPEARING: | |
return mChangingDisappearingStagger; | |
+ case CHANGING: | |
+ return mChangingStagger; | |
} | |
// shouldn't reach here | |
return 0; | |
@@ -431,9 +566,9 @@ | |
* <code>transitionType</code> parameter determines the animation whose interpolator | |
* is being set. | |
* | |
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose | |
- * duration is being set. | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines | |
+ * the animation whose interpolator is being set. | |
* @param interpolator The interpolator that the specified animation should use. | |
* @see Animator#setInterpolator(TimeInterpolator) | |
*/ | |
@@ -445,6 +580,9 @@ | |
case CHANGE_DISAPPEARING: | |
mChangingDisappearingInterpolator = interpolator; | |
break; | |
+ case CHANGING: | |
+ mChangingInterpolator = interpolator; | |
+ break; | |
case APPEARING: | |
mAppearingInterpolator = interpolator; | |
break; | |
@@ -459,9 +597,9 @@ | |
* <code>transitionType</code> parameter determines the animation whose interpolator | |
* is returned. | |
* | |
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose | |
- * duration is being set. | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines | |
+ * the animation whose interpolator is being returned. | |
* @return TimeInterpolator The interpolator that the specified animation uses. | |
* @see Animator#setInterpolator(TimeInterpolator) | |
*/ | |
@@ -471,6 +609,8 @@ | |
return mChangingAppearingInterpolator; | |
case CHANGE_DISAPPEARING: | |
return mChangingDisappearingInterpolator; | |
+ case CHANGING: | |
+ return mChangingInterpolator; | |
case APPEARING: | |
return mAppearingInterpolator; | |
case DISAPPEARING: | |
@@ -504,9 +644,9 @@ | |
* values queried when the transition begins may need to use a different mechanism | |
* than a standard ObjectAnimator object.</p> | |
* | |
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose | |
- * duration is being set. | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the | |
+ * animation whose animator is being set. | |
* @param animator The animation being assigned. A value of <code>null</code> means that no | |
* animation will be run for the specified transitionType. | |
*/ | |
@@ -518,6 +658,9 @@ | |
case CHANGE_DISAPPEARING: | |
mChangingDisappearingAnim = animator; | |
break; | |
+ case CHANGING: | |
+ mChangingAnim = animator; | |
+ break; | |
case APPEARING: | |
mAppearingAnim = animator; | |
break; | |
@@ -530,9 +673,9 @@ | |
/** | |
* Gets the animation used during one of the transition types that may run. | |
* | |
- * @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
- * {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose | |
- * duration is being set. | |
+ * @param transitionType One of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING}, | |
+ * {@link #CHANGING}, {@link #APPEARING}, or {@link #DISAPPEARING}, which determines | |
+ * the animation whose animator is being returned. | |
* @return Animator The animation being used for the given transition type. | |
* @see #setAnimator(int, Animator) | |
*/ | |
@@ -542,6 +685,8 @@ | |
return mChangingAppearingAnim; | |
case CHANGE_DISAPPEARING: | |
return mChangingDisappearingAnim; | |
+ case CHANGING: | |
+ return mChangingAnim; | |
case APPEARING: | |
return mAppearingAnim; | |
case DISAPPEARING: | |
@@ -554,20 +699,44 @@ | |
/** | |
* This function sets up animations on all of the views that change during layout. | |
* For every child in the parent, we create a change animation of the appropriate | |
- * type (appearing or disappearing) and ask it to populate its start values from its | |
+ * type (appearing, disappearing, or changing) and ask it to populate its start values from its | |
* target view. We add layout listeners to all child views and listen for changes. For | |
* those views that change, we populate the end values for those animations and start them. | |
* Animations are not run on unchanging views. | |
* | |
- * @param parent The container which is undergoing an appearing or disappearing change. | |
- * @param newView The view being added to or removed from the parent. | |
- * @param changeReason A value of APPEARING or DISAPPEARING, indicating whether the | |
- * transition is occuring because an item is being added to or removed from the parent. | |
+ * @param parent The container which is undergoing a change. | |
+ * @param newView The view being added to or removed from the parent. May be null if the | |
+ * changeReason is CHANGING. | |
+ * @param changeReason A value of APPEARING, DISAPPEARING, or CHANGING, indicating whether the | |
+ * transition is occurring because an item is being added to or removed from the parent, or | |
+ * if it is running in response to a layout operation (that is, if the value is CHANGING). | |
*/ | |
private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) { | |
- Animator baseAnimator = (changeReason == APPEARING) ? | |
- mChangingAppearingAnim : mChangingDisappearingAnim; | |
+ Animator baseAnimator = null; | |
+ Animator parentAnimator = null; | |
+ final long duration; | |
+ switch (changeReason) { | |
+ case APPEARING: | |
+ baseAnimator = mChangingAppearingAnim; | |
+ duration = mChangingAppearingDuration; | |
+ parentAnimator = defaultChangeIn; | |
+ break; | |
+ case DISAPPEARING: | |
+ baseAnimator = mChangingDisappearingAnim; | |
+ duration = mChangingDisappearingDuration; | |
+ parentAnimator = defaultChangeOut; | |
+ break; | |
+ case CHANGING: | |
+ baseAnimator = mChangingAnim; | |
+ duration = mChangingDuration; | |
+ parentAnimator = defaultChange; | |
+ break; | |
+ default: | |
+ // Shouldn't reach here | |
+ duration = 0; | |
+ break; | |
+ } | |
// If the animation is null, there's nothing to do | |
if (baseAnimator == null) { | |
return; | |
@@ -575,8 +744,6 @@ | |
// reset the inter-animation delay, in case we use it later | |
staggerDelay = 0; | |
- final long duration = (changeReason == APPEARING) ? | |
- mChangingAppearingDuration : mChangingDisappearingDuration; | |
final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup | |
if (!observer.isAlive()) { | |
@@ -594,8 +761,6 @@ | |
} | |
} | |
if (mAnimateParentHierarchy) { | |
- Animator parentAnimator = (changeReason == APPEARING) ? | |
- defaultChangeIn : defaultChangeOut; | |
ViewGroup tempParent = parent; | |
while (tempParent != null) { | |
ViewParent parentParent = tempParent.getParent(); | |
@@ -666,6 +831,14 @@ | |
return; | |
} | |
+ // Don't animate items up from size(0,0); this is likely because the objects | |
+ // were offscreen/invisible or otherwise measured to be infinitely small. We don't | |
+ // want to see them animate into their real size; just ignore animation requests | |
+ // on these views | |
+ if (child.getWidth() == 0 && child.getHeight() == 0) { | |
+ return; | |
+ } | |
+ | |
// Make a copy of the appropriate animation | |
final Animator anim = baseAnimator.clone(); | |
@@ -727,13 +900,20 @@ | |
} | |
} | |
- long startDelay; | |
- if (changeReason == APPEARING) { | |
- startDelay = mChangingAppearingDelay + staggerDelay; | |
- staggerDelay += mChangingAppearingStagger; | |
- } else { | |
- startDelay = mChangingDisappearingDelay + staggerDelay; | |
- staggerDelay += mChangingDisappearingStagger; | |
+ long startDelay = 0; | |
+ switch (changeReason) { | |
+ case APPEARING: | |
+ startDelay = mChangingAppearingDelay + staggerDelay; | |
+ staggerDelay += mChangingAppearingStagger; | |
+ break; | |
+ case DISAPPEARING: | |
+ startDelay = mChangingDisappearingDelay + staggerDelay; | |
+ staggerDelay += mChangingDisappearingStagger; | |
+ break; | |
+ case CHANGING: | |
+ startDelay = mChangingDelay + staggerDelay; | |
+ staggerDelay += mChangingStagger; | |
+ break; | |
} | |
anim.setStartDelay(startDelay); | |
anim.setDuration(duration); | |
@@ -762,11 +942,14 @@ | |
@Override | |
public void onAnimationStart(Animator animator) { | |
- if (mListeners != null) { | |
- for (TransitionListener listener : mListeners) { | |
+ if (hasListeners()) { | |
+ ArrayList<TransitionListener> listeners = | |
+ (ArrayList<TransitionListener>) mListeners.clone(); | |
+ for (TransitionListener listener : listeners) { | |
listener.startTransition(LayoutTransition.this, parent, child, | |
changeReason == APPEARING ? | |
- CHANGE_APPEARING : CHANGE_DISAPPEARING); | |
+ CHANGE_APPEARING : changeReason == DISAPPEARING ? | |
+ CHANGE_DISAPPEARING : CHANGING); | |
} | |
} | |
} | |
@@ -780,11 +963,14 @@ | |
@Override | |
public void onAnimationEnd(Animator animator) { | |
currentChangingAnimations.remove(child); | |
- if (mListeners != null) { | |
- for (TransitionListener listener : mListeners) { | |
+ if (hasListeners()) { | |
+ ArrayList<TransitionListener> listeners = | |
+ (ArrayList<TransitionListener>) mListeners.clone(); | |
+ for (TransitionListener listener : listeners) { | |
listener.endTransition(LayoutTransition.this, parent, child, | |
changeReason == APPEARING ? | |
- CHANGE_APPEARING : CHANGE_DISAPPEARING); | |
+ CHANGE_APPEARING : changeReason == DISAPPEARING ? | |
+ CHANGE_DISAPPEARING : CHANGING); | |
} | |
} | |
} | |
@@ -831,6 +1017,8 @@ | |
anim.start(); | |
anim.end(); | |
} | |
+ // listeners should clean up the currentChangingAnimations list, but just in case... | |
+ currentChangingAnimations.clear(); | |
} | |
/** | |
@@ -902,6 +1090,7 @@ | |
switch (transitionType) { | |
case CHANGE_APPEARING: | |
case CHANGE_DISAPPEARING: | |
+ case CHANGING: | |
if (currentChangingAnimations.size() > 0) { | |
LinkedHashMap<View, Animator> currentAnimCopy = | |
(LinkedHashMap<View, Animator>) currentChangingAnimations.clone(); | |
@@ -946,8 +1135,10 @@ | |
currentAnimation.cancel(); | |
} | |
if (mAppearingAnim == null) { | |
- if (mListeners != null) { | |
- for (TransitionListener listener : mListeners) { | |
+ if (hasListeners()) { | |
+ ArrayList<TransitionListener> listeners = | |
+ (ArrayList<TransitionListener>) mListeners.clone(); | |
+ for (TransitionListener listener : listeners) { | |
listener.endTransition(LayoutTransition.this, parent, child, APPEARING); | |
} | |
} | |
@@ -960,17 +1151,19 @@ | |
if (anim instanceof ObjectAnimator) { | |
((ObjectAnimator) anim).setCurrentPlayTime(0); | |
} | |
- if (mListeners != null) { | |
- anim.addListener(new AnimatorListenerAdapter() { | |
- @Override | |
- public void onAnimationEnd(Animator anim) { | |
- currentAppearingAnimations.remove(child); | |
- for (TransitionListener listener : mListeners) { | |
+ anim.addListener(new AnimatorListenerAdapter() { | |
+ @Override | |
+ public void onAnimationEnd(Animator anim) { | |
+ currentAppearingAnimations.remove(child); | |
+ if (hasListeners()) { | |
+ ArrayList<TransitionListener> listeners = | |
+ (ArrayList<TransitionListener>) mListeners.clone(); | |
+ for (TransitionListener listener : listeners) { | |
listener.endTransition(LayoutTransition.this, parent, child, APPEARING); | |
} | |
} | |
- }); | |
- } | |
+ } | |
+ }); | |
currentAppearingAnimations.put(child, anim); | |
anim.start(); | |
} | |
@@ -987,8 +1180,10 @@ | |
currentAnimation.cancel(); | |
} | |
if (mDisappearingAnim == null) { | |
- if (mListeners != null) { | |
- for (TransitionListener listener : mListeners) { | |
+ if (hasListeners()) { | |
+ ArrayList<TransitionListener> listeners = | |
+ (ArrayList<TransitionListener>) mListeners.clone(); | |
+ for (TransitionListener listener : listeners) { | |
listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); | |
} | |
} | |
@@ -998,17 +1193,21 @@ | |
anim.setStartDelay(mDisappearingDelay); | |
anim.setDuration(mDisappearingDuration); | |
anim.setTarget(child); | |
- if (mListeners != null) { | |
- anim.addListener(new AnimatorListenerAdapter() { | |
- @Override | |
- public void onAnimationEnd(Animator anim) { | |
- currentDisappearingAnimations.remove(child); | |
- for (TransitionListener listener : mListeners) { | |
+ final float preAnimAlpha = child.getAlpha(); | |
+ anim.addListener(new AnimatorListenerAdapter() { | |
+ @Override | |
+ public void onAnimationEnd(Animator anim) { | |
+ currentDisappearingAnimations.remove(child); | |
+ child.setAlpha(preAnimAlpha); | |
+ if (hasListeners()) { | |
+ ArrayList<TransitionListener> listeners = | |
+ (ArrayList<TransitionListener>) mListeners.clone(); | |
+ for (TransitionListener listener : listeners) { | |
listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING); | |
} | |
} | |
- }); | |
- } | |
+ } | |
+ }); | |
if (anim instanceof ObjectAnimator) { | |
((ObjectAnimator) anim).setCurrentPlayTime(0); | |
} | |
@@ -1024,19 +1223,65 @@ | |
* | |
* @param parent The ViewGroup to which the View is being added. | |
* @param child The View being added to the ViewGroup. | |
+ * @param changesLayout Whether the removal will cause changes in the layout of other views | |
+ * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not | |
+ * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations. | |
*/ | |
- public void addChild(ViewGroup parent, View child) { | |
- // Want disappearing animations to finish up before proceeding | |
- cancel(DISAPPEARING); | |
- // Also, cancel changing animations so that we start fresh ones from current locations | |
- cancel(CHANGE_APPEARING); | |
- if (mListeners != null) { | |
- for (TransitionListener listener : mListeners) { | |
+ private void addChild(ViewGroup parent, View child, boolean changesLayout) { | |
+ if (parent.getWindowVisibility() != View.VISIBLE) { | |
+ return; | |
+ } | |
+ if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) { | |
+ // Want disappearing animations to finish up before proceeding | |
+ cancel(DISAPPEARING); | |
+ } | |
+ if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) { | |
+ // Also, cancel changing animations so that we start fresh ones from current locations | |
+ cancel(CHANGE_APPEARING); | |
+ cancel(CHANGING); | |
+ } | |
+ if (hasListeners() && (mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) { | |
+ ArrayList<TransitionListener> listeners = | |
+ (ArrayList<TransitionListener>) mListeners.clone(); | |
+ for (TransitionListener listener : listeners) { | |
listener.startTransition(this, parent, child, APPEARING); | |
} | |
} | |
- runChangeTransition(parent, child, APPEARING); | |
- runAppearingTransition(parent, child); | |
+ if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) { | |
+ runChangeTransition(parent, child, APPEARING); | |
+ } | |
+ if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) { | |
+ runAppearingTransition(parent, child); | |
+ } | |
+ } | |
+ | |
+ private boolean hasListeners() { | |
+ return mListeners != null && mListeners.size() > 0; | |
+ } | |
+ | |
+ /** | |
+ * This method is called by ViewGroup when there is a call to layout() on the container | |
+ * with this LayoutTransition. If the CHANGING transition is enabled and if there is no other | |
+ * transition currently running on the container, then this call runs a CHANGING transition. | |
+ * The transition does not start immediately; it just sets up the mechanism to run if any | |
+ * of the children of the container change their layout parameters (similar to | |
+ * the CHANGE_APPEARING and CHANGE_DISAPPEARING transitions). | |
+ * | |
+ * @param parent The ViewGroup whose layout() method has been called. | |
+ * | |
+ * @hide | |
+ */ | |
+ public void layoutChange(ViewGroup parent) { | |
+ if (parent.getWindowVisibility() != View.VISIBLE) { | |
+ return; | |
+ } | |
+ if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING && !isRunning()) { | |
+ // This method is called for all calls to layout() in the container, including | |
+ // those caused by add/remove/hide/show events, which will already have set up | |
+ // transition animations. Avoid setting up CHANGING animations in this case; only | |
+ // do so when there is not a transition already running on the container. | |
+ runChangeTransition(parent, null, CHANGING); | |
+ } | |
} | |
/** | |
@@ -1048,8 +1293,31 @@ | |
* @param parent The ViewGroup to which the View is being added. | |
* @param child The View being added to the ViewGroup. | |
*/ | |
+ public void addChild(ViewGroup parent, View child) { | |
+ addChild(parent, child, true); | |
+ } | |
+ | |
+ /** | |
+ * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}. | |
+ */ | |
+ @Deprecated | |
public void showChild(ViewGroup parent, View child) { | |
- addChild(parent, child); | |
+ addChild(parent, child, true); | |
+ } | |
+ | |
+ /** | |
+ * This method is called by ViewGroup when a child view is about to be made visible in the | |
+ * container. This callback starts the process of a transition; we grab the starting | |
+ * values, listen for changes to all of the children of the container, and start appropriate | |
+ * animations. | |
+ * | |
+ * @param parent The ViewGroup in which the View is being made visible. | |
+ * @param child The View being made visible. | |
+ * @param oldVisibility The previous visibility value of the child View, either | |
+ * {@link View#GONE} or {@link View#INVISIBLE}. | |
+ */ | |
+ public void showChild(ViewGroup parent, View child, int oldVisibility) { | |
+ addChild(parent, child, oldVisibility == View.GONE); | |
} | |
/** | |
@@ -1060,19 +1328,38 @@ | |
* | |
* @param parent The ViewGroup from which the View is being removed. | |
* @param child The View being removed from the ViewGroup. | |
+ * @param changesLayout Whether the removal will cause changes in the layout of other views | |
+ * in the container. Views becoming INVISIBLE will not cause changes and thus will not | |
+ * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations. | |
*/ | |
- public void removeChild(ViewGroup parent, View child) { | |
- // Want appearing animations to finish up before proceeding | |
- cancel(APPEARING); | |
- // Also, cancel changing animations so that we start fresh ones from current locations | |
- cancel(CHANGE_DISAPPEARING); | |
- if (mListeners != null) { | |
- for (TransitionListener listener : mListeners) { | |
+ private void removeChild(ViewGroup parent, View child, boolean changesLayout) { | |
+ if (parent.getWindowVisibility() != View.VISIBLE) { | |
+ return; | |
+ } | |
+ if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) { | |
+ // Want appearing animations to finish up before proceeding | |
+ cancel(APPEARING); | |
+ } | |
+ if (changesLayout && | |
+ (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) { | |
+ // Also, cancel changing animations so that we start fresh ones from current locations | |
+ cancel(CHANGE_DISAPPEARING); | |
+ cancel(CHANGING); | |
+ } | |
+ if (hasListeners() && (mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) { | |
+ ArrayList<TransitionListener> listeners = (ArrayList<TransitionListener>) mListeners | |
+ .clone(); | |
+ for (TransitionListener listener : listeners) { | |
listener.startTransition(this, parent, child, DISAPPEARING); | |
} | |
} | |
- runChangeTransition(parent, child, DISAPPEARING); | |
- runDisappearingTransition(parent, child); | |
+ if (changesLayout && | |
+ (mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) { | |
+ runChangeTransition(parent, child, DISAPPEARING); | |
+ } | |
+ if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) { | |
+ runDisappearingTransition(parent, child); | |
+ } | |
} | |
/** | |
@@ -1084,8 +1371,31 @@ | |
* @param parent The ViewGroup from which the View is being removed. | |
* @param child The View being removed from the ViewGroup. | |
*/ | |
+ public void removeChild(ViewGroup parent, View child) { | |
+ removeChild(parent, child, true); | |
+ } | |
+ | |
+ /** | |
+ * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}. | |
+ */ | |
+ @Deprecated | |
public void hideChild(ViewGroup parent, View child) { | |
- removeChild(parent, child); | |
+ removeChild(parent, child, true); | |
+ } | |
+ | |
+ /** | |
+ * This method is called by ViewGroup when a child view is about to be hidden in | |
+ * container. This callback starts the process of a transition; we grab the starting | |
+ * values, listen for changes to all of the children of the container, and start appropriate | |
+ * animations. | |
+ * | |
+ * @param parent The parent ViewGroup of the View being hidden. | |
+ * @param child The View being hidden. | |
+ * @param newVisibility The new visibility value of the child View, either | |
+ * {@link View#GONE} or {@link View#INVISIBLE}. | |
+ */ | |
+ public void hideChild(ViewGroup parent, View child, int newVisibility) { | |
+ removeChild(parent, child, newVisibility == View.GONE); | |
} | |
/** | |
diff -Nur android-15/android/animation/PropertyValuesHolder.java android-16/android/animation/PropertyValuesHolder.java | |
--- android-15/android/animation/PropertyValuesHolder.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/animation/PropertyValuesHolder.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -384,8 +384,7 @@ | |
try { | |
returnVal = targetClass.getMethod(methodName, args); | |
} catch (NoSuchMethodException e) { | |
- Log.e("PropertyValuesHolder", | |
- "Couldn't find no-arg method for property " + mPropertyName + ": " + e); | |
+ // Swallow the error, log it later | |
} | |
} else { | |
args = new Class[1]; | |
@@ -412,9 +411,12 @@ | |
} | |
} | |
// If we got here, then no appropriate function was found | |
- Log.e("PropertyValuesHolder", | |
- "Couldn't find setter/getter for property " + mPropertyName + | |
- " with value type "+ mValueType); | |
+ } | |
+ | |
+ if (returnVal == null) { | |
+ Log.w("PropertyValuesHolder", "Method " + | |
+ getMethodName(prefix, mPropertyName) + "() with type " + mValueType + | |
+ " not found on target class " + targetClass); | |
} | |
return returnVal; | |
@@ -495,7 +497,7 @@ | |
} | |
return; | |
} catch (ClassCastException e) { | |
- Log.e("PropertyValuesHolder","No such property (" + mProperty.getName() + | |
+ Log.w("PropertyValuesHolder","No such property (" + mProperty.getName() + | |
") on target object " + target + ". Trying reflection instead"); | |
mProperty = null; | |
} | |
@@ -508,6 +510,10 @@ | |
if (!kf.hasValue()) { | |
if (mGetter == null) { | |
setupGetter(targetClass); | |
+ if (mGetter == null) { | |
+ // Already logged the error - just return to avoid NPE | |
+ return; | |
+ } | |
} | |
try { | |
kf.setValue(mGetter.invoke(target)); | |
@@ -535,6 +541,10 @@ | |
if (mGetter == null) { | |
Class targetClass = target.getClass(); | |
setupGetter(targetClass); | |
+ if (mGetter == null) { | |
+ // Already logged the error - just return to avoid NPE | |
+ return; | |
+ } | |
} | |
kf.setValue(mGetter.invoke(target)); | |
} catch (InvocationTargetException e) { | |
@@ -854,8 +864,9 @@ | |
} | |
} | |
} catch (NoSuchMethodError e) { | |
- Log.d("PropertyValuesHolder", | |
- "Can't find native method using JNI, use reflection" + e); | |
+ // Couldn't find it via JNI - try reflection next. Probably means the method | |
+ // doesn't exist, or the type is wrong. An error will be logged later if | |
+ // reflection fails as well. | |
} finally { | |
mPropertyMapLock.writeLock().unlock(); | |
} | |
@@ -990,8 +1001,9 @@ | |
} | |
} | |
} catch (NoSuchMethodError e) { | |
- Log.d("PropertyValuesHolder", | |
- "Can't find native method using JNI, use reflection" + e); | |
+ // Couldn't find it via JNI - try reflection next. Probably means the method | |
+ // doesn't exist, or the type is wrong. An error will be logged later if | |
+ // reflection fails as well. | |
} finally { | |
mPropertyMapLock.writeLock().unlock(); | |
} | |
diff -Nur android-15/android/animation/TimeAnimator.java android-16/android/animation/TimeAnimator.java | |
--- android-15/android/animation/TimeAnimator.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/animation/TimeAnimator.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -1,13 +1,11 @@ | |
package android.animation; | |
/** | |
- * This class provides a simple callback mechanism to listeners that is synchronized with other | |
- * animators in the system. There is no duration, interpolation, or object value-setting | |
- * with this Animator. Instead, it is simply started and proceeds to send out events on every | |
- * animation frame to its TimeListener (if set), with information about this animator, | |
- * the total elapsed time, and the time since the last animation frame. | |
- * | |
- * @hide | |
+ * This class provides a simple callback mechanism to listeners that is synchronized with all | |
+ * other animators in the system. There is no duration, interpolation, or object value-setting | |
+ * with this Animator. Instead, it is simply started, after which it proceeds to send out events | |
+ * on every animation frame to its TimeListener (if set), with information about this animator, | |
+ * the total elapsed time, and the elapsed time since the previous animation frame. | |
*/ | |
public class TimeAnimator extends ValueAnimator { | |
@@ -16,16 +14,6 @@ | |
@Override | |
boolean animationFrame(long currentTime) { | |
- if (mPlayingState == STOPPED) { | |
- mPlayingState = RUNNING; | |
- if (mSeekTime < 0) { | |
- mStartTime = currentTime; | |
- } else { | |
- mStartTime = currentTime - mSeekTime; | |
- // Now that we're playing, reset the seek time | |
- mSeekTime = -1; | |
- } | |
- } | |
if (mListener != null) { | |
long totalTime = currentTime - mStartTime; | |
long deltaTime = (mPreviousTime < 0) ? 0 : (currentTime - mPreviousTime); | |
@@ -59,10 +47,10 @@ | |
* Implementors of this interface can set themselves as update listeners | |
* to a <code>TimeAnimator</code> instance to receive callbacks on every animation | |
* frame to receive the total time since the animator started and the delta time | |
- * since the last frame. The first time the listener is called, totalTime and | |
- * deltaTime should both be zero. | |
- * | |
- * @hide | |
+ * since the last frame. The first time the listener is called, | |
+ * deltaTime will be zero. The same is true for totalTime, unless the animator was | |
+ * set to a specific {@link ValueAnimator#setCurrentPlayTime(long) currentPlayTime} | |
+ * prior to starting. | |
*/ | |
public static interface TimeListener { | |
/** | |
@@ -70,7 +58,8 @@ | |
* along with information about the elapsed time.</p> | |
* | |
* @param animation The animator sending out the notification. | |
- * @param totalTime The | |
+ * @param totalTime The total time elapsed since the animator started, in milliseconds. | |
+ * @param deltaTime The time elapsed since the previous frame, in milliseconds. | |
*/ | |
void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime); | |
diff -Nur android-15/android/animation/ValueAnimator.java android-16/android/animation/ValueAnimator.java | |
--- android-15/android/animation/ValueAnimator.java 2012-06-18 20:00:45.000000000 +0900 | |
+++ android-16/android/animation/ValueAnimator.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -19,7 +19,9 @@ | |
import android.os.Handler; | |
import android.os.Looper; | |
import android.os.Message; | |
+import android.os.SystemProperties; | |
import android.util.AndroidRuntimeException; | |
+import android.view.Choreographer; | |
import android.view.animation.AccelerateDecelerateInterpolator; | |
import android.view.animation.AnimationUtils; | |
import android.view.animation.LinearInterpolator; | |
@@ -51,18 +53,7 @@ | |
/** | |
* Internal constants | |
*/ | |
- | |
- /* | |
- * The default amount of time in ms between animation frames | |
- */ | |
- private static final long DEFAULT_FRAME_DELAY = 10; | |
- | |
- /** | |
- * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent | |
- * by the handler to itself to process the next animation frame | |
- */ | |
- static final int ANIMATION_START = 0; | |
- static final int ANIMATION_FRAME = 1; | |
+ private static float sDurationScale = 1.0f; | |
/** | |
* Values used with internal variable mPlayingState to indicate the current state of an | |
@@ -90,70 +81,15 @@ | |
*/ | |
long mSeekTime = -1; | |
- // TODO: We access the following ThreadLocal variables often, some of them on every update. | |
- // If ThreadLocal access is significantly expensive, we may want to put all of these | |
- // fields into a structure sot hat we just access ThreadLocal once to get the reference | |
- // to that structure, then access the structure directly for each field. | |
- | |
// The static sAnimationHandler processes the internal timing loop on which all animations | |
// are based | |
private static ThreadLocal<AnimationHandler> sAnimationHandler = | |
new ThreadLocal<AnimationHandler>(); | |
- // The per-thread list of all active animations | |
- private static final ThreadLocal<ArrayList<ValueAnimator>> sAnimations = | |
- new ThreadLocal<ArrayList<ValueAnimator>>() { | |
- @Override | |
- protected ArrayList<ValueAnimator> initialValue() { | |
- return new ArrayList<ValueAnimator>(); | |
- } | |
- }; | |
- | |
- // The per-thread set of animations to be started on the next animation frame | |
- private static final ThreadLocal<ArrayList<ValueAnimator>> sPendingAnimations = | |
- new ThreadLocal<ArrayList<ValueAnimator>>() { | |
- @Override | |
- protected ArrayList<ValueAnimator> initialValue() { | |
- return new ArrayList<ValueAnimator>(); | |
- } | |
- }; | |
- | |
- /** | |
- * Internal per-thread collections used to avoid set collisions as animations start and end | |
- * while being processed. | |
- */ | |
- private static final ThreadLocal<ArrayList<ValueAnimator>> sDelayedAnims = | |
- new ThreadLocal<ArrayList<ValueAnimator>>() { | |
- @Override | |
- protected ArrayList<ValueAnimator> initialValue() { | |
- return new ArrayList<ValueAnimator>(); | |
- } | |
- }; | |
- | |
- private static final ThreadLocal<ArrayList<ValueAnimator>> sEndingAnims = | |
- new ThreadLocal<ArrayList<ValueAnimator>>() { | |
- @Override | |
- protected ArrayList<ValueAnimator> initialValue() { | |
- return new ArrayList<ValueAnimator>(); | |
- } | |
- }; | |
- | |
- private static final ThreadLocal<ArrayList<ValueAnimator>> sReadyAnims = | |
- new ThreadLocal<ArrayList<ValueAnimator>>() { | |
- @Override | |
- protected ArrayList<ValueAnimator> initialValue() { | |
- return new ArrayList<ValueAnimator>(); | |
- } | |
- }; | |
- | |
// The time interpolator to be used if none is set on the animation | |
private static final TimeInterpolator sDefaultInterpolator = | |
new AccelerateDecelerateInterpolator(); | |
- // type evaluators for the primitive types handled by this implementation | |
- private static final TypeEvaluator sIntEvaluator = new IntEvaluator(); | |
- private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator(); | |
- | |
/** | |
* Used to indicate whether the animation is currently playing in reverse. This causes the | |
* elapsed fraction to be inverted to calculate the appropriate values. | |
@@ -209,6 +145,13 @@ | |
private boolean mStarted = false; | |
/** | |
+ * Tracks whether we've notified listeners of the onAnimationSTart() event. This can be | |
+ * complex to keep track of since we notify listeners at different times depending on | |
+ * startDelay and whether start() was called before end(). | |
+ */ | |
+ private boolean mStartListenersCalled = false; | |
+ | |
+ /** | |
* Flag that denotes whether the animation is set up and ready to go. Used to | |
* set up animation that has not yet been started. | |
*/ | |
@@ -219,13 +162,12 @@ | |
// | |
// How long the animation should last in ms | |
- private long mDuration = 300; | |
+ private long mDuration = (long)(300 * sDurationScale); | |
+ private long mUnscaledDuration = 300; | |
// The amount of time in ms to delay starting the animation after start() is called | |
private long mStartDelay = 0; | |
- | |
- // The number of milliseconds between animation frames | |
- private static long sFrameDelay = DEFAULT_FRAME_DELAY; | |
+ private long mUnscaledStartDelay = 0; | |
// The number of times the animation will repeat. The default is 0, which means the animation | |
// will play only once | |
@@ -281,6 +223,14 @@ | |
*/ | |
public static final int INFINITE = -1; | |
+ | |
+ /** | |
+ * @hide | |
+ */ | |
+ public static void setDurationScale(float durationScale) { | |
+ sDurationScale = durationScale; | |
+ } | |
+ | |
/** | |
* Creates a new ValueAnimator object. This default constructor is primarily for | |
* use internally; the factory methods which take parameters are more generally | |
@@ -452,7 +402,7 @@ | |
/** | |
* Sets the values, per property, being animated between. This function is called internally | |
- * by the constructors of ValueAnimator that take a list of values. But an ValueAnimator can | |
+ * by the constructors of ValueAnimator that take a list of values. But a ValueAnimator can | |
* be constructed without values and this method can be called to set the values manually | |
* instead. | |
* | |
@@ -517,7 +467,8 @@ | |
throw new IllegalArgumentException("Animators cannot have negative duration: " + | |
duration); | |
} | |
- mDuration = duration; | |
+ mUnscaledDuration = duration; | |
+ mDuration = (long)(duration * sDurationScale); | |
return this; | |
} | |
@@ -527,7 +478,7 @@ | |
* @return The length of the animation, in milliseconds. | |
*/ | |
public long getDuration() { | |
- return mDuration; | |
+ return mUnscaledDuration; | |
} | |
/** | |
@@ -548,7 +499,7 @@ | |
mPlayingState = SEEKED; | |
} | |
mStartTime = currentTime - playTime; | |
- animationFrame(currentTime); | |
+ doAnimationFrame(currentTime); | |
} | |
/** | |
@@ -572,118 +523,126 @@ | |
* the same times for calculating their values, which makes synchronizing | |
* animations possible. | |
* | |
+ * The handler uses the Choreographer for executing periodic callbacks. | |
*/ | |
- private static class AnimationHandler extends Handler { | |
+ private static class AnimationHandler implements Runnable { | |
+ // The per-thread list of all active animations | |
+ private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>(); | |
+ | |
+ // The per-thread set of animations to be started on the next animation frame | |
+ private final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>(); | |
+ | |
/** | |
- * There are only two messages that we care about: ANIMATION_START and | |
- * ANIMATION_FRAME. The START message is sent when an animation's start() | |
- * method is called. It cannot start synchronously when start() is called | |
- * because the call may be on the wrong thread, and it would also not be | |
- * synchronized with other animations because it would not start on a common | |
- * timing pulse. So each animation sends a START message to the handler, which | |
- * causes the handler to place the animation on the active animations queue and | |
- * start processing frames for that animation. | |
- * The FRAME message is the one that is sent over and over while there are any | |
- * active animations to process. | |
+ * Internal per-thread collections used to avoid set collisions as animations start and end | |
+ * while being processed. | |
*/ | |
- @Override | |
- public void handleMessage(Message msg) { | |
- boolean callAgain = true; | |
- ArrayList<ValueAnimator> animations = sAnimations.get(); | |
- ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get(); | |
- switch (msg.what) { | |
- // TODO: should we avoid sending frame message when starting if we | |
- // were already running? | |
- case ANIMATION_START: | |
- ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get(); | |
- if (animations.size() > 0 || delayedAnims.size() > 0) { | |
- callAgain = false; | |
- } | |
- // pendingAnims holds any animations that have requested to be started | |
- // We're going to clear sPendingAnimations, but starting animation may | |
- // cause more to be added to the pending list (for example, if one animation | |
- // starting triggers another starting). So we loop until sPendingAnimations | |
- // is empty. | |
- while (pendingAnimations.size() > 0) { | |
- ArrayList<ValueAnimator> pendingCopy = | |
- (ArrayList<ValueAnimator>) pendingAnimations.clone(); | |
- pendingAnimations.clear(); | |
- int count = pendingCopy.size(); | |
- for (int i = 0; i < count; ++i) { | |
- ValueAnimator anim = pendingCopy.get(i); | |
- // If the animation has a startDelay, place it on the delayed list | |
- if (anim.mStartDelay == 0) { | |
- anim.startAnimation(); | |
- } else { | |
- delayedAnims.add(anim); | |
- } | |
- } | |
- } | |
- // fall through to process first frame of new animations | |
- case ANIMATION_FRAME: | |
- // currentTime holds the common time for all animations processed | |
- // during this frame | |
- long currentTime = AnimationUtils.currentAnimationTimeMillis(); | |
- ArrayList<ValueAnimator> readyAnims = sReadyAnims.get(); | |
- ArrayList<ValueAnimator> endingAnims = sEndingAnims.get(); | |
- | |
- // First, process animations currently sitting on the delayed queue, adding | |
- // them to the active animations if they are ready | |
- int numDelayedAnims = delayedAnims.size(); | |
- for (int i = 0; i < numDelayedAnims; ++i) { | |
- ValueAnimator anim = delayedAnims.get(i); | |
- if (anim.delayedAnimationFrame(currentTime)) { | |
- readyAnims.add(anim); | |
- } | |
- } | |
- int numReadyAnims = readyAnims.size(); | |
- if (numReadyAnims > 0) { | |
- for (int i = 0; i < numReadyAnims; ++i) { | |
- ValueAnimator anim = readyAnims.get(i); | |
- anim.startAnimation(); | |
- anim.mRunning = true; | |
- delayedAnims.remove(anim); | |
- } | |
- readyAnims.clear(); | |
- } | |
+ private final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>(); | |
+ private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>(); | |
+ private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>(); | |
- // Now process all active animations. The return value from animationFrame() | |
- // tells the handler whether it should now be ended | |
- int numAnims = animations.size(); | |
- int i = 0; | |
- while (i < numAnims) { | |
- ValueAnimator anim = animations.get(i); | |
- if (anim.animationFrame(currentTime)) { | |
- endingAnims.add(anim); | |
- } | |
- if (animations.size() == numAnims) { | |
- ++i; | |
- } else { | |
- // An animation might be canceled or ended by client code | |
- // during the animation frame. Check to see if this happened by | |
- // seeing whether the current index is the same as it was before | |
- // calling animationFrame(). Another approach would be to copy | |
- // animations to a temporary list and process that list instead, | |
- // but that entails garbage and processing overhead that would | |
- // be nice to avoid. | |
- --numAnims; | |
- endingAnims.remove(anim); | |
- } | |
- } | |
- if (endingAnims.size() > 0) { | |
- for (i = 0; i < endingAnims.size(); ++i) { | |
- endingAnims.get(i).endAnimation(); | |
- } | |
- endingAnims.clear(); | |
- } | |
+ private final Choreographer mChoreographer; | |
+ private boolean mAnimationScheduled; | |
+ | |
+ private AnimationHandler() { | |
+ mChoreographer = Choreographer.getInstance(); | |
+ } | |
+ | |
+ /** | |
+ * Start animating on the next frame. | |
+ */ | |
+ public void start() { | |
+ scheduleAnimation(); | |
+ } | |
- // If there are still active or delayed animations, call the handler again | |
- // after the frameDelay | |
- if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) { | |
- sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay - | |
- (AnimationUtils.currentAnimationTimeMillis() - currentTime))); | |
+ private void doAnimationFrame(long frameTime) { | |
+ // mPendingAnimations holds any animations that have requested to be started | |
+ // We're going to clear mPendingAnimations, but starting animation may | |
+ // cause more to be added to the pending list (for example, if one animation | |
+ // starting triggers another starting). So we loop until mPendingAnimations | |
+ // is empty. | |
+ while (mPendingAnimations.size() > 0) { | |
+ ArrayList<ValueAnimator> pendingCopy = | |
+ (ArrayList<ValueAnimator>) mPendingAnimations.clone(); | |
+ mPendingAnimations.clear(); | |
+ int count = pendingCopy.size(); | |
+ for (int i = 0; i < count; ++i) { | |
+ ValueAnimator anim = pendingCopy.get(i); | |
+ // If the animation has a startDelay, place it on the delayed list | |
+ if (anim.mStartDelay == 0) { | |
+ anim.startAnimation(this); | |
+ } else { | |
+ mDelayedAnims.add(anim); | |
} | |
- break; | |
+ } | |
+ } | |
+ // Next, process animations currently sitting on the delayed queue, adding | |
+ // them to the active animations if they are ready | |
+ int numDelayedAnims = mDelayedAnims.size(); | |
+ for (int i = 0; i < numDelayedAnims; ++i) { | |
+ ValueAnimator anim = mDelayedAnims.get(i); | |
+ if (anim.delayedAnimationFrame(frameTime)) { | |
+ mReadyAnims.add(anim); | |
+ } | |
+ } | |
+ int numReadyAnims = mReadyAnims.size(); | |
+ if (numReadyAnims > 0) { | |
+ for (int i = 0; i < numReadyAnims; ++i) { | |
+ ValueAnimator anim = mReadyAnims.get(i); | |
+ anim.startAnimation(this); | |
+ anim.mRunning = true; | |
+ mDelayedAnims.remove(anim); | |
+ } | |
+ mReadyAnims.clear(); | |
+ } | |
+ | |
+ // Now process all active animations. The return value from animationFrame() | |
+ // tells the handler whether it should now be ended | |
+ int numAnims = mAnimations.size(); | |
+ int i = 0; | |
+ while (i < numAnims) { | |
+ ValueAnimator anim = mAnimations.get(i); | |
+ if (anim.doAnimationFrame(frameTime)) { | |
+ mEndingAnims.add(anim); | |
+ } | |
+ if (mAnimations.size() == numAnims) { | |
+ ++i; | |
+ } else { | |
+ // An animation might be canceled or ended by client code | |
+ // during the animation frame. Check to see if this happened by | |
+ // seeing whether the current index is the same as it was before | |
+ // calling animationFrame(). Another approach would be to copy | |
+ // animations to a temporary list and process that list instead, | |
+ // but that entails garbage and processing overhead that would | |
+ // be nice to avoid. | |
+ --numAnims; | |
+ mEndingAnims.remove(anim); | |
+ } | |
+ } | |
+ if (mEndingAnims.size() > 0) { | |
+ for (i = 0; i < mEndingAnims.size(); ++i) { | |
+ mEndingAnims.get(i).endAnimation(this); | |
+ } | |
+ mEndingAnims.clear(); | |
+ } | |
+ | |
+ // If there are still active or delayed animations, schedule a future call to | |
+ // onAnimate to process the next frame of the animations. | |
+ if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { | |
+ scheduleAnimation(); | |
+ } | |
+ } | |
+ | |
+ // Called by the Choreographer. | |
+ @Override | |
+ public void run() { | |
+ mAnimationScheduled = false; | |
+ doAnimationFrame(mChoreographer.getFrameTime()); | |
+ } | |
+ | |
+ private void scheduleAnimation() { | |
+ if (!mAnimationScheduled) { | |
+ mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null); | |
+ mAnimationScheduled = true; | |
} | |
} | |
} | |
@@ -695,7 +654,7 @@ | |
* @return the number of milliseconds to delay running the animation | |
*/ | |
public long getStartDelay() { | |
- return mStartDelay; | |
+ return mUnscaledStartDelay; | |
} | |
/** | |
@@ -705,7 +664,8 @@ | |
* @param startDelay The amount of the delay, in milliseconds | |
*/ | |
public void setStartDelay(long startDelay) { | |
- this.mStartDelay = startDelay; | |
+ this.mStartDelay = (long)(startDelay * sDurationScale); | |
+ mUnscaledStartDelay = startDelay; | |
} | |
/** | |
@@ -715,10 +675,13 @@ | |
* function because the same delay will be applied to all animations, since they are all | |
* run off of a single timing loop. | |
* | |
+ * The frame delay may be ignored when the animation system uses an external timing | |
+ * source, such as the display refresh rate (vsync), to govern animations. | |
+ * | |
* @return the requested time between frames, in milliseconds | |
*/ | |
public static long getFrameDelay() { | |
- return sFrameDelay; | |
+ return Choreographer.getFrameDelay(); | |
} | |
/** | |
@@ -728,10 +691,13 @@ | |
* function because the same delay will be applied to all animations, since they are all | |
* run off of a single timing loop. | |
* | |
+ * The frame delay may be ignored when the animation system uses an external timing | |
+ * source, such as the display refresh rate (vsync), to govern animations. | |
+ * | |
* @param frameDelay the requested time between frames, in milliseconds | |
*/ | |
public static void setFrameDelay(long frameDelay) { | |
- sFrameDelay = frameDelay; | |
+ Choreographer.setFrameDelay(frameDelay); | |
} | |
/** | |
@@ -906,6 +872,18 @@ | |
} | |
} | |
+ private void notifyStartListeners() { | |
+ if (mListeners != null && !mStartListenersCalled) { | |
+ ArrayList<AnimatorListener> tmpListeners = | |
+ (ArrayList<AnimatorListener>) mListeners.clone(); | |
+ int numListeners = tmpListeners.size(); | |
+ for (int i = 0; i < numListeners; ++i) { | |
+ tmpListeners.get(i).onAnimationStart(this); | |
+ } | |
+ } | |
+ mStartListenersCalled = true; | |
+ } | |
+ | |
/** | |
* Start the animation playing. This version of start() takes a boolean flag that indicates | |
* whether the animation should play in reverse. The flag is usually false, but may be set | |
@@ -928,28 +906,16 @@ | |
mPlayingState = STOPPED; | |
mStarted = true; | |
mStartedDelay = false; | |
- sPendingAnimations.get().add(this); | |
+ AnimationHandler animationHandler = getOrCreateAnimationHandler(); | |
+ animationHandler.mPendingAnimations.add(this); | |
if (mStartDelay == 0) { | |
// This sets the initial value of the animation, prior to actually starting it running | |
- setCurrentPlayTime(getCurrentPlayTime()); | |
+ setCurrentPlayTime(0); | |
mPlayingState = STOPPED; | |
mRunning = true; | |
- | |
- if (mListeners != null) { | |
- ArrayList<AnimatorListener> tmpListeners = | |
- (ArrayList<AnimatorListener>) mListeners.clone(); | |
- int numListeners = tmpListeners.size(); | |
- for (int i = 0; i < numListeners; ++i) { | |
- tmpListeners.get(i).onAnimationStart(this); | |
- } | |
- } | |
- } | |
- AnimationHandler animationHandler = sAnimationHandler.get(); | |
- if (animationHandler == null) { | |
- animationHandler = new AnimationHandler(); | |
- sAnimationHandler.set(animationHandler); | |
+ notifyStartListeners(); | |
} | |
- animationHandler.sendEmptyMessage(ANIMATION_START); | |
+ animationHandler.start(); | |
} | |
@Override | |
@@ -961,26 +927,34 @@ | |
public void cancel() { | |
// Only cancel if the animation is actually running or has been started and is about | |
// to run | |
- if (mPlayingState != STOPPED || sPendingAnimations.get().contains(this) || | |
- sDelayedAnims.get().contains(this)) { | |
+ AnimationHandler handler = getOrCreateAnimationHandler(); | |
+ if (mPlayingState != STOPPED | |
+ || handler.mPendingAnimations.contains(this) | |
+ || handler.mDelayedAnims.contains(this)) { | |
// Only notify listeners if the animator has actually started | |
- if (mRunning && mListeners != null) { | |
+ if ((mStarted || mRunning) && mListeners != null) { | |
+ if (!mRunning) { | |
+ // If it's not yet running, then start listeners weren't called. Call them now. | |
+ notifyStartListeners(); | |
+ } | |
ArrayList<AnimatorListener> tmpListeners = | |
(ArrayList<AnimatorListener>) mListeners.clone(); | |
for (AnimatorListener listener : tmpListeners) { | |
listener.onAnimationCancel(this); | |
} | |
} | |
- endAnimation(); | |
+ endAnimation(handler); | |
} | |
} | |
@Override | |
public void end() { | |
- if (!sAnimations.get().contains(this) && !sPendingAnimations.get().contains(this)) { | |
+ AnimationHandler handler = getOrCreateAnimationHandler(); | |
+ if (!handler.mAnimations.contains(this) && !handler.mPendingAnimations.contains(this)) { | |
// Special case if the animation has not yet started; get it ready for ending | |
mStartedDelay = false; | |
- startAnimation(); | |
+ startAnimation(handler); | |
+ mStarted = true; | |
} else if (!mInitialized) { | |
initAnimation(); | |
} | |
@@ -991,7 +965,7 @@ | |
} else { | |
animateValue(1f); | |
} | |
- endAnimation(); | |
+ endAnimation(handler); | |
} | |
@Override | |
@@ -1027,12 +1001,16 @@ | |
* Called internally to end an animation by removing it from the animations list. Must be | |
* called on the UI thread. | |
*/ | |
- private void endAnimation() { | |
- sAnimations.get().remove(this); | |
- sPendingAnimations.get().remove(this); | |
- sDelayedAnims.get().remove(this); | |
+ private void endAnimation(AnimationHandler handler) { | |
+ handler.mAnimations.remove(this); | |
+ handler.mPendingAnimations.remove(this); | |
+ handler.mDelayedAnims.remove(this); | |
mPlayingState = STOPPED; | |
- if (mRunning && mListeners != null) { | |
+ if ((mStarted || mRunning) && mListeners != null) { | |
+ if (!mRunning) { | |
+ // If it's not yet running, then start listeners weren't called. Call them now. | |
+ notifyStartListeners(); | |
+ } | |
ArrayList<AnimatorListener> tmpListeners = | |
(ArrayList<AnimatorListener>) mListeners.clone(); | |
int numListeners = tmpListeners.size(); | |
@@ -1042,24 +1020,20 @@ | |
} | |
mRunning = false; | |
mStarted = false; | |
+ mStartListenersCalled = false; | |
} | |
/** | |
* Called internally to start an animation by adding it to the active animations list. Must be | |
* called on the UI thread. | |
*/ | |
- private void startAnimation() { | |
+ private void startAnimation(AnimationHandler handler) { | |
initAnimation(); | |
- sAnimations.get().add(this); | |
+ handler.mAnimations.add(this); | |
if (mStartDelay > 0 && mListeners != null) { | |
// Listeners were already notified in start() if startDelay is 0; this is | |
// just for delayed animations | |
- ArrayList<AnimatorListener> tmpListeners = | |
- (ArrayList<AnimatorListener>) mListeners.clone(); | |
- int numListeners = tmpListeners.size(); | |
- for (int i = 0; i < numListeners; ++i) { | |
- tmpListeners.get(i).onAnimationStart(this); | |
- } | |
+ notifyStartListeners(); | |
} | |
} | |
@@ -1104,17 +1078,6 @@ | |
*/ | |
boolean animationFrame(long currentTime) { | |
boolean done = false; | |
- | |
- if (mPlayingState == STOPPED) { | |
- mPlayingState = RUNNING; | |
- if (mSeekTime < 0) { | |
- mStartTime = currentTime; | |
- } else { | |
- mStartTime = currentTime - mSeekTime; | |
- // Now that we're playing, reset the seek time | |
- mSeekTime = -1; | |
- } | |
- } | |
switch (mPlayingState) { | |
case RUNNING: | |
case SEEKED: | |
@@ -1150,6 +1113,31 @@ | |
} | |
/** | |
+ * Processes a frame of the animation, adjusting the start time if needed. | |
+ * | |
+ * @param frameTime The frame time. | |
+ * @return true if the animation has ended. | |
+ */ | |
+ final boolean doAnimationFrame(long frameTime) { | |
+ if (mPlayingState == STOPPED) { | |
+ mPlayingState = RUNNING; | |
+ if (mSeekTime < 0) { | |
+ mStartTime = frameTime; | |
+ } else { | |
+ mStartTime = frameTime - mSeekTime; | |
+ // Now that we're playing, reset the seek time | |
+ mSeekTime = -1; | |
+ } | |
+ } | |
+ // The frame time might be before the start time during the first frame of | |
+ // an animation. The "current time" must always be on or after the start | |
+ // time to avoid animating frames at negative time intervals. In practice, this | |
+ // is very rare and only happens when seeking backwards. | |
+ final long currentTime = Math.max(frameTime, mStartTime); | |
+ return animationFrame(currentTime); | |
+ } | |
+ | |
+ /** | |
* Returns the current animation fraction, which is the elapsed/interpolated fraction used in | |
* the most recent frame update on the animation. | |
* | |
@@ -1236,13 +1224,14 @@ | |
/** | |
* Return the number of animations currently running. | |
* | |
- * Used by StrictMode internally to annotate violations. Only | |
- * called on the main thread. | |
+ * Used by StrictMode internally to annotate violations. | |
+ * May be called on arbitrary threads! | |
* | |
* @hide | |
*/ | |
public static int getCurrentAnimationsCount() { | |
- return sAnimations.get().size(); | |
+ AnimationHandler handler = sAnimationHandler.get(); | |
+ return handler != null ? handler.mAnimations.size() : 0; | |
} | |
/** | |
@@ -1252,9 +1241,21 @@ | |
* @hide | |
*/ | |
public static void clearAllAnimations() { | |
- sAnimations.get().clear(); | |
- sPendingAnimations.get().clear(); | |
- sDelayedAnims.get().clear(); | |
+ AnimationHandler handler = sAnimationHandler.get(); | |
+ if (handler != null) { | |
+ handler.mAnimations.clear(); | |
+ handler.mPendingAnimations.clear(); | |
+ handler.mDelayedAnims.clear(); | |
+ } | |
+ } | |
+ | |
+ private AnimationHandler getOrCreateAnimationHandler() { | |
+ AnimationHandler handler = sAnimationHandler.get(); | |
+ if (handler == null) { | |
+ handler = new AnimationHandler(); | |
+ sAnimationHandler.set(handler); | |
+ } | |
+ return handler; | |
} | |
@Override | |
diff -Nur android-15/android/annotation/SuppressLint.java android-16/android/annotation/SuppressLint.java | |
--- android-15/android/annotation/SuppressLint.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/annotation/SuppressLint.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -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 android.annotation; | |
+ | |
+import static java.lang.annotation.ElementType.CONSTRUCTOR; | |
+import static java.lang.annotation.ElementType.FIELD; | |
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE; | |
+import static java.lang.annotation.ElementType.METHOD; | |
+import static java.lang.annotation.ElementType.PARAMETER; | |
+import static java.lang.annotation.ElementType.TYPE; | |
+ | |
+import java.lang.annotation.Retention; | |
+import java.lang.annotation.RetentionPolicy; | |
+import java.lang.annotation.Target; | |
+ | |
+/** Indicates that Lint should ignore the specified warnings for the annotated element. */ | |
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) | |
+@Retention(RetentionPolicy.CLASS) | |
+public @interface SuppressLint { | |
+ /** | |
+ * The set of warnings (identified by the lint issue id) that should be | |
+ * ignored by lint. It is not an error to specify an unrecognized name. | |
+ */ | |
+ String[] value(); | |
+} | |
diff -Nur android-15/android/annotation/TargetApi.java android-16/android/annotation/TargetApi.java | |
--- android-15/android/annotation/TargetApi.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/annotation/TargetApi.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -0,0 +1,35 @@ | |
+/* | |
+ * 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 android.annotation; | |
+ | |
+import static java.lang.annotation.ElementType.CONSTRUCTOR; | |
+import static java.lang.annotation.ElementType.METHOD; | |
+import static java.lang.annotation.ElementType.TYPE; | |
+ | |
+import java.lang.annotation.Retention; | |
+import java.lang.annotation.RetentionPolicy; | |
+import java.lang.annotation.Target; | |
+ | |
+/** Indicates that Lint should treat this type as targeting a given API level, no matter what the | |
+ project target is. */ | |
+@Target({TYPE, METHOD, CONSTRUCTOR}) | |
+@Retention(RetentionPolicy.CLASS) | |
+public @interface TargetApi { | |
+ /** | |
+ * This sets the target api level for the type.. | |
+ */ | |
+ int value(); | |
+} | |
diff -Nur android-15/android/app/ActionBar.java android-16/android/app/ActionBar.java | |
--- android-15/android/app/ActionBar.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/app/ActionBar.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -611,6 +611,10 @@ | |
* If the window hosting the ActionBar does not have the feature | |
* {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application | |
* content to fit the new space available. | |
+ * | |
+ * <p>If you are hiding the ActionBar through | |
+ * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN}, | |
+ * you should not call this function directly. | |
*/ | |
public abstract void show(); | |
@@ -619,6 +623,12 @@ | |
* If the window hosting the ActionBar does not have the feature | |
* {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application | |
* content to fit the new space available. | |
+ * | |
+ * <p>Instead of calling this function directly, you can also cause an | |
+ * ActionBar using the overlay feature to hide through | |
+ * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN}. | |
+ * Hiding the ActionBar through this system UI flag allows you to more | |
+ * seamlessly hide it in conjunction with other screen decorations. | |
*/ | |
public abstract void hide(); | |
diff -Nur android-15/android/app/Activity.java android-16/android/app/Activity.java | |
--- android-15/android/app/Activity.java 2012-06-18 20:00:45.000000000 +0900 | |
+++ android-16/android/app/Activity.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -29,10 +29,11 @@ | |
import android.content.IntentSender; | |
import android.content.SharedPreferences; | |
import android.content.pm.ActivityInfo; | |
+import android.content.pm.PackageManager; | |
+import android.content.pm.PackageManager.NameNotFoundException; | |
import android.content.res.Configuration; | |
import android.content.res.Resources; | |
import android.content.res.TypedArray; | |
-import android.content.res.Resources.Theme; | |
import android.database.Cursor; | |
import android.graphics.Bitmap; | |
import android.graphics.Canvas; | |
@@ -54,8 +55,8 @@ | |
import android.util.AttributeSet; | |
import android.util.EventLog; | |
import android.util.Log; | |
+import android.util.Slog; | |
import android.util.SparseArray; | |
-import android.util.TypedValue; | |
import android.view.ActionMode; | |
import android.view.ContextMenu; | |
import android.view.ContextMenu.ContextMenuInfo; | |
@@ -67,13 +68,13 @@ | |
import android.view.MenuItem; | |
import android.view.MotionEvent; | |
import android.view.View; | |
-import android.view.WindowManagerImpl; | |
import android.view.View.OnCreateContextMenuListener; | |
import android.view.ViewGroup; | |
import android.view.ViewGroup.LayoutParams; | |
import android.view.ViewManager; | |
import android.view.Window; | |
import android.view.WindowManager; | |
+import android.view.WindowManagerImpl; | |
import android.view.accessibility.AccessibilityEvent; | |
import android.widget.AdapterView; | |
@@ -203,8 +204,8 @@ | |
* with the user. Between these two methods you can maintain resources that | |
* are needed to show the activity to the user. For example, you can register | |
* a {@link android.content.BroadcastReceiver} in onStart() to monitor for changes | |
- * that impact your UI, and unregister it in onStop() when the user an no | |
- * longer see what you are displaying. The onStart() and onStop() methods | |
+ * that impact your UI, and unregister it in onStop() when the user no | |
+ * longer sees what you are displaying. The onStart() and onStop() methods | |
* can be called multiple times, as the activity becomes visible and hidden | |
* to the user. | |
* | |
@@ -548,7 +549,7 @@ | |
* super.onCreate(savedInstanceState); | |
* | |
* SharedPreferences mPrefs = getSharedPreferences(); | |
- * mCurViewMode = mPrefs.getInt("view_mode" DAY_VIEW_MODE); | |
+ * mCurViewMode = mPrefs.getInt("view_mode", DAY_VIEW_MODE); | |
* } | |
* | |
* protected void onPause() { | |
@@ -570,7 +571,18 @@ | |
* tag. By doing so, other applications will need to declare a corresponding | |
* {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>} | |
* element in their own manifest to be able to start that activity. | |
- * | |
+ * | |
+ * <p>When starting an Activity you can set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION | |
+ * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION | |
+ * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} on the Intent. This will grant the | |
+ * Activity access to the specific URIs in the Intent. Access will remain | |
+ * until the Activity has finished (it will remain across the hosting | |
+ * process being killed and other temporary destruction). As of | |
+ * {@link android.os.Build.VERSION_CODES#GINGERBREAD}, if the Activity | |
+ * was already created and a new Intent is being delivered to | |
+ * {@link #onNewIntent(Intent)}, any newly granted URI permissions will be added | |
+ * to the existing ones it holds. | |
+ * | |
* <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a> | |
* document for more information on permissions and security in general. | |
* | |
@@ -631,6 +643,7 @@ | |
Window.Callback, KeyEvent.Callback, | |
OnCreateContextMenuListener, ComponentCallbacks2 { | |
private static final String TAG = "Activity"; | |
+ private static final boolean DEBUG_LIFECYCLE = false; | |
/** Standard activity result: operation canceled. */ | |
public static final int RESULT_CANCELED = 0; | |
@@ -695,6 +708,7 @@ | |
/*package*/ boolean mVisibleFromServer = false; | |
/*package*/ boolean mVisibleFromClient = true; | |
/*package*/ ActionBarImpl mActionBar = null; | |
+ private boolean mEnableDefaultActionBarUp; | |
private CharSequence mTitle; | |
private int mTitleColor = 0; | |
@@ -853,9 +867,17 @@ | |
* @see #onPostCreate | |
*/ | |
protected void onCreate(Bundle savedInstanceState) { | |
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState); | |
if (mLastNonConfigurationInstances != null) { | |
mAllLoaderManagers = mLastNonConfigurationInstances.loaders; | |
} | |
+ if (mActivityInfo.parentActivityName != null) { | |
+ if (mActionBar == null) { | |
+ mEnableDefaultActionBarUp = true; | |
+ } else { | |
+ mActionBar.setDefaultDisplayHomeAsUpEnabled(true); | |
+ } | |
+ } | |
if (savedInstanceState != null) { | |
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); | |
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null | |
@@ -994,6 +1016,7 @@ | |
* @see #onResume | |
*/ | |
protected void onStart() { | |
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this); | |
mCalled = true; | |
if (!mLoadersStarted) { | |
@@ -1054,6 +1077,7 @@ | |
* @see #onPause | |
*/ | |
protected void onResume() { | |
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this); | |
getApplication().dispatchActivityResumed(this); | |
mCalled = true; | |
} | |
@@ -1112,6 +1136,7 @@ | |
final void performSaveInstanceState(Bundle outState) { | |
onSaveInstanceState(outState); | |
saveManagedDialogs(outState); | |
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState); | |
} | |
/** | |
@@ -1217,7 +1242,7 @@ | |
* making sure nothing is lost if there are not enough resources to start | |
* the new activity without first killing this one. This is also a good | |
* place to do things like stop animations and other things that consume a | |
- * noticeable mount of CPU in order to make the switch to the next activity | |
+ * noticeable amount of CPU in order to make the switch to the next activity | |
* as fast as possible, or to close resources that are exclusive access | |
* such as the camera. | |
* | |
@@ -1242,6 +1267,7 @@ | |
* @see #onStop | |
*/ | |
protected void onPause() { | |
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this); | |
getApplication().dispatchActivityPaused(this); | |
mCalled = true; | |
} | |
@@ -1328,6 +1354,7 @@ | |
* @see #onDestroy | |
*/ | |
protected void onStop() { | |
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this); | |
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); | |
getApplication().dispatchActivityStopped(this); | |
mCalled = true; | |
@@ -1362,6 +1389,7 @@ | |
* @see #isFinishing | |
*/ | |
protected void onDestroy() { | |
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this); | |
mCalled = true; | |
// dismiss any dialogs we are managing. | |
@@ -1413,6 +1441,7 @@ | |
* @param newConfig The new device configuration. | |
*/ | |
public void onConfigurationChanged(Configuration newConfig) { | |
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onConfigurationChanged " + this + ": " + newConfig); | |
mCalled = true; | |
mFragments.dispatchConfigurationChanged(newConfig); | |
@@ -1594,11 +1623,13 @@ | |
} | |
public void onLowMemory() { | |
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this); | |
mCalled = true; | |
mFragments.dispatchLowMemory(); | |
} | |
public void onTrimMemory(int level) { | |
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level); | |
mCalled = true; | |
mFragments.dispatchTrimMemory(level); | |
} | |
@@ -1820,6 +1851,7 @@ | |
} | |
mActionBar = new ActionBarImpl(this); | |
+ mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); | |
} | |
/** | |
@@ -2461,7 +2493,7 @@ | |
if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) { | |
boolean goforit = onPrepareOptionsMenu(menu); | |
goforit |= mFragments.dispatchPrepareOptionsMenu(menu); | |
- return goforit && menu.hasVisibleItems(); | |
+ return goforit; | |
} | |
return true; | |
} | |
@@ -2502,7 +2534,18 @@ | |
if (onOptionsItemSelected(item)) { | |
return true; | |
} | |
- return mFragments.dispatchOptionsItemSelected(item); | |
+ if (mFragments.dispatchOptionsItemSelected(item)) { | |
+ return true; | |
+ } | |
+ if (item.getItemId() == android.R.id.home && mActionBar != null && | |
+ (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) { | |
+ if (mParent == null) { | |
+ return onNavigateUp(); | |
+ } else { | |
+ return mParent.onNavigateUpFromChild(this); | |
+ } | |
+ } | |
+ return false; | |
case Window.FEATURE_CONTEXT_MENU: | |
EventLog.writeEvent(50000, 1, item.getTitleCondensed()); | |
@@ -2621,7 +2664,7 @@ | |
* facilities. | |
* | |
* <p>Derived classes should call through to the base class for it to | |
- * perform the default menu handling. | |
+ * perform the default menu handling.</p> | |
* | |
* @param item The menu item that was selected. | |
* | |
@@ -2638,6 +2681,105 @@ | |
} | |
/** | |
+ * This method is called whenever the user chooses to navigate Up within your application's | |
+ * activity hierarchy from the action bar. | |
+ * | |
+ * <p>If the attribute {@link android.R.attr#parentActivityName parentActivityName} | |
+ * was specified in the manifest for this activity or an activity-alias to it, | |
+ * default Up navigation will be handled automatically. If any activity | |
+ * along the parent chain requires extra Intent arguments, the Activity subclass | |
+ * should override the method {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)} | |
+ * to supply those arguments.</p> | |
+ * | |
+ * <p>See <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a> | |
+ * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> | |
+ * from the design guide for more information about navigating within your app.</p> | |
+ * | |
+ * <p>See the {@link TaskStackBuilder} class and the Activity methods | |
+ * {@link #getParentActivityIntent()}, {@link #shouldUpRecreateTask(Intent)}, and | |
+ * {@link #navigateUpTo(Intent)} for help implementing custom Up navigation. | |
+ * The AppNavigation sample application in the Android SDK is also available for reference.</p> | |
+ * | |
+ * @return true if Up navigation completed successfully and this Activity was finished, | |
+ * false otherwise. | |
+ */ | |
+ public boolean onNavigateUp() { | |
+ // Automatically handle hierarchical Up navigation if the proper | |
+ // metadata is available. | |
+ Intent upIntent = getParentActivityIntent(); | |
+ if (upIntent != null) { | |
+ if (shouldUpRecreateTask(upIntent)) { | |
+ TaskStackBuilder b = TaskStackBuilder.create(this); | |
+ onCreateNavigateUpTaskStack(b); | |
+ onPrepareNavigateUpTaskStack(b); | |
+ b.startActivities(); | |
+ | |
+ // We can't finishAffinity if we have a result. | |
+ // Fall back and simply finish the current activity instead. | |
+ if (mResultCode != RESULT_CANCELED || mResultData != null) { | |
+ // Tell the developer what's going on to avoid hair-pulling. | |
+ Log.i(TAG, "onNavigateUp only finishing topmost activity to return a result"); | |
+ finish(); | |
+ } else { | |
+ finishAffinity(); | |
+ } | |
+ } else { | |
+ navigateUpTo(upIntent); | |
+ } | |
+ return true; | |
+ } | |
+ return false; | |
+ } | |
+ | |
+ /** | |
+ * This is called when a child activity of this one attempts to navigate up. | |
+ * The default implementation simply calls onNavigateUp() on this activity (the parent). | |
+ * | |
+ * @param child The activity making the call. | |
+ */ | |
+ public boolean onNavigateUpFromChild(Activity child) { | |
+ return onNavigateUp(); | |
+ } | |
+ | |
+ /** | |
+ * Define the synthetic task stack that will be generated during Up navigation from | |
+ * a different task. | |
+ * | |
+ * <p>The default implementation of this method adds the parent chain of this activity | |
+ * as specified in the manifest to the supplied {@link TaskStackBuilder}. Applications | |
+ * may choose to override this method to construct the desired task stack in a different | |
+ * way.</p> | |
+ * | |
+ * <p>This method will be invoked by the default implementation of {@link #onNavigateUp()} | |
+ * if {@link #shouldUpRecreateTask(Intent)} returns true when supplied with the intent | |
+ * returned by {@link #getParentActivityIntent()}.</p> | |
+ * | |
+ * <p>Applications that wish to supply extra Intent parameters to the parent stack defined | |
+ * by the manifest should override {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)}.</p> | |
+ * | |
+ * @param builder An empty TaskStackBuilder - the application should add intents representing | |
+ * the desired task stack | |
+ */ | |
+ public void onCreateNavigateUpTaskStack(TaskStackBuilder builder) { | |
+ builder.addParentStack(this); | |
+ } | |
+ | |
+ /** | |
+ * Prepare the synthetic task stack that will be generated during Up navigation | |
+ * from a different task. | |
+ * | |
+ * <p>This method receives the {@link TaskStackBuilder} with the constructed series of | |
+ * Intents as generated by {@link #onCreateNavigateUpTaskStack(TaskStackBuilder)}. | |
+ * If any extra data should be added to these intents before launching the new task, | |
+ * the application should override this method and add that data here.</p> | |
+ * | |
+ * @param builder A TaskStackBuilder that has been populated with Intents by | |
+ * onCreateNavigateUpTaskStack. | |
+ */ | |
+ public void onPrepareNavigateUpTaskStack(TaskStackBuilder builder) { | |
+ } | |
+ | |
+ /** | |
* This hook is called whenever the options menu is being closed (either by the user canceling | |
* the menu with the back/menu button, or when an item is selected). | |
* | |
@@ -3131,7 +3273,7 @@ | |
if (mMenuInflater == null) { | |
initActionBar(); | |
if (mActionBar != null) { | |
- mMenuInflater = new MenuInflater(mActionBar.getThemedContext()); | |
+ mMenuInflater = new MenuInflater(mActionBar.getThemedContext(), this); | |
} else { | |
mMenuInflater = new MenuInflater(this); | |
} | |
@@ -3155,42 +3297,61 @@ | |
} | |
/** | |
+ * Same as calling {@link #startActivityForResult(Intent, int, Bundle)} | |
+ * with no options. | |
+ * | |
+ * @param intent The intent to start. | |
+ * @param requestCode If >= 0, this code will be returned in | |
+ * onActivityResult() when the activity exits. | |
+ * | |
+ * @throws android.content.ActivityNotFoundException | |
+ * | |
+ * @see #startActivity | |
+ */ | |
+ public void startActivityForResult(Intent intent, int requestCode) { | |
+ startActivityForResult(intent, requestCode, null); | |
+ } | |
+ | |
+ /** | |
* Launch an activity for which you would like a result when it finished. | |
* When this activity exits, your | |
* onActivityResult() method will be called with the given requestCode. | |
* Using a negative requestCode is the same as calling | |
* {@link #startActivity} (the activity is not launched as a sub-activity). | |
- * | |
+ * | |
* <p>Note that this method should only be used with Intent protocols | |
* that are defined to return a result. In other protocols (such as | |
* {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may | |
* not get the result when you expect. For example, if the activity you | |
* are launching uses the singleTask launch mode, it will not run in your | |
* task and thus you will immediately receive a cancel result. | |
- * | |
+ * | |
* <p>As a special case, if you call startActivityForResult() with a requestCode | |
* >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your | |
* activity, then your window will not be displayed until a result is | |
* returned back from the started activity. This is to avoid visible | |
* flickering when redirecting to another activity. | |
- * | |
+ * | |
* <p>This method throws {@link android.content.ActivityNotFoundException} | |
* if there was no Activity found to run the given Intent. | |
- * | |
+ * | |
* @param intent The intent to start. | |
* @param requestCode If >= 0, this code will be returned in | |
* onActivityResult() when the activity exits. | |
- * | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. | |
+ * | |
* @throws android.content.ActivityNotFoundException | |
- * | |
+ * | |
* @see #startActivity | |
*/ | |
- public void startActivityForResult(Intent intent, int requestCode) { | |
+ public void startActivityForResult(Intent intent, int requestCode, Bundle options) { | |
if (mParent == null) { | |
Instrumentation.ActivityResult ar = | |
mInstrumentation.execStartActivity( | |
this, mMainThread.getApplicationThread(), mToken, this, | |
- intent, requestCode); | |
+ intent, requestCode, options); | |
if (ar != null) { | |
mMainThread.sendActivityResult( | |
mToken, mEmbeddedID, requestCode, ar.getResultCode(), | |
@@ -3207,11 +3368,39 @@ | |
mStartedActivity = true; | |
} | |
} else { | |
- mParent.startActivityFromChild(this, intent, requestCode); | |
+ if (options != null) { | |
+ mParent.startActivityFromChild(this, intent, requestCode, options); | |
+ } else { | |
+ // Note we want to go through this method for compatibility with | |
+ // existing applications that may have overridden it. | |
+ mParent.startActivityFromChild(this, intent, requestCode); | |
+ } | |
} | |
} | |
/** | |
+ * Same as calling {@link #startIntentSenderForResult(IntentSender, int, | |
+ * Intent, int, int, int, Bundle)} with no options. | |
+ * | |
+ * @param intent The IntentSender to launch. | |
+ * @param requestCode If >= 0, this code will be returned in | |
+ * onActivityResult() when the activity exits. | |
+ * @param fillInIntent If non-null, this will be provided as the | |
+ * intent parameter to {@link IntentSender#sendIntent}. | |
+ * @param flagsMask Intent flags in the original IntentSender that you | |
+ * would like to change. | |
+ * @param flagsValues Desired values for any bits set in | |
+ * <var>flagsMask</var> | |
+ * @param extraFlags Always set to 0. | |
+ */ | |
+ public void startIntentSenderForResult(IntentSender intent, int requestCode, | |
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) | |
+ throws IntentSender.SendIntentException { | |
+ startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, | |
+ flagsValues, extraFlags, null); | |
+ } | |
+ | |
+ /** | |
* Like {@link #startActivityForResult(Intent, int)}, but allowing you | |
* to use a IntentSender to describe the activity to be started. If | |
* the IntentSender is for an activity, that activity will be started | |
@@ -3230,21 +3419,32 @@ | |
* @param flagsValues Desired values for any bits set in | |
* <var>flagsMask</var> | |
* @param extraFlags Always set to 0. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. If options | |
+ * have also been supplied by the IntentSender, options given here will | |
+ * override any that conflict with those given by the IntentSender. | |
*/ | |
public void startIntentSenderForResult(IntentSender intent, int requestCode, | |
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) | |
- throws IntentSender.SendIntentException { | |
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, | |
+ Bundle options) throws IntentSender.SendIntentException { | |
if (mParent == null) { | |
startIntentSenderForResultInner(intent, requestCode, fillInIntent, | |
- flagsMask, flagsValues, this); | |
+ flagsMask, flagsValues, this, options); | |
+ } else if (options != null) { | |
+ mParent.startIntentSenderFromChild(this, intent, requestCode, | |
+ fillInIntent, flagsMask, flagsValues, extraFlags, options); | |
} else { | |
+ // Note we want to go through this call for compatibility with | |
+ // existing applications that may have overridden the method. | |
mParent.startIntentSenderFromChild(this, intent, requestCode, | |
fillInIntent, flagsMask, flagsValues, extraFlags); | |
} | |
} | |
private void startIntentSenderForResultInner(IntentSender intent, int requestCode, | |
- Intent fillInIntent, int flagsMask, int flagsValues, Activity activity) | |
+ Intent fillInIntent, int flagsMask, int flagsValues, Activity activity, | |
+ Bundle options) | |
throws IntentSender.SendIntentException { | |
try { | |
String resolvedType = null; | |
@@ -3255,8 +3455,8 @@ | |
int result = ActivityManagerNative.getDefault() | |
.startActivityIntentSender(mMainThread.getApplicationThread(), intent, | |
fillInIntent, resolvedType, mToken, activity.mEmbeddedID, | |
- requestCode, flagsMask, flagsValues); | |
- if (result == IActivityManager.START_CANCELED) { | |
+ requestCode, flagsMask, flagsValues, options); | |
+ if (result == ActivityManager.START_CANCELED) { | |
throw new IntentSender.SendIntentException(); | |
} | |
Instrumentation.checkStartActivityResult(result, null); | |
@@ -3275,6 +3475,22 @@ | |
} | |
/** | |
+ * Same as {@link #startActivity(Intent, Bundle)} with no options | |
+ * specified. | |
+ * | |
+ * @param intent The intent to start. | |
+ * | |
+ * @throws android.content.ActivityNotFoundException | |
+ * | |
+ * @see {@link #startActivity(Intent, Bundle)} | |
+ * @see #startActivityForResult | |
+ */ | |
+ @Override | |
+ public void startActivity(Intent intent) { | |
+ startActivity(intent, null); | |
+ } | |
+ | |
+ /** | |
* Launch a new activity. You will not receive any information about when | |
* the activity exits. This implementation overrides the base version, | |
* providing information about | |
@@ -3287,14 +3503,40 @@ | |
* if there was no Activity found to run the given Intent. | |
* | |
* @param intent The intent to start. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. | |
* | |
* @throws android.content.ActivityNotFoundException | |
- * | |
+ * | |
+ * @see {@link #startActivity(Intent)} | |
* @see #startActivityForResult | |
*/ | |
@Override | |
- public void startActivity(Intent intent) { | |
- startActivityForResult(intent, -1); | |
+ public void startActivity(Intent intent, Bundle options) { | |
+ if (options != null) { | |
+ startActivityForResult(intent, -1, options); | |
+ } else { | |
+ // Note we want to go through this call for compatibility with | |
+ // applications that may have overridden the method. | |
+ startActivityForResult(intent, -1); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Same as {@link #startActivities(Intent[], Bundle)} with no options | |
+ * specified. | |
+ * | |
+ * @param intents The intents to start. | |
+ * | |
+ * @throws android.content.ActivityNotFoundException | |
+ * | |
+ * @see {@link #startActivities(Intent[], Bundle)} | |
+ * @see #startActivityForResult | |
+ */ | |
+ @Override | |
+ public void startActivities(Intent[] intents) { | |
+ startActivities(intents, null); | |
} | |
/** | |
@@ -3310,22 +3552,24 @@ | |
* if there was no Activity found to run the given Intent. | |
* | |
* @param intents The intents to start. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. | |
* | |
* @throws android.content.ActivityNotFoundException | |
* | |
+ * @see {@link #startActivities(Intent[])} | |
* @see #startActivityForResult | |
*/ | |
@Override | |
- public void startActivities(Intent[] intents) { | |
+ public void startActivities(Intent[] intents, Bundle options) { | |
mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(), | |
- mToken, this, intents); | |
+ mToken, this, intents, options); | |
} | |
/** | |
- * Like {@link #startActivity(Intent)}, but taking a IntentSender | |
- * to start; see | |
- * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} | |
- * for more information. | |
+ * Same as calling {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)} | |
+ * with no options. | |
* | |
* @param intent The IntentSender to launch. | |
* @param fillInIntent If non-null, this will be provided as the | |
@@ -3339,8 +3583,61 @@ | |
public void startIntentSender(IntentSender intent, | |
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) | |
throws IntentSender.SendIntentException { | |
- startIntentSenderForResult(intent, -1, fillInIntent, flagsMask, | |
- flagsValues, extraFlags); | |
+ startIntentSender(intent, fillInIntent, flagsMask, flagsValues, | |
+ extraFlags, null); | |
+ } | |
+ | |
+ /** | |
+ * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender | |
+ * to start; see | |
+ * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int, Bundle)} | |
+ * for more information. | |
+ * | |
+ * @param intent The IntentSender to launch. | |
+ * @param fillInIntent If non-null, this will be provided as the | |
+ * intent parameter to {@link IntentSender#sendIntent}. | |
+ * @param flagsMask Intent flags in the original IntentSender that you | |
+ * would like to change. | |
+ * @param flagsValues Desired values for any bits set in | |
+ * <var>flagsMask</var> | |
+ * @param extraFlags Always set to 0. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. If options | |
+ * have also been supplied by the IntentSender, options given here will | |
+ * override any that conflict with those given by the IntentSender. | |
+ */ | |
+ public void startIntentSender(IntentSender intent, | |
+ Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, | |
+ Bundle options) throws IntentSender.SendIntentException { | |
+ if (options != null) { | |
+ startIntentSenderForResult(intent, -1, fillInIntent, flagsMask, | |
+ flagsValues, extraFlags, options); | |
+ } else { | |
+ // Note we want to go through this call for compatibility with | |
+ // applications that may have overridden the method. | |
+ startIntentSenderForResult(intent, -1, fillInIntent, flagsMask, | |
+ flagsValues, extraFlags); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Same as calling {@link #startActivityIfNeeded(Intent, int, Bundle)} | |
+ * with no options. | |
+ * | |
+ * @param intent The intent to start. | |
+ * @param requestCode If >= 0, this code will be returned in | |
+ * onActivityResult() when the activity exits, as described in | |
+ * {@link #startActivityForResult}. | |
+ * | |
+ * @return If a new activity was launched then true is returned; otherwise | |
+ * false is returned and you must handle the Intent yourself. | |
+ * | |
+ * @see #startActivity | |
+ * @see #startActivityForResult | |
+ */ | |
+ public boolean startActivityIfNeeded(Intent intent, int requestCode) { | |
+ return startActivityIfNeeded(intent, requestCode, null); | |
} | |
/** | |
@@ -3363,6 +3660,9 @@ | |
* @param requestCode If >= 0, this code will be returned in | |
* onActivityResult() when the activity exits, as described in | |
* {@link #startActivityForResult}. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. | |
* | |
* @return If a new activity was launched then true is returned; otherwise | |
* false is returned and you must handle the Intent yourself. | |
@@ -3370,24 +3670,23 @@ | |
* @see #startActivity | |
* @see #startActivityForResult | |
*/ | |
- public boolean startActivityIfNeeded(Intent intent, int requestCode) { | |
+ public boolean startActivityIfNeeded(Intent intent, int requestCode, Bundle options) { | |
if (mParent == null) { | |
- int result = IActivityManager.START_RETURN_INTENT_TO_CALLER; | |
+ int result = ActivityManager.START_RETURN_INTENT_TO_CALLER; | |
try { | |
intent.setAllowFds(false); | |
result = ActivityManagerNative.getDefault() | |
.startActivity(mMainThread.getApplicationThread(), | |
- intent, intent.resolveTypeIfNeeded( | |
- getContentResolver()), | |
- null, 0, | |
- mToken, mEmbeddedID, requestCode, true, false, | |
- null, null, false); | |
+ intent, intent.resolveTypeIfNeeded(getContentResolver()), | |
+ mToken, mEmbeddedID, requestCode, | |
+ ActivityManager.START_FLAG_ONLY_IF_NEEDED, null, null, | |
+ options); | |
} catch (RemoteException e) { | |
// Empty | |
} | |
- | |
+ | |
Instrumentation.checkStartActivityResult(result, intent); | |
- | |
+ | |
if (requestCode >= 0) { | |
// If this start is requesting a result, we can avoid making | |
// the activity visible until the result is received. Setting | |
@@ -3398,7 +3697,7 @@ | |
// activity is finished, no matter what happens to it. | |
mStartedActivity = true; | |
} | |
- return result != IActivityManager.START_RETURN_INTENT_TO_CALLER; | |
+ return result != ActivityManager.START_RETURN_INTENT_TO_CALLER; | |
} | |
throw new UnsupportedOperationException( | |
@@ -3406,6 +3705,24 @@ | |
} | |
/** | |
+ * Same as calling {@link #startNextMatchingActivity(Intent, Bundle)} with | |
+ * no options. | |
+ * | |
+ * @param intent The intent to dispatch to the next activity. For | |
+ * correct behavior, this must be the same as the Intent that started | |
+ * your own activity; the only changes you can make are to the extras | |
+ * inside of it. | |
+ * | |
+ * @return Returns a boolean indicating whether there was another Activity | |
+ * to start: true if there was a next activity to start, false if there | |
+ * wasn't. In general, if true is returned you will then want to call | |
+ * finish() on yourself. | |
+ */ | |
+ public boolean startNextMatchingActivity(Intent intent) { | |
+ return startNextMatchingActivity(intent, null); | |
+ } | |
+ | |
+ /** | |
* Special version of starting an activity, for use when you are replacing | |
* other activity components. You can use this to hand the Intent off | |
* to the next Activity that can handle it. You typically call this in | |
@@ -3415,18 +3732,21 @@ | |
* correct behavior, this must be the same as the Intent that started | |
* your own activity; the only changes you can make are to the extras | |
* inside of it. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. | |
* | |
* @return Returns a boolean indicating whether there was another Activity | |
* to start: true if there was a next activity to start, false if there | |
* wasn't. In general, if true is returned you will then want to call | |
* finish() on yourself. | |
*/ | |
- public boolean startNextMatchingActivity(Intent intent) { | |
+ public boolean startNextMatchingActivity(Intent intent, Bundle options) { | |
if (mParent == null) { | |
try { | |
intent.setAllowFds(false); | |
return ActivityManagerNative.getDefault() | |
- .startNextMatchingActivity(mToken, intent); | |
+ .startNextMatchingActivity(mToken, intent, options); | |
} catch (RemoteException e) { | |
// Empty | |
} | |
@@ -3436,7 +3756,25 @@ | |
throw new UnsupportedOperationException( | |
"startNextMatchingActivity can only be called from a top-level activity"); | |
} | |
- | |
+ | |
+ /** | |
+ * Same as calling {@link #startActivityFromChild(Activity, Intent, int, Bundle)} | |
+ * with no options. | |
+ * | |
+ * @param child The activity making the call. | |
+ * @param intent The intent to start. | |
+ * @param requestCode Reply request code. < 0 if reply is not requested. | |
+ * | |
+ * @throws android.content.ActivityNotFoundException | |
+ * | |
+ * @see #startActivity | |
+ * @see #startActivityForResult | |
+ */ | |
+ public void startActivityFromChild(Activity child, Intent intent, | |
+ int requestCode) { | |
+ startActivityFromChild(child, intent, requestCode, null); | |
+ } | |
+ | |
/** | |
* This is called when a child activity of this one calls its | |
* {@link #startActivity} or {@link #startActivityForResult} method. | |
@@ -3446,7 +3784,10 @@ | |
* | |
* @param child The activity making the call. | |
* @param intent The intent to start. | |
- * @param requestCode Reply request code. < 0 if reply is not requested. | |
+ * @param requestCode Reply request code. < 0 if reply is not requested. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. | |
* | |
* @throws android.content.ActivityNotFoundException | |
* | |
@@ -3454,11 +3795,11 @@ | |
* @see #startActivityForResult | |
*/ | |
public void startActivityFromChild(Activity child, Intent intent, | |
- int requestCode) { | |
+ int requestCode, Bundle options) { | |
Instrumentation.ActivityResult ar = | |
mInstrumentation.execStartActivity( | |
this, mMainThread.getApplicationThread(), mToken, child, | |
- intent, requestCode); | |
+ intent, requestCode, options); | |
if (ar != null) { | |
mMainThread.sendActivityResult( | |
mToken, child.mEmbeddedID, requestCode, | |
@@ -3467,6 +3808,24 @@ | |
} | |
/** | |
+ * Same as calling {@link #startActivityFromFragment(Fragment, Intent, int, Bundle)} | |
+ * with no options. | |
+ * | |
+ * @param fragment The fragment making the call. | |
+ * @param intent The intent to start. | |
+ * @param requestCode Reply request code. < 0 if reply is not requested. | |
+ * | |
+ * @throws android.content.ActivityNotFoundException | |
+ * | |
+ * @see Fragment#startActivity | |
+ * @see Fragment#startActivityForResult | |
+ */ | |
+ public void startActivityFromFragment(Fragment fragment, Intent intent, | |
+ int requestCode) { | |
+ startActivityFromFragment(fragment, intent, requestCode, null); | |
+ } | |
+ | |
+ /** | |
* This is called when a Fragment in this activity calls its | |
* {@link Fragment#startActivity} or {@link Fragment#startActivityForResult} | |
* method. | |
@@ -3477,6 +3836,9 @@ | |
* @param fragment The fragment making the call. | |
* @param intent The intent to start. | |
* @param requestCode Reply request code. < 0 if reply is not requested. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. | |
* | |
* @throws android.content.ActivityNotFoundException | |
* | |
@@ -3484,11 +3846,11 @@ | |
* @see Fragment#startActivityForResult | |
*/ | |
public void startActivityFromFragment(Fragment fragment, Intent intent, | |
- int requestCode) { | |
+ int requestCode, Bundle options) { | |
Instrumentation.ActivityResult ar = | |
mInstrumentation.execStartActivity( | |
this, mMainThread.getApplicationThread(), mToken, fragment, | |
- intent, requestCode); | |
+ intent, requestCode, options); | |
if (ar != null) { | |
mMainThread.sendActivityResult( | |
mToken, fragment.mWho, requestCode, | |
@@ -3497,6 +3859,18 @@ | |
} | |
/** | |
+ * Same as calling {@link #startIntentSenderFromChild(Activity, IntentSender, | |
+ * int, Intent, int, int, int, Bundle)} with no options. | |
+ */ | |
+ public void startIntentSenderFromChild(Activity child, IntentSender intent, | |
+ int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, | |
+ int extraFlags) | |
+ throws IntentSender.SendIntentException { | |
+ startIntentSenderFromChild(child, intent, requestCode, fillInIntent, | |
+ flagsMask, flagsValues, extraFlags, null); | |
+ } | |
+ | |
+ /** | |
* Like {@link #startActivityFromChild(Activity, Intent, int)}, but | |
* taking a IntentSender; see | |
* {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} | |
@@ -3504,16 +3878,24 @@ | |
*/ | |
public void startIntentSenderFromChild(Activity child, IntentSender intent, | |
int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, | |
- int extraFlags) | |
+ int extraFlags, Bundle options) | |
throws IntentSender.SendIntentException { | |
startIntentSenderForResultInner(intent, requestCode, fillInIntent, | |
- flagsMask, flagsValues, child); | |
+ flagsMask, flagsValues, child, options); | |
} | |
/** | |
* Call immediately after one of the flavors of {@link #startActivity(Intent)} | |
* or {@link #finish} to specify an explicit transition animation to | |
* perform next. | |
+ * | |
+ * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN} an alternative | |
+ * to using this with starting activities is to supply the desired animation | |
+ * information through a {@link ActivityOptions} bundle to | |
+ * {@link #startActivity(Intent, Bundle) or a related function. This allows | |
+ * you to specify a custom animation even when starting an activity from | |
+ * outside the context of the current top activity. | |
+ * | |
* @param enterAnim A resource ID of the animation resource to use for | |
* the incoming activity. Use 0 for no animation. | |
* @param exitAnim A resource ID of the animation resource to use for | |
@@ -3549,7 +3931,16 @@ | |
/** | |
* Call this to set the result that your activity will return to its | |
* caller. | |
- * | |
+ * | |
+ * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, the Intent | |
+ * you supply here can have {@link Intent#FLAG_GRANT_READ_URI_PERMISSION | |
+ * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION | |
+ * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} set. This will grant the | |
+ * Activity receiving the result access to the specific URIs in the Intent. | |
+ * Access will remain until the Activity has finished (it will remain across the hosting | |
+ * process being killed and other temporary destruction) and will be added | |
+ * to any existing set of URI permissions it already holds. | |
+ * | |
* @param resultCode The result code to propagate back to the originating | |
* activity, often RESULT_CANCELED or RESULT_OK | |
* @param data The data to propagate back to the originating activity. | |
@@ -3714,6 +4105,36 @@ | |
} | |
/** | |
+ * Finish this activity as well as all activities immediately below it | |
+ * in the current task that have the same affinity. This is typically | |
+ * used when an application can be launched on to another task (such as | |
+ * from an ACTION_VIEW of a content type it understands) and the user | |
+ * has used the up navigation to switch out of the current task and in | |
+ * to its own task. In this case, if the user has navigated down into | |
+ * any other activities of the second application, all of those should | |
+ * be removed from the original task as part of the task switch. | |
+ * | |
+ * <p>Note that this finish does <em>not</em> allow you to deliver results | |
+ * to the previous activity, and an exception will be thrown if you are trying | |
+ * to do so.</p> | |
+ */ | |
+ public void finishAffinity() { | |
+ if (mParent != null) { | |
+ throw new IllegalStateException("Can not be called from an embedded activity"); | |
+ } | |
+ if (mResultCode != RESULT_CANCELED || mResultData != null) { | |
+ throw new IllegalStateException("Can not be called to deliver a result"); | |
+ } | |
+ try { | |
+ if (ActivityManagerNative.getDefault().finishActivityAffinity(mToken)) { | |
+ mFinished = true; | |
+ } | |
+ } catch (RemoteException e) { | |
+ // Empty | |
+ } | |
+ } | |
+ | |
+ /** | |
* This is called when a child activity of this one calls its | |
* {@link #finish} method. The default implementation simply calls | |
* finish() on this activity (the parent), finishing the entire group. | |
@@ -3824,9 +4245,9 @@ | |
data.setAllowFds(false); | |
IIntentSender target = | |
ActivityManagerNative.getDefault().getIntentSender( | |
- IActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, | |
+ ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, | |
mParent == null ? mToken : mParent.mToken, | |
- mEmbeddedID, requestCode, new Intent[] { data }, null, flags); | |
+ mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null); | |
return target != null ? new PendingIntent(target) : null; | |
} catch (RemoteException e) { | |
// Empty | |
@@ -4277,7 +4698,7 @@ | |
/** | |
* Print the Activity's state into the given stream. This gets invoked if | |
- * you run "adb shell dumpsys activity <activity_component_name>". | |
+ * you run "adb shell dumpsys activity <activity_component_name>". | |
* | |
* @param prefix Desired prefix to prepend at each line of output. | |
* @param fd The raw file descriptor that the dump is being sent to. | |
@@ -4400,6 +4821,127 @@ | |
public void onActionModeFinished(ActionMode mode) { | |
} | |
+ /** | |
+ * Returns true if the app should recreate the task when navigating 'up' from this activity | |
+ * by using targetIntent. | |
+ * | |
+ * <p>If this method returns false the app can trivially call | |
+ * {@link #navigateUpTo(Intent)} using the same parameters to correctly perform | |
+ * up navigation. If this method returns false, the app should synthesize a new task stack | |
+ * by using {@link TaskStackBuilder} or another similar mechanism to perform up navigation.</p> | |
+ * | |
+ * @param targetIntent An intent representing the target destination for up navigation | |
+ * @return true if navigating up should recreate a new task stack, false if the same task | |
+ * should be used for the destination | |
+ */ | |
+ public boolean shouldUpRecreateTask(Intent targetIntent) { | |
+ try { | |
+ PackageManager pm = getPackageManager(); | |
+ ComponentName cn = targetIntent.getComponent(); | |
+ if (cn == null) { | |
+ cn = targetIntent.resolveActivity(pm); | |
+ } | |
+ ActivityInfo info = pm.getActivityInfo(cn, 0); | |
+ if (info.taskAffinity == null) { | |
+ return false; | |
+ } | |
+ return !ActivityManagerNative.getDefault() | |
+ .targetTaskAffinityMatchesActivity(mToken, info.taskAffinity); | |
+ } catch (RemoteException e) { | |
+ return false; | |
+ } catch (NameNotFoundException e) { | |
+ return false; | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Navigate from this activity to the activity specified by upIntent, finishing this activity | |
+ * in the process. If the activity indicated by upIntent already exists in the task's history, | |
+ * this activity and all others before the indicated activity in the history stack will be | |
+ * finished. | |
+ * | |
+ * <p>If the indicated activity does not appear in the history stack, this will finish | |
+ * each activity in this task until the root activity of the task is reached, resulting in | |
+ * an "in-app home" behavior. This can be useful in apps with a complex navigation hierarchy | |
+ * when an activity may be reached by a path not passing through a canonical parent | |
+ * activity.</p> | |
+ * | |
+ * <p>This method should be used when performing up navigation from within the same task | |
+ * as the destination. If up navigation should cross tasks in some cases, see | |
+ * {@link #shouldUpRecreateTask(Intent)}.</p> | |
+ * | |
+ * @param upIntent An intent representing the target destination for up navigation | |
+ * | |
+ * @return true if up navigation successfully reached the activity indicated by upIntent and | |
+ * upIntent was delivered to it. false if an instance of the indicated activity could | |
+ * not be found and this activity was simply finished normally. | |
+ */ | |
+ public boolean navigateUpTo(Intent upIntent) { | |
+ if (mParent == null) { | |
+ ComponentName destInfo = upIntent.getComponent(); | |
+ if (destInfo == null) { | |
+ destInfo = upIntent.resolveActivity(getPackageManager()); | |
+ if (destInfo == null) { | |
+ return false; | |
+ } | |
+ upIntent = new Intent(upIntent); | |
+ upIntent.setComponent(destInfo); | |
+ } | |
+ int resultCode; | |
+ Intent resultData; | |
+ synchronized (this) { | |
+ resultCode = mResultCode; | |
+ resultData = mResultData; | |
+ } | |
+ if (resultData != null) { | |
+ resultData.setAllowFds(false); | |
+ } | |
+ try { | |
+ return ActivityManagerNative.getDefault().navigateUpTo(mToken, upIntent, | |
+ resultCode, resultData); | |
+ } catch (RemoteException e) { | |
+ return false; | |
+ } | |
+ } else { | |
+ return mParent.navigateUpToFromChild(this, upIntent); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * This is called when a child activity of this one calls its | |
+ * {@link #navigateUpTo} method. The default implementation simply calls | |
+ * navigateUpTo(upIntent) on this activity (the parent). | |
+ * | |
+ * @param child The activity making the call. | |
+ * @param upIntent An intent representing the target destination for up navigation | |
+ * | |
+ * @return true if up navigation successfully reached the activity indicated by upIntent and | |
+ * upIntent was delivered to it. false if an instance of the indicated activity could | |
+ * not be found and this activity was simply finished normally. | |
+ */ | |
+ public boolean navigateUpToFromChild(Activity child, Intent upIntent) { | |
+ return navigateUpTo(upIntent); | |
+ } | |
+ | |
+ /** | |
+ * Obtain an {@link Intent} that will launch an explicit target activity specified by | |
+ * this activity's logical parent. The logical parent is named in the application's manifest | |
+ * by the {@link android.R.attr#parentActivityName parentActivityName} attribute. | |
+ * Activity subclasses may override this method to modify the Intent returned by | |
+ * super.getParentActivityIntent() or to implement a different mechanism of retrieving | |
+ * the parent intent entirely. | |
+ * | |
+ * @return a new Intent targeting the defined parent of this activity or null if | |
+ * there is no valid parent. | |
+ */ | |
+ public Intent getParentActivityIntent() { | |
+ final String parentName = mActivityInfo.parentActivityName; | |
+ if (TextUtils.isEmpty(parentName)) { | |
+ return null; | |
+ } | |
+ return new Intent().setClassName(this, parentName); | |
+ } | |
+ | |
// ------------------ Internal API ------------------ | |
final void setParent(Activity parent) { | |
@@ -4457,7 +4999,8 @@ | |
mCurrentConfig = config; | |
} | |
- final IBinder getActivityToken() { | |
+ /** @hide */ | |
+ public final IBinder getActivityToken() { | |
return mParent != null ? mParent.getActivityToken() : mToken; | |
} | |
diff -Nur android-15/android/app/ActivityManager.java android-16/android/app/ActivityManager.java | |
--- android-15/android/app/ActivityManager.java 2012-06-18 20:00:46.000000000 +0900 | |
+++ android-16/android/app/ActivityManager.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -26,24 +26,27 @@ | |
import android.content.pm.ApplicationInfo; | |
import android.content.pm.ConfigurationInfo; | |
import android.content.pm.IPackageDataObserver; | |
-import android.content.res.Configuration; | |
+import android.content.pm.PackageManager; | |
import android.content.res.Resources; | |
import android.graphics.Bitmap; | |
import android.graphics.Point; | |
+import android.os.Binder; | |
+import android.os.Bundle; | |
import android.os.Debug; | |
import android.os.Handler; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
+import android.os.Process; | |
import android.os.RemoteException; | |
import android.os.ServiceManager; | |
import android.os.SystemProperties; | |
+import android.os.UserId; | |
import android.text.TextUtils; | |
import android.util.DisplayMetrics; | |
import android.util.Log; | |
import android.util.Slog; | |
import android.view.Display; | |
-import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
@@ -58,6 +61,154 @@ | |
private final Context mContext; | |
private final Handler mHandler; | |
+ /** | |
+ * Result for IActivityManager.startActivity: an error where the | |
+ * start had to be canceled. | |
+ * @hide | |
+ */ | |
+ public static final int START_CANCELED = -6; | |
+ | |
+ /** | |
+ * Result for IActivityManager.startActivity: an error where the | |
+ * thing being started is not an activity. | |
+ * @hide | |
+ */ | |
+ public static final int START_NOT_ACTIVITY = -5; | |
+ | |
+ /** | |
+ * Result for IActivityManager.startActivity: an error where the | |
+ * caller does not have permission to start the activity. | |
+ * @hide | |
+ */ | |
+ public static final int START_PERMISSION_DENIED = -4; | |
+ | |
+ /** | |
+ * Result for IActivityManager.startActivity: an error where the | |
+ * caller has requested both to forward a result and to receive | |
+ * a result. | |
+ * @hide | |
+ */ | |
+ public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3; | |
+ | |
+ /** | |
+ * Result for IActivityManager.startActivity: an error where the | |
+ * requested class is not found. | |
+ * @hide | |
+ */ | |
+ public static final int START_CLASS_NOT_FOUND = -2; | |
+ | |
+ /** | |
+ * Result for IActivityManager.startActivity: an error where the | |
+ * given Intent could not be resolved to an activity. | |
+ * @hide | |
+ */ | |
+ public static final int START_INTENT_NOT_RESOLVED = -1; | |
+ | |
+ /** | |
+ * Result for IActivityManaqer.startActivity: the activity was started | |
+ * successfully as normal. | |
+ * @hide | |
+ */ | |
+ public static final int START_SUCCESS = 0; | |
+ | |
+ /** | |
+ * Result for IActivityManaqer.startActivity: the caller asked that the Intent not | |
+ * be executed if it is the recipient, and that is indeed the case. | |
+ * @hide | |
+ */ | |
+ public static final int START_RETURN_INTENT_TO_CALLER = 1; | |
+ | |
+ /** | |
+ * Result for IActivityManaqer.startActivity: activity wasn't really started, but | |
+ * a task was simply brought to the foreground. | |
+ * @hide | |
+ */ | |
+ public static final int START_TASK_TO_FRONT = 2; | |
+ | |
+ /** | |
+ * Result for IActivityManaqer.startActivity: activity wasn't really started, but | |
+ * the given Intent was given to the existing top activity. | |
+ * @hide | |
+ */ | |
+ public static final int START_DELIVERED_TO_TOP = 3; | |
+ | |
+ /** | |
+ * Result for IActivityManaqer.startActivity: request was canceled because | |
+ * app switches are temporarily canceled to ensure the user's last request | |
+ * (such as pressing home) is performed. | |
+ * @hide | |
+ */ | |
+ public static final int START_SWITCHES_CANCELED = 4; | |
+ | |
+ /** | |
+ * Flag for IActivityManaqer.startActivity: do special start mode where | |
+ * a new activity is launched only if it is needed. | |
+ * @hide | |
+ */ | |
+ public static final int START_FLAG_ONLY_IF_NEEDED = 1<<0; | |
+ | |
+ /** | |
+ * Flag for IActivityManaqer.startActivity: launch the app for | |
+ * debugging. | |
+ * @hide | |
+ */ | |
+ public static final int START_FLAG_DEBUG = 1<<1; | |
+ | |
+ /** | |
+ * Flag for IActivityManaqer.startActivity: launch the app for | |
+ * OpenGL tracing. | |
+ * @hide | |
+ */ | |
+ public static final int START_FLAG_OPENGL_TRACES = 1<<2; | |
+ | |
+ /** | |
+ * Flag for IActivityManaqer.startActivity: if the app is being | |
+ * launched for profiling, automatically stop the profiler once done. | |
+ * @hide | |
+ */ | |
+ public static final int START_FLAG_AUTO_STOP_PROFILER = 1<<3; | |
+ | |
+ /** | |
+ * Result for IActivityManaqer.broadcastIntent: success! | |
+ * @hide | |
+ */ | |
+ public static final int BROADCAST_SUCCESS = 0; | |
+ | |
+ /** | |
+ * Result for IActivityManaqer.broadcastIntent: attempt to broadcast | |
+ * a sticky intent without appropriate permission. | |
+ * @hide | |
+ */ | |
+ public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1; | |
+ | |
+ /** | |
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is | |
+ * for a sendBroadcast operation. | |
+ * @hide | |
+ */ | |
+ public static final int INTENT_SENDER_BROADCAST = 1; | |
+ | |
+ /** | |
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is | |
+ * for a startActivity operation. | |
+ * @hide | |
+ */ | |
+ public static final int INTENT_SENDER_ACTIVITY = 2; | |
+ | |
+ /** | |
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is | |
+ * for an activity result operation. | |
+ * @hide | |
+ */ | |
+ public static final int INTENT_SENDER_ACTIVITY_RESULT = 3; | |
+ | |
+ /** | |
+ * Type for IActivityManaqer.getIntentSender: this PendingIntent is | |
+ * for a startService operation. | |
+ * @hide | |
+ */ | |
+ public static final int INTENT_SENDER_SERVICE = 4; | |
+ | |
/*package*/ ActivityManager(Context context, Handler handler) { | |
mContext = context; | |
mHandler = handler; | |
@@ -352,7 +503,16 @@ | |
/** | |
* Return a list of the tasks that the user has recently launched, with | |
* the most recent being first and older ones after in order. | |
- * | |
+ * | |
+ * <p><b>Note: this method is only intended for debugging and presenting | |
+ * task management user interfaces</b>. This should never be used for | |
+ * core logic in an application, such as deciding between different | |
+ * behaviors based on the information found here. Such uses are | |
+ * <em>not</em> supported, and will likely break in the future. For | |
+ * example, if multiple applications can be actively running at the | |
+ * same time, assumptions made about the meaning of the data here for | |
+ * purposes of control flow will be incorrect.</p> | |
+ * | |
* @param maxNum The maximum number of entries to return in the list. The | |
* actual number returned may be smaller, depending on how many tasks the | |
* user has started and the maximum number the system can remember. | |
@@ -519,6 +679,15 @@ | |
* can be restarted in its previous state when next brought to the | |
* foreground. | |
* | |
+ * <p><b>Note: this method is only intended for debugging and presenting | |
+ * task management user interfaces</b>. This should never be used for | |
+ * core logic in an application, such as deciding between different | |
+ * behaviors based on the information found here. Such uses are | |
+ * <em>not</em> supported, and will likely break in the future. For | |
+ * example, if multiple applications can be actively running at the | |
+ * same time, assumptions made about the meaning of the data here for | |
+ * purposes of control flow will be incorrect.</p> | |
+ * | |
* @param maxNum The maximum number of entries to return in the list. The | |
* actual number returned may be smaller, depending on how many tasks the | |
* user has started. | |
@@ -667,6 +836,19 @@ | |
public static final int MOVE_TASK_NO_USER_ACTION = 0x00000002; | |
/** | |
+ * Equivalent to calling {@link #moveTaskToFront(int, int, Bundle)} | |
+ * with a null options argument. | |
+ * | |
+ * @param taskId The identifier of the task to be moved, as found in | |
+ * {@link RunningTaskInfo} or {@link RecentTaskInfo}. | |
+ * @param flags Additional operational flags, 0 or more of | |
+ * {@link #MOVE_TASK_WITH_HOME}. | |
+ */ | |
+ public void moveTaskToFront(int taskId, int flags) { | |
+ moveTaskToFront(taskId, flags, null); | |
+ } | |
+ | |
+ /** | |
* Ask that the task associated with a given task ID be moved to the | |
* front of the stack, so it is now visible to the user. Requires that | |
* the caller hold permission {@link android.Manifest.permission#REORDER_TASKS} | |
@@ -676,10 +858,13 @@ | |
* {@link RunningTaskInfo} or {@link RecentTaskInfo}. | |
* @param flags Additional operational flags, 0 or more of | |
* {@link #MOVE_TASK_WITH_HOME}. | |
+ * @param options Additional options for the operation, either null or | |
+ * as per {@link Context#startActivity(Intent, android.os.Bundle) | |
+ * Context.startActivity(Intent, Bundle)}. | |
*/ | |
- public void moveTaskToFront(int taskId, int flags) { | |
+ public void moveTaskToFront(int taskId, int flags, Bundle options) { | |
try { | |
- ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags); | |
+ ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags, options); | |
} catch (RemoteException e) { | |
// System dead, we will be dead too soon! | |
} | |
@@ -850,7 +1035,10 @@ | |
/** | |
* Return a list of the services that are currently running. | |
- * | |
+ * | |
+ * <p><b>Note: this method is only intended for debugging or implementing | |
+ * service management type user interfaces.</b></p> | |
+ * | |
* @param maxNum The maximum number of entries to return in the list. The | |
* actual number returned may be smaller, depending on how many services | |
* are running. | |
@@ -891,13 +1079,20 @@ | |
*/ | |
public static class MemoryInfo implements Parcelable { | |
/** | |
- * The total available memory on the system. This number should not | |
+ * The available memory on the system. This number should not | |
* be considered absolute: due to the nature of the kernel, a significant | |
* portion of this memory is actually in use and needed for the overall | |
* system to run well. | |
*/ | |
public long availMem; | |
- | |
+ | |
+ /** | |
+ * The total memory accessible by the kernel. This is basically the | |
+ * RAM size of the device, not including below-kernel fixed allocations | |
+ * like DMA buffers, RAM for the baseband CPU, etc. | |
+ */ | |
+ public long totalMem; | |
+ | |
/** | |
* The threshold of {@link #availMem} at which we consider memory to be | |
* low and start killing background services and other non-extraneous | |
@@ -929,6 +1124,7 @@ | |
public void writeToParcel(Parcel dest, int flags) { | |
dest.writeLong(availMem); | |
+ dest.writeLong(totalMem); | |
dest.writeLong(threshold); | |
dest.writeInt(lowMemory ? 1 : 0); | |
dest.writeLong(hiddenAppThreshold); | |
@@ -939,6 +1135,7 @@ | |
public void readFromParcel(Parcel source) { | |
availMem = source.readLong(); | |
+ totalMem = source.readLong(); | |
threshold = source.readLong(); | |
lowMemory = source.readInt() != 0; | |
hiddenAppThreshold = source.readLong(); | |
@@ -962,6 +1159,16 @@ | |
} | |
} | |
+ /** | |
+ * Return general information about the memory state of the system. This | |
+ * can be used to help decide how to manage your own memory, though note | |
+ * that polling is not recommended and | |
+ * {@link android.content.ComponentCallbacks2#onTrimMemory(int) | |
+ * ComponentCallbacks2.onTrimMemory(int)} is the preferred way to do this. | |
+ * Also see {@link #getMyMemoryState} for how to retrieve the current trim | |
+ * level of your process as needed, which gives a better hint for how to | |
+ * manage its memory. | |
+ */ | |
public void getMemoryInfo(MemoryInfo outInfo) { | |
try { | |
ActivityManagerNative.getDefault().getMemoryInfo(outInfo); | |
@@ -975,7 +1182,7 @@ | |
public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) { | |
try { | |
return ActivityManagerNative.getDefault().clearApplicationUserData(packageName, | |
- observer); | |
+ observer, Binder.getOrigCallingUser()); | |
} catch (RemoteException e) { | |
return false; | |
} | |
@@ -1144,7 +1351,21 @@ | |
* @hide | |
*/ | |
public int flags; | |
- | |
+ | |
+ /** | |
+ * Last memory trim level reported to the process: corresponds to | |
+ * the values supplied to {@link android.content.ComponentCallbacks2#onTrimMemory(int) | |
+ * ComponentCallbacks2.onTrimMemory(int)}. | |
+ */ | |
+ public int lastTrimLevel; | |
+ | |
+ /** | |
+ * Constant for {@link #importance}: this is a persistent process. | |
+ * Only used when reporting to process observers. | |
+ * @hide | |
+ */ | |
+ public static final int IMPORTANCE_PERSISTENT = 50; | |
+ | |
/** | |
* Constant for {@link #importance}: this process is running the | |
* foreground UI. | |
@@ -1211,7 +1432,7 @@ | |
* be maintained in the future. | |
*/ | |
public int lru; | |
- | |
+ | |
/** | |
* Constant for {@link #importanceReasonCode}: nothing special has | |
* been specified for the reason for this level. | |
@@ -1281,6 +1502,7 @@ | |
dest.writeInt(uid); | |
dest.writeStringArray(pkgList); | |
dest.writeInt(this.flags); | |
+ dest.writeInt(lastTrimLevel); | |
dest.writeInt(importance); | |
dest.writeInt(lru); | |
dest.writeInt(importanceReasonCode); | |
@@ -1295,6 +1517,7 @@ | |
uid = source.readInt(); | |
pkgList = source.readStringArray(); | |
flags = source.readInt(); | |
+ lastTrimLevel = source.readInt(); | |
importance = source.readInt(); | |
lru = source.readInt(); | |
importanceReasonCode = source.readInt(); | |
@@ -1322,6 +1545,9 @@ | |
* Returns a list of application processes installed on external media | |
* that are running on the device. | |
* | |
+ * <p><b>Note: this method is only intended for debugging or building | |
+ * a user-facing process management UI.</b></p> | |
+ * | |
* @return Returns a list of ApplicationInfo records, or null if none | |
* This list ordering is not specified. | |
* @hide | |
@@ -1336,7 +1562,10 @@ | |
/** | |
* Returns a list of application processes that are running on the device. | |
- * | |
+ * | |
+ * <p><b>Note: this method is only intended for debugging or building | |
+ * a user-facing process management UI.</b></p> | |
+ * | |
* @return Returns a list of RunningAppProcessInfo records, or null if there are no | |
* running processes (it will not return an empty list). This list ordering is not | |
* specified. | |
@@ -1348,10 +1577,31 @@ | |
return null; | |
} | |
} | |
- | |
+ | |
+ /** | |
+ * Return global memory state information for the calling process. This | |
+ * does not fill in all fields of the {@link RunningAppProcessInfo}. The | |
+ * only fields that will be filled in are | |
+ * {@link RunningAppProcessInfo#pid}, | |
+ * {@link RunningAppProcessInfo#uid}, | |
+ * {@link RunningAppProcessInfo#lastTrimLevel}, | |
+ * {@link RunningAppProcessInfo#importance}, | |
+ * {@link RunningAppProcessInfo#lru}, and | |
+ * {@link RunningAppProcessInfo#importanceReasonCode}. | |
+ */ | |
+ static public void getMyMemoryState(RunningAppProcessInfo outState) { | |
+ try { | |
+ ActivityManagerNative.getDefault().getMyMemoryState(outState); | |
+ } catch (RemoteException e) { | |
+ } | |
+ } | |
+ | |
/** | |
* Return information about the memory usage of one or more processes. | |
- * | |
+ * | |
+ * <p><b>Note: this method is only intended for debugging or building | |
+ * a user-facing process management UI.</b></p> | |
+ * | |
* @param pids The pids of the processes whose memory usage is to be | |
* retrieved. | |
* @return Returns an array of memory information, one for each | |
@@ -1442,9 +1692,10 @@ | |
public int getLauncherLargeIconDensity() { | |
final Resources res = mContext.getResources(); | |
final int density = res.getDisplayMetrics().densityDpi; | |
+ final int sw = res.getConfiguration().smallestScreenWidthDp; | |
- if ((res.getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) | |
- != Configuration.SCREENLAYOUT_SIZE_XLARGE) { | |
+ if (sw < 600) { | |
+ // Smaller than approx 7" tablets, use the regular icon size. | |
return density; | |
} | |
@@ -1453,12 +1704,18 @@ | |
return DisplayMetrics.DENSITY_MEDIUM; | |
case DisplayMetrics.DENSITY_MEDIUM: | |
return DisplayMetrics.DENSITY_HIGH; | |
+ case DisplayMetrics.DENSITY_TV: | |
+ return DisplayMetrics.DENSITY_XHIGH; | |
case DisplayMetrics.DENSITY_HIGH: | |
return DisplayMetrics.DENSITY_XHIGH; | |
case DisplayMetrics.DENSITY_XHIGH: | |
- return DisplayMetrics.DENSITY_MEDIUM * 2; | |
+ return DisplayMetrics.DENSITY_XXHIGH; | |
+ case DisplayMetrics.DENSITY_XXHIGH: | |
+ return DisplayMetrics.DENSITY_XHIGH * 2; | |
default: | |
- return density; | |
+ // The density is some abnormal value. Return some other | |
+ // abnormal value that is a reasonable scaling of it. | |
+ return (int)((density*1.5f)+.5f); | |
} | |
} | |
@@ -1471,9 +1728,10 @@ | |
public int getLauncherLargeIconSize() { | |
final Resources res = mContext.getResources(); | |
final int size = res.getDimensionPixelSize(android.R.dimen.app_icon_size); | |
+ final int sw = res.getConfiguration().smallestScreenWidthDp; | |
- if ((res.getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) | |
- != Configuration.SCREENLAYOUT_SIZE_XLARGE) { | |
+ if (sw < 600) { | |
+ // Smaller than approx 7" tablets, use the regular icon size. | |
return size; | |
} | |
@@ -1484,12 +1742,18 @@ | |
return (size * DisplayMetrics.DENSITY_MEDIUM) / DisplayMetrics.DENSITY_LOW; | |
case DisplayMetrics.DENSITY_MEDIUM: | |
return (size * DisplayMetrics.DENSITY_HIGH) / DisplayMetrics.DENSITY_MEDIUM; | |
+ case DisplayMetrics.DENSITY_TV: | |
+ return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH; | |
case DisplayMetrics.DENSITY_HIGH: | |
return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH; | |
case DisplayMetrics.DENSITY_XHIGH: | |
- return (size * DisplayMetrics.DENSITY_MEDIUM * 2) / DisplayMetrics.DENSITY_XHIGH; | |
+ return (size * DisplayMetrics.DENSITY_XXHIGH) / DisplayMetrics.DENSITY_XHIGH; | |
+ case DisplayMetrics.DENSITY_XXHIGH: | |
+ return (size * DisplayMetrics.DENSITY_XHIGH*2) / DisplayMetrics.DENSITY_XXHIGH; | |
default: | |
- return size; | |
+ // The density is some abnormal value. Return some other | |
+ // abnormal value that is a reasonable scaling of it. | |
+ return (int)((size*1.5f) + .5f); | |
} | |
} | |
@@ -1542,6 +1806,40 @@ | |
} | |
} | |
+ /** @hide */ | |
+ public static int checkComponentPermission(String permission, int uid, | |
+ int owningUid, boolean exported) { | |
+ // Root, system server get to do everything. | |
+ if (uid == 0 || uid == Process.SYSTEM_UID) { | |
+ return PackageManager.PERMISSION_GRANTED; | |
+ } | |
+ // Isolated processes don't get any permissions. | |
+ if (UserId.isIsolated(uid)) { | |
+ return PackageManager.PERMISSION_DENIED; | |
+ } | |
+ // If there is a uid that owns whatever is being accessed, it has | |
+ // blanket access to it regardless of the permissions it requires. | |
+ if (owningUid >= 0 && UserId.isSameApp(uid, owningUid)) { | |
+ return PackageManager.PERMISSION_GRANTED; | |
+ } | |
+ // If the target is not exported, then nobody else can get to it. | |
+ if (!exported) { | |
+ Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid); | |
+ return PackageManager.PERMISSION_DENIED; | |
+ } | |
+ if (permission == null) { | |
+ return PackageManager.PERMISSION_GRANTED; | |
+ } | |
+ try { | |
+ return AppGlobals.getPackageManager() | |
+ .checkUidPermission(permission, uid); | |
+ } catch (RemoteException e) { | |
+ // Should never happen, but if it does... deny! | |
+ Slog.e(TAG, "PackageManager is dead?!?", e); | |
+ } | |
+ return PackageManager.PERMISSION_DENIED; | |
+ } | |
+ | |
/** | |
* Returns the usage statistics of each installed package. | |
* | |
@@ -1571,5 +1869,4 @@ | |
return false; | |
} | |
} | |
- | |
} | |
diff -Nur android-15/android/app/ActivityManagerNative.java android-16/android/app/ActivityManagerNative.java | |
--- android-15/android/app/ActivityManagerNative.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/ActivityManagerNative.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -17,25 +17,26 @@ | |
package android.app; | |
import android.content.ComponentName; | |
+import android.content.IIntentReceiver; | |
+import android.content.IIntentSender; | |
import android.content.Intent; | |
import android.content.IntentFilter; | |
-import android.content.IIntentSender; | |
-import android.content.IIntentReceiver; | |
import android.content.IntentSender; | |
import android.content.pm.ApplicationInfo; | |
import android.content.pm.ConfigurationInfo; | |
import android.content.pm.IPackageDataObserver; | |
+import android.content.pm.UserInfo; | |
import android.content.res.Configuration; | |
import android.graphics.Bitmap; | |
import android.net.Uri; | |
import android.os.Binder; | |
import android.os.Bundle; | |
import android.os.Debug; | |
-import android.os.Parcelable; | |
-import android.os.ParcelFileDescriptor; | |
-import android.os.RemoteException; | |
import android.os.IBinder; | |
import android.os.Parcel; | |
+import android.os.ParcelFileDescriptor; | |
+import android.os.Parcelable; | |
+import android.os.RemoteException; | |
import android.os.ServiceManager; | |
import android.os.StrictMode; | |
import android.text.TextUtils; | |
@@ -91,7 +92,7 @@ | |
try { | |
getDefault().broadcastIntent( | |
null, intent, null, null, Activity.RESULT_OK, null, null, | |
- null /*permission*/, false, true); | |
+ null /*permission*/, false, true, Binder.getOrigCallingUser()); | |
} catch (RemoteException ex) { | |
} | |
} | |
@@ -117,20 +118,18 @@ | |
IApplicationThread app = ApplicationThreadNative.asInterface(b); | |
Intent intent = Intent.CREATOR.createFromParcel(data); | |
String resolvedType = data.readString(); | |
- Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR); | |
- int grantedMode = data.readInt(); | |
IBinder resultTo = data.readStrongBinder(); | |
- String resultWho = data.readString(); | |
+ String resultWho = data.readString(); | |
int requestCode = data.readInt(); | |
- boolean onlyIfNeeded = data.readInt() != 0; | |
- boolean debug = data.readInt() != 0; | |
+ int startFlags = data.readInt(); | |
String profileFile = data.readString(); | |
ParcelFileDescriptor profileFd = data.readInt() != 0 | |
? data.readFileDescriptor() : null; | |
- boolean autoStopProfiler = data.readInt() != 0; | |
+ Bundle options = data.readInt() != 0 | |
+ ? Bundle.CREATOR.createFromParcel(data) : null; | |
int result = startActivity(app, intent, resolvedType, | |
- grantedUriPermissions, grantedMode, resultTo, resultWho, | |
- requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler); | |
+ resultTo, resultWho, requestCode, startFlags, | |
+ profileFile, profileFd, options); | |
reply.writeNoException(); | |
reply.writeInt(result); | |
return true; | |
@@ -143,20 +142,18 @@ | |
IApplicationThread app = ApplicationThreadNative.asInterface(b); | |
Intent intent = Intent.CREATOR.createFromParcel(data); | |
String resolvedType = data.readString(); | |
- Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR); | |
- int grantedMode = data.readInt(); | |
IBinder resultTo = data.readStrongBinder(); | |
- String resultWho = data.readString(); | |
+ String resultWho = data.readString(); | |
int requestCode = data.readInt(); | |
- boolean onlyIfNeeded = data.readInt() != 0; | |
- boolean debug = data.readInt() != 0; | |
+ int startFlags = data.readInt(); | |
String profileFile = data.readString(); | |
ParcelFileDescriptor profileFd = data.readInt() != 0 | |
? data.readFileDescriptor() : null; | |
- boolean autoStopProfiler = data.readInt() != 0; | |
+ Bundle options = data.readInt() != 0 | |
+ ? Bundle.CREATOR.createFromParcel(data) : null; | |
WaitResult result = startActivityAndWait(app, intent, resolvedType, | |
- grantedUriPermissions, grantedMode, resultTo, resultWho, | |
- requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler); | |
+ resultTo, resultWho, requestCode, startFlags, | |
+ profileFile, profileFd, options); | |
reply.writeNoException(); | |
result.writeToParcel(reply, 0); | |
return true; | |
@@ -169,17 +166,15 @@ | |
IApplicationThread app = ApplicationThreadNative.asInterface(b); | |
Intent intent = Intent.CREATOR.createFromParcel(data); | |
String resolvedType = data.readString(); | |
- Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR); | |
- int grantedMode = data.readInt(); | |
IBinder resultTo = data.readStrongBinder(); | |
String resultWho = data.readString(); | |
int requestCode = data.readInt(); | |
- boolean onlyIfNeeded = data.readInt() != 0; | |
- boolean debug = data.readInt() != 0; | |
+ int startFlags = data.readInt(); | |
Configuration config = Configuration.CREATOR.createFromParcel(data); | |
+ Bundle options = data.readInt() != 0 | |
+ ? Bundle.CREATOR.createFromParcel(data) : null; | |
int result = startActivityWithConfig(app, intent, resolvedType, | |
- grantedUriPermissions, grantedMode, resultTo, resultWho, | |
- requestCode, onlyIfNeeded, debug, config); | |
+ resultTo, resultWho, requestCode, startFlags, config, options); | |
reply.writeNoException(); | |
reply.writeInt(result); | |
return true; | |
@@ -201,9 +196,11 @@ | |
int requestCode = data.readInt(); | |
int flagsMask = data.readInt(); | |
int flagsValues = data.readInt(); | |
+ Bundle options = data.readInt() != 0 | |
+ ? Bundle.CREATOR.createFromParcel(data) : null; | |
int result = startActivityIntentSender(app, intent, | |
fillInIntent, resolvedType, resultTo, resultWho, | |
- requestCode, flagsMask, flagsValues); | |
+ requestCode, flagsMask, flagsValues, options); | |
reply.writeNoException(); | |
reply.writeInt(result); | |
return true; | |
@@ -214,12 +211,14 @@ | |
data.enforceInterface(IActivityManager.descriptor); | |
IBinder callingActivity = data.readStrongBinder(); | |
Intent intent = Intent.CREATOR.createFromParcel(data); | |
- boolean result = startNextMatchingActivity(callingActivity, intent); | |
+ Bundle options = data.readInt() != 0 | |
+ ? Bundle.CREATOR.createFromParcel(data) : null; | |
+ boolean result = startNextMatchingActivity(callingActivity, intent, options); | |
reply.writeNoException(); | |
reply.writeInt(result ? 1 : 0); | |
return true; | |
} | |
- | |
+ | |
case FINISH_ACTIVITY_TRANSACTION: { | |
data.enforceInterface(IActivityManager.descriptor); | |
IBinder token = data.readStrongBinder(); | |
@@ -244,6 +243,15 @@ | |
return true; | |
} | |
+ case FINISH_ACTIVITY_AFFINITY_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ IBinder token = data.readStrongBinder(); | |
+ boolean res = finishActivityAffinity(token); | |
+ reply.writeNoException(); | |
+ reply.writeInt(res ? 1 : 0); | |
+ return true; | |
+ } | |
+ | |
case WILL_ACTIVITY_BE_VISIBLE_TRANSACTION: { | |
data.enforceInterface(IActivityManager.descriptor); | |
IBinder token = data.readStrongBinder(); | |
@@ -306,9 +314,10 @@ | |
String perm = data.readString(); | |
boolean serialized = data.readInt() != 0; | |
boolean sticky = data.readInt() != 0; | |
+ int userId = data.readInt(); | |
int res = broadcastIntent(app, intent, resolvedType, resultTo, | |
resultCode, resultData, resultExtras, perm, | |
- serialized, sticky); | |
+ serialized, sticky, userId); | |
reply.writeNoException(); | |
reply.writeInt(res); | |
return true; | |
@@ -320,7 +329,8 @@ | |
IBinder b = data.readStrongBinder(); | |
IApplicationThread app = b != null ? ApplicationThreadNative.asInterface(b) : null; | |
Intent intent = Intent.CREATOR.createFromParcel(data); | |
- unbroadcastIntent(app, intent); | |
+ int userId = data.readInt(); | |
+ unbroadcastIntent(app, intent, userId); | |
reply.writeNoException(); | |
return true; | |
} | |
@@ -510,7 +520,9 @@ | |
data.enforceInterface(IActivityManager.descriptor); | |
int task = data.readInt(); | |
int fl = data.readInt(); | |
- moveTaskToFront(task, fl); | |
+ Bundle options = data.readInt() != 0 | |
+ ? Bundle.CREATOR.createFromParcel(data) : null; | |
+ moveTaskToFront(task, fl, options); | |
reply.writeNoException(); | |
return true; | |
} | |
@@ -552,15 +564,6 @@ | |
return true; | |
} | |
- case FINISH_OTHER_INSTANCES_TRANSACTION: { | |
- data.enforceInterface(IActivityManager.descriptor); | |
- IBinder token = data.readStrongBinder(); | |
- ComponentName className = ComponentName.readFromParcel(data); | |
- finishOtherInstances(token, className); | |
- reply.writeNoException(); | |
- return true; | |
- } | |
- | |
case REPORT_THUMBNAIL_TRANSACTION: { | |
data.enforceInterface(IActivityManager.descriptor); | |
IBinder token = data.readStrongBinder(); | |
@@ -577,7 +580,23 @@ | |
IBinder b = data.readStrongBinder(); | |
IApplicationThread app = ApplicationThreadNative.asInterface(b); | |
String name = data.readString(); | |
- ContentProviderHolder cph = getContentProvider(app, name); | |
+ boolean stable = data.readInt() != 0; | |
+ ContentProviderHolder cph = getContentProvider(app, name, stable); | |
+ reply.writeNoException(); | |
+ if (cph != null) { | |
+ reply.writeInt(1); | |
+ cph.writeToParcel(reply, 0); | |
+ } else { | |
+ reply.writeInt(0); | |
+ } | |
+ return true; | |
+ } | |
+ | |
+ case GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ String name = data.readString(); | |
+ IBinder token = data.readStrongBinder(); | |
+ ContentProviderHolder cph = getContentProviderExternal(name, token); | |
reply.writeNoException(); | |
if (cph != null) { | |
reply.writeInt(1); | |
@@ -599,16 +618,43 @@ | |
return true; | |
} | |
+ case REF_CONTENT_PROVIDER_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ IBinder b = data.readStrongBinder(); | |
+ int stable = data.readInt(); | |
+ int unstable = data.readInt(); | |
+ boolean res = refContentProvider(b, stable, unstable); | |
+ reply.writeNoException(); | |
+ reply.writeInt(res ? 1 : 0); | |
+ return true; | |
+ } | |
+ | |
+ case UNSTABLE_PROVIDER_DIED_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ IBinder b = data.readStrongBinder(); | |
+ unstableProviderDied(b); | |
+ reply.writeNoException(); | |
+ return true; | |
+ } | |
+ | |
case REMOVE_CONTENT_PROVIDER_TRANSACTION: { | |
data.enforceInterface(IActivityManager.descriptor); | |
IBinder b = data.readStrongBinder(); | |
- IApplicationThread app = ApplicationThreadNative.asInterface(b); | |
+ boolean stable = data.readInt() != 0; | |
+ removeContentProvider(b, stable); | |
+ reply.writeNoException(); | |
+ return true; | |
+ } | |
+ | |
+ case REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
String name = data.readString(); | |
- removeContentProvider(app, name); | |
+ IBinder token = data.readStrongBinder(); | |
+ removeContentProviderExternal(name, token); | |
reply.writeNoException(); | |
return true; | |
} | |
- | |
+ | |
case GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION: { | |
data.enforceInterface(IActivityManager.descriptor); | |
ComponentName comp = ComponentName.CREATOR.createFromParcel(data); | |
@@ -677,8 +723,9 @@ | |
String resolvedType = data.readString(); | |
b = data.readStrongBinder(); | |
int fl = data.readInt(); | |
+ int userId = data.readInt(); | |
IServiceConnection conn = IServiceConnection.Stub.asInterface(b); | |
- int res = bindService(app, token, service, resolvedType, conn, fl); | |
+ int res = bindService(app, token, service, resolvedType, conn, fl, userId); | |
reply.writeNoException(); | |
reply.writeInt(res); | |
return true; | |
@@ -819,9 +866,11 @@ | |
requestResolvedTypes = null; | |
} | |
int fl = data.readInt(); | |
+ Bundle options = data.readInt() != 0 | |
+ ? Bundle.CREATOR.createFromParcel(data) : null; | |
IIntentSender res = getIntentSender(type, packageName, token, | |
resultWho, requestCode, requestIntents, | |
- requestResolvedTypes, fl); | |
+ requestResolvedTypes, fl, options); | |
reply.writeNoException(); | |
reply.writeStrongBinder(res != null ? res.asBinder() : null); | |
return true; | |
@@ -846,6 +895,16 @@ | |
return true; | |
} | |
+ case GET_UID_FOR_INTENT_SENDER_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ IIntentSender r = IIntentSender.Stub.asInterface( | |
+ data.readStrongBinder()); | |
+ int res = getUidForIntentSender(r); | |
+ reply.writeNoException(); | |
+ reply.writeInt(res); | |
+ return true; | |
+ } | |
+ | |
case SET_PROCESS_LIMIT_TRANSACTION: { | |
data.enforceInterface(IActivityManager.descriptor); | |
int max = data.readInt(); | |
@@ -900,7 +959,8 @@ | |
String packageName = data.readString(); | |
IPackageDataObserver observer = IPackageDataObserver.Stub.asInterface( | |
data.readStrongBinder()); | |
- boolean res = clearApplicationUserData(packageName, observer); | |
+ int userId = data.readInt(); | |
+ boolean res = clearApplicationUserData(packageName, observer, userId); | |
reply.writeNoException(); | |
reply.writeInt(res ? 1 : 0); | |
return true; | |
@@ -968,7 +1028,7 @@ | |
} | |
return true; | |
} | |
- | |
+ | |
case GOING_TO_SLEEP_TRANSACTION: { | |
data.enforceInterface(IActivityManager.descriptor); | |
goingToSleep(); | |
@@ -983,6 +1043,13 @@ | |
return true; | |
} | |
+ case SET_LOCK_SCREEN_SHOWN_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ setLockScreenShown(data.readInt() != 0); | |
+ reply.writeNoException(); | |
+ return true; | |
+ } | |
+ | |
case SET_DEBUG_APP_TRANSACTION: { | |
data.enforceInterface(IActivityManager.descriptor); | |
String pn = data.readString(); | |
@@ -1036,6 +1103,15 @@ | |
return true; | |
} | |
+ case KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ String reason = data.readString(); | |
+ boolean res = killProcessesBelowForeground(reason); | |
+ reply.writeNoException(); | |
+ reply.writeInt(res ? 1 : 0); | |
+ return true; | |
+ } | |
+ | |
case START_RUNNING_TRANSACTION: { | |
data.enforceInterface(IActivityManager.descriptor); | |
String pkg = data.readString(); | |
@@ -1107,7 +1183,17 @@ | |
reply.writeNoException(); | |
return true; | |
} | |
- | |
+ | |
+ case GET_MY_MEMORY_STATE_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ ActivityManager.RunningAppProcessInfo info = | |
+ new ActivityManager.RunningAppProcessInfo(); | |
+ getMyMemoryState(info); | |
+ reply.writeNoException(); | |
+ info.writeToParcel(reply, 0); | |
+ return true; | |
+ } | |
+ | |
case GET_DEVICE_CONFIGURATION_TRANSACTION: { | |
data.enforceInterface(IActivityManager.descriptor); | |
ConfigurationInfo config = getDeviceConfigurationInfo(); | |
@@ -1189,22 +1275,6 @@ | |
return true; | |
} | |
- case REGISTER_ACTIVITY_WATCHER_TRANSACTION: { | |
- data.enforceInterface(IActivityManager.descriptor); | |
- IActivityWatcher watcher = IActivityWatcher.Stub.asInterface( | |
- data.readStrongBinder()); | |
- registerActivityWatcher(watcher); | |
- return true; | |
- } | |
- | |
- case UNREGISTER_ACTIVITY_WATCHER_TRANSACTION: { | |
- data.enforceInterface(IActivityManager.descriptor); | |
- IActivityWatcher watcher = IActivityWatcher.Stub.asInterface( | |
- data.readStrongBinder()); | |
- unregisterActivityWatcher(watcher); | |
- return true; | |
- } | |
- | |
case START_ACTIVITY_IN_PACKAGE_TRANSACTION: | |
{ | |
data.enforceInterface(IActivityManager.descriptor); | |
@@ -1214,9 +1284,11 @@ | |
IBinder resultTo = data.readStrongBinder(); | |
String resultWho = data.readString(); | |
int requestCode = data.readInt(); | |
- boolean onlyIfNeeded = data.readInt() != 0; | |
+ int startFlags = data.readInt(); | |
+ Bundle options = data.readInt() != 0 | |
+ ? Bundle.CREATOR.createFromParcel(data) : null; | |
int result = startActivityInPackage(uid, intent, resolvedType, | |
- resultTo, resultWho, requestCode, onlyIfNeeded); | |
+ resultTo, resultWho, requestCode, startFlags, options); | |
reply.writeNoException(); | |
reply.writeInt(result); | |
return true; | |
@@ -1395,7 +1467,10 @@ | |
Intent[] intents = data.createTypedArray(Intent.CREATOR); | |
String[] resolvedTypes = data.createStringArray(); | |
IBinder resultTo = data.readStrongBinder(); | |
- int result = startActivitiesInPackage(uid, intents, resolvedTypes, resultTo); | |
+ Bundle options = data.readInt() != 0 | |
+ ? Bundle.CREATOR.createFromParcel(data) : null; | |
+ int result = startActivitiesInPackage(uid, intents, resolvedTypes, | |
+ resultTo, options); | |
reply.writeNoException(); | |
reply.writeInt(result); | |
return true; | |
@@ -1409,7 +1484,10 @@ | |
Intent[] intents = data.createTypedArray(Intent.CREATOR); | |
String[] resolvedTypes = data.createStringArray(); | |
IBinder resultTo = data.readStrongBinder(); | |
- int result = startActivities(app, intents, resolvedTypes, resultTo); | |
+ Bundle options = data.readInt() != 0 | |
+ ? Bundle.CREATOR.createFromParcel(data) : null; | |
+ int result = startActivities(app, intents, resolvedTypes, resultTo, | |
+ options); | |
reply.writeNoException(); | |
reply.writeInt(result); | |
return true; | |
@@ -1462,7 +1540,15 @@ | |
reply.writeInt(result ? 1 : 0); | |
return true; | |
} | |
- | |
+ | |
+ case GET_CURRENT_USER_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ UserInfo userInfo = getCurrentUser(); | |
+ reply.writeNoException(); | |
+ userInfo.writeToParcel(reply, 0); | |
+ return true; | |
+ } | |
+ | |
case REMOVE_SUB_TASK_TRANSACTION: | |
{ | |
data.enforceInterface(IActivityManager.descriptor); | |
@@ -1564,6 +1650,40 @@ | |
return true; | |
} | |
+ case TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ IBinder token = data.readStrongBinder(); | |
+ String destAffinity = data.readString(); | |
+ boolean res = targetTaskAffinityMatchesActivity(token, destAffinity); | |
+ reply.writeNoException(); | |
+ reply.writeInt(res ? 1 : 0); | |
+ return true; | |
+ } | |
+ | |
+ case NAVIGATE_UP_TO_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ IBinder token = data.readStrongBinder(); | |
+ Intent target = Intent.CREATOR.createFromParcel(data); | |
+ int resultCode = data.readInt(); | |
+ Intent resultData = null; | |
+ if (data.readInt() != 0) { | |
+ resultData = Intent.CREATOR.createFromParcel(data); | |
+ } | |
+ boolean res = navigateUpTo(token, target, resultCode, resultData); | |
+ reply.writeNoException(); | |
+ reply.writeInt(res ? 1 : 0); | |
+ return true; | |
+ } | |
+ | |
+ case GET_LAUNCHED_FROM_UID_TRANSACTION: { | |
+ data.enforceInterface(IActivityManager.descriptor); | |
+ IBinder token = data.readStrongBinder(); | |
+ int res = getLaunchedFromUid(token); | |
+ reply.writeNoException(); | |
+ reply.writeInt(res); | |
+ return true; | |
+ } | |
+ | |
} | |
return super.onTransact(code, data, reply, flags); | |
@@ -1594,31 +1714,26 @@ | |
{ | |
mRemote = remote; | |
} | |
- | |
+ | |
public IBinder asBinder() | |
{ | |
return mRemote; | |
} | |
- | |
+ | |
public int startActivity(IApplicationThread caller, Intent intent, | |
- String resolvedType, Uri[] grantedUriPermissions, int grantedMode, | |
- IBinder resultTo, String resultWho, | |
- int requestCode, boolean onlyIfNeeded, | |
- boolean debug, String profileFile, ParcelFileDescriptor profileFd, | |
- boolean autoStopProfiler) throws RemoteException { | |
+ String resolvedType, IBinder resultTo, String resultWho, int requestCode, | |
+ int startFlags, String profileFile, | |
+ ParcelFileDescriptor profileFd, Bundle options) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
data.writeStrongBinder(caller != null ? caller.asBinder() : null); | |
intent.writeToParcel(data, 0); | |
data.writeString(resolvedType); | |
- data.writeTypedArray(grantedUriPermissions, 0); | |
- data.writeInt(grantedMode); | |
data.writeStrongBinder(resultTo); | |
data.writeString(resultWho); | |
data.writeInt(requestCode); | |
- data.writeInt(onlyIfNeeded ? 1 : 0); | |
- data.writeInt(debug ? 1 : 0); | |
+ data.writeInt(startFlags); | |
data.writeString(profileFile); | |
if (profileFd != null) { | |
data.writeInt(1); | |
@@ -1626,7 +1741,12 @@ | |
} else { | |
data.writeInt(0); | |
} | |
- data.writeInt(autoStopProfiler ? 1 : 0); | |
+ if (options != null) { | |
+ data.writeInt(1); | |
+ options.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
int result = reply.readInt(); | |
@@ -1635,24 +1755,19 @@ | |
return result; | |
} | |
public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent, | |
- String resolvedType, Uri[] grantedUriPermissions, int grantedMode, | |
- IBinder resultTo, String resultWho, | |
- int requestCode, boolean onlyIfNeeded, | |
- boolean debug, String profileFile, ParcelFileDescriptor profileFd, | |
- boolean autoStopProfiler) throws RemoteException { | |
+ String resolvedType, IBinder resultTo, String resultWho, | |
+ int requestCode, int startFlags, String profileFile, | |
+ ParcelFileDescriptor profileFd, Bundle options) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
data.writeStrongBinder(caller != null ? caller.asBinder() : null); | |
intent.writeToParcel(data, 0); | |
data.writeString(resolvedType); | |
- data.writeTypedArray(grantedUriPermissions, 0); | |
- data.writeInt(grantedMode); | |
data.writeStrongBinder(resultTo); | |
data.writeString(resultWho); | |
data.writeInt(requestCode); | |
- data.writeInt(onlyIfNeeded ? 1 : 0); | |
- data.writeInt(debug ? 1 : 0); | |
+ data.writeInt(startFlags); | |
data.writeString(profileFile); | |
if (profileFd != null) { | |
data.writeInt(1); | |
@@ -1660,7 +1775,12 @@ | |
} else { | |
data.writeInt(0); | |
} | |
- data.writeInt(autoStopProfiler ? 1 : 0); | |
+ if (options != null) { | |
+ data.writeInt(1); | |
+ options.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
mRemote.transact(START_ACTIVITY_AND_WAIT_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
WaitResult result = WaitResult.CREATOR.createFromParcel(reply); | |
@@ -1669,24 +1789,26 @@ | |
return result; | |
} | |
public int startActivityWithConfig(IApplicationThread caller, Intent intent, | |
- String resolvedType, Uri[] grantedUriPermissions, int grantedMode, | |
- IBinder resultTo, String resultWho, | |
- int requestCode, boolean onlyIfNeeded, | |
- boolean debug, Configuration config) throws RemoteException { | |
+ String resolvedType, IBinder resultTo, String resultWho, | |
+ int requestCode, int startFlags, Configuration config, | |
+ Bundle options) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
data.writeStrongBinder(caller != null ? caller.asBinder() : null); | |
intent.writeToParcel(data, 0); | |
data.writeString(resolvedType); | |
- data.writeTypedArray(grantedUriPermissions, 0); | |
- data.writeInt(grantedMode); | |
data.writeStrongBinder(resultTo); | |
data.writeString(resultWho); | |
data.writeInt(requestCode); | |
- data.writeInt(onlyIfNeeded ? 1 : 0); | |
- data.writeInt(debug ? 1 : 0); | |
+ data.writeInt(startFlags); | |
config.writeToParcel(data, 0); | |
+ if (options != null) { | |
+ data.writeInt(1); | |
+ options.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
int result = reply.readInt(); | |
@@ -1697,7 +1819,7 @@ | |
public int startActivityIntentSender(IApplicationThread caller, | |
IntentSender intent, Intent fillInIntent, String resolvedType, | |
IBinder resultTo, String resultWho, int requestCode, | |
- int flagsMask, int flagsValues) throws RemoteException { | |
+ int flagsMask, int flagsValues, Bundle options) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
@@ -1715,6 +1837,12 @@ | |
data.writeInt(requestCode); | |
data.writeInt(flagsMask); | |
data.writeInt(flagsValues); | |
+ if (options != null) { | |
+ data.writeInt(1); | |
+ options.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
mRemote.transact(START_ACTIVITY_INTENT_SENDER_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
int result = reply.readInt(); | |
@@ -1723,12 +1851,18 @@ | |
return result; | |
} | |
public boolean startNextMatchingActivity(IBinder callingActivity, | |
- Intent intent) throws RemoteException { | |
+ Intent intent, Bundle options) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
data.writeStrongBinder(callingActivity); | |
intent.writeToParcel(data, 0); | |
+ if (options != null) { | |
+ data.writeInt(1); | |
+ options.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
mRemote.transact(START_NEXT_MATCHING_ACTIVITY_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
int result = reply.readInt(); | |
@@ -1769,6 +1903,18 @@ | |
data.recycle(); | |
reply.recycle(); | |
} | |
+ public boolean finishActivityAffinity(IBinder token) throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeStrongBinder(token); | |
+ mRemote.transact(FINISH_ACTIVITY_AFFINITY_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ boolean res = reply.readInt() != 0; | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ return res; | |
+ } | |
public boolean willActivityBeVisible(IBinder token) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
@@ -1819,7 +1965,7 @@ | |
Intent intent, String resolvedType, IIntentReceiver resultTo, | |
int resultCode, String resultData, Bundle map, | |
String requiredPermission, boolean serialized, | |
- boolean sticky) throws RemoteException | |
+ boolean sticky, int userId) throws RemoteException | |
{ | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
@@ -1834,6 +1980,7 @@ | |
data.writeString(requiredPermission); | |
data.writeInt(serialized ? 1 : 0); | |
data.writeInt(sticky ? 1 : 0); | |
+ data.writeInt(userId); | |
mRemote.transact(BROADCAST_INTENT_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
int res = reply.readInt(); | |
@@ -1841,13 +1988,15 @@ | |
data.recycle(); | |
return res; | |
} | |
- public void unbroadcastIntent(IApplicationThread caller, Intent intent) throws RemoteException | |
+ public void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) | |
+ throws RemoteException | |
{ | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
data.writeStrongBinder(caller != null ? caller.asBinder() : null); | |
intent.writeToParcel(data, 0); | |
+ data.writeInt(userId); | |
mRemote.transact(UNBROADCAST_INTENT_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
data.recycle(); | |
@@ -2096,13 +2245,19 @@ | |
reply.recycle(); | |
return list; | |
} | |
- public void moveTaskToFront(int task, int flags) throws RemoteException | |
+ public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException | |
{ | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
data.writeInt(task); | |
data.writeInt(flags); | |
+ if (options != null) { | |
+ data.writeInt(1); | |
+ options.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
mRemote.transact(MOVE_TASK_TO_FRONT_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
data.recycle(); | |
@@ -2158,18 +2313,6 @@ | |
reply.recycle(); | |
return res; | |
} | |
- public void finishOtherInstances(IBinder token, ComponentName className) throws RemoteException | |
- { | |
- Parcel data = Parcel.obtain(); | |
- Parcel reply = Parcel.obtain(); | |
- data.writeInterfaceToken(IActivityManager.descriptor); | |
- data.writeStrongBinder(token); | |
- ComponentName.writeToParcel(className, data); | |
- mRemote.transact(FINISH_OTHER_INSTANCES_TRANSACTION, data, reply, 0); | |
- reply.readException(); | |
- data.recycle(); | |
- reply.recycle(); | |
- } | |
public void reportThumbnail(IBinder token, | |
Bitmap thumbnail, CharSequence description) throws RemoteException | |
{ | |
@@ -2190,13 +2333,13 @@ | |
reply.recycle(); | |
} | |
public ContentProviderHolder getContentProvider(IApplicationThread caller, | |
- String name) throws RemoteException | |
- { | |
+ String name, boolean stable) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
data.writeStrongBinder(caller != null ? caller.asBinder() : null); | |
data.writeString(name); | |
+ data.writeInt(stable ? 1 : 0); | |
mRemote.transact(GET_CONTENT_PROVIDER_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
int res = reply.readInt(); | |
@@ -2208,8 +2351,27 @@ | |
reply.recycle(); | |
return cph; | |
} | |
+ public ContentProviderHolder getContentProviderExternal(String name, IBinder token) | |
+ throws RemoteException | |
+ { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeString(name); | |
+ data.writeStrongBinder(token); | |
+ mRemote.transact(GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ int res = reply.readInt(); | |
+ ContentProviderHolder cph = null; | |
+ if (res != 0) { | |
+ cph = ContentProviderHolder.CREATOR.createFromParcel(reply); | |
+ } | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ return cph; | |
+ } | |
public void publishContentProviders(IApplicationThread caller, | |
- List<ContentProviderHolder> providers) throws RemoteException | |
+ List<ContentProviderHolder> providers) throws RemoteException | |
{ | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
@@ -2221,20 +2383,56 @@ | |
data.recycle(); | |
reply.recycle(); | |
} | |
- | |
- public void removeContentProvider(IApplicationThread caller, | |
- String name) throws RemoteException { | |
+ public boolean refContentProvider(IBinder connection, int stable, int unstable) | |
+ throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
- data.writeStrongBinder(caller != null ? caller.asBinder() : null); | |
- data.writeString(name); | |
+ data.writeStrongBinder(connection); | |
+ data.writeInt(stable); | |
+ data.writeInt(unstable); | |
+ mRemote.transact(REF_CONTENT_PROVIDER_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ boolean res = reply.readInt() != 0; | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ return res; | |
+ } | |
+ public void unstableProviderDied(IBinder connection) throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeStrongBinder(connection); | |
+ mRemote.transact(UNSTABLE_PROVIDER_DIED_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ } | |
+ | |
+ public void removeContentProvider(IBinder connection, boolean stable) throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeStrongBinder(connection); | |
+ data.writeInt(stable ? 1 : 0); | |
mRemote.transact(REMOVE_CONTENT_PROVIDER_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
data.recycle(); | |
reply.recycle(); | |
} | |
- | |
+ | |
+ public void removeContentProviderExternal(String name, IBinder token) throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeString(name); | |
+ data.writeStrongBinder(token); | |
+ mRemote.transact(REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ } | |
+ | |
public PendingIntent getRunningServiceControlPanel(ComponentName service) | |
throws RemoteException | |
{ | |
@@ -2319,7 +2517,7 @@ | |
} | |
public int bindService(IApplicationThread caller, IBinder token, | |
Intent service, String resolvedType, IServiceConnection connection, | |
- int flags) throws RemoteException { | |
+ int flags, int userId) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
@@ -2329,6 +2527,7 @@ | |
data.writeString(resolvedType); | |
data.writeStrongBinder(connection.asBinder()); | |
data.writeInt(flags); | |
+ data.writeInt(userId); | |
mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
int res = reply.readInt(); | |
@@ -2551,8 +2750,8 @@ | |
} | |
public IIntentSender getIntentSender(int type, | |
String packageName, IBinder token, String resultWho, | |
- int requestCode, Intent[] intents, String[] resolvedTypes, int flags) | |
- throws RemoteException { | |
+ int requestCode, Intent[] intents, String[] resolvedTypes, int flags, | |
+ Bundle options) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
@@ -2569,6 +2768,12 @@ | |
data.writeInt(0); | |
} | |
data.writeInt(flags); | |
+ if (options != null) { | |
+ data.writeInt(1); | |
+ options.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
IIntentSender res = IIntentSender.Stub.asInterface( | |
@@ -2599,6 +2804,18 @@ | |
reply.recycle(); | |
return res; | |
} | |
+ public int getUidForIntentSender(IIntentSender sender) throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeStrongBinder(sender.asBinder()); | |
+ mRemote.transact(GET_UID_FOR_INTENT_SENDER_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ int res = reply.readInt(); | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ return res; | |
+ } | |
public void setProcessLimit(int max) throws RemoteException | |
{ | |
Parcel data = Parcel.obtain(); | |
@@ -2651,12 +2868,13 @@ | |
return res; | |
} | |
public boolean clearApplicationUserData(final String packageName, | |
- final IPackageDataObserver observer) throws RemoteException { | |
+ final IPackageDataObserver observer, final int userId) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
data.writeString(packageName); | |
data.writeStrongBinder(observer.asBinder()); | |
+ data.writeInt(userId); | |
mRemote.transact(CLEAR_APP_DATA_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
boolean res = reply.readInt() != 0; | |
@@ -2774,6 +2992,17 @@ | |
data.recycle(); | |
reply.recycle(); | |
} | |
+ public void setLockScreenShown(boolean shown) throws RemoteException | |
+ { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeInt(shown ? 1 : 0); | |
+ mRemote.transact(SET_LOCK_SCREEN_SHOWN_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ } | |
public void setDebugApp( | |
String packageName, boolean waitForDebugger, boolean persistent) | |
throws RemoteException | |
@@ -2837,6 +3066,18 @@ | |
reply.recycle(); | |
return res; | |
} | |
+ @Override | |
+ public boolean killProcessesBelowForeground(String reason) throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeString(reason); | |
+ mRemote.transact(KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION, data, reply, 0); | |
+ boolean res = reply.readInt() != 0; | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ return res; | |
+ } | |
public void startRunning(String pkg, String cls, String action, | |
String indata) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
@@ -2946,6 +3187,19 @@ | |
reply.recycle(); | |
} | |
+ public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outInfo) | |
+ throws RemoteException | |
+ { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ mRemote.transact(GET_MY_MEMORY_STATE_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ outInfo.readFromParcel(reply); | |
+ reply.recycle(); | |
+ data.recycle(); | |
+ } | |
+ | |
public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException | |
{ | |
Parcel data = Parcel.obtain(); | |
@@ -3017,33 +3271,9 @@ | |
data.recycle(); | |
} | |
- public void registerActivityWatcher(IActivityWatcher watcher) | |
- throws RemoteException { | |
- Parcel data = Parcel.obtain(); | |
- Parcel reply = Parcel.obtain(); | |
- data.writeInterfaceToken(IActivityManager.descriptor); | |
- data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); | |
- mRemote.transact(REGISTER_ACTIVITY_WATCHER_TRANSACTION, data, reply, 0); | |
- reply.readException(); | |
- data.recycle(); | |
- reply.recycle(); | |
- } | |
- | |
- public void unregisterActivityWatcher(IActivityWatcher watcher) | |
- throws RemoteException { | |
- Parcel data = Parcel.obtain(); | |
- Parcel reply = Parcel.obtain(); | |
- data.writeInterfaceToken(IActivityManager.descriptor); | |
- data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); | |
- mRemote.transact(UNREGISTER_ACTIVITY_WATCHER_TRANSACTION, data, reply, 0); | |
- reply.readException(); | |
- data.recycle(); | |
- reply.recycle(); | |
- } | |
- | |
public int startActivityInPackage(int uid, | |
Intent intent, String resolvedType, IBinder resultTo, | |
- String resultWho, int requestCode, boolean onlyIfNeeded) | |
+ String resultWho, int requestCode, int startFlags, Bundle options) | |
throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
@@ -3054,7 +3284,13 @@ | |
data.writeStrongBinder(resultTo); | |
data.writeString(resultWho); | |
data.writeInt(requestCode); | |
- data.writeInt(onlyIfNeeded ? 1 : 0); | |
+ data.writeInt(startFlags); | |
+ if (options != null) { | |
+ data.writeInt(1); | |
+ options.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
mRemote.transact(START_ACTIVITY_IN_PACKAGE_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
int result = reply.readInt(); | |
@@ -3307,7 +3543,8 @@ | |
} | |
public int startActivities(IApplicationThread caller, | |
- Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException { | |
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo, | |
+ Bundle options) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
@@ -3315,6 +3552,12 @@ | |
data.writeTypedArray(intents, 0); | |
data.writeStringArray(resolvedTypes); | |
data.writeStrongBinder(resultTo); | |
+ if (options != null) { | |
+ data.writeInt(1); | |
+ options.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
mRemote.transact(START_ACTIVITIES_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
int result = reply.readInt(); | |
@@ -3324,7 +3567,8 @@ | |
} | |
public int startActivitiesInPackage(int uid, | |
- Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException { | |
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo, | |
+ Bundle options) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
data.writeInterfaceToken(IActivityManager.descriptor); | |
@@ -3332,6 +3576,12 @@ | |
data.writeTypedArray(intents, 0); | |
data.writeStringArray(resolvedTypes); | |
data.writeStrongBinder(resultTo); | |
+ if (options != null) { | |
+ data.writeInt(1); | |
+ options.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
mRemote.transact(START_ACTIVITIES_IN_PACKAGE_TRANSACTION, data, reply, 0); | |
reply.readException(); | |
int result = reply.readInt(); | |
@@ -3427,7 +3677,19 @@ | |
data.recycle(); | |
return result; | |
} | |
- | |
+ | |
+ public UserInfo getCurrentUser() throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ mRemote.transact(SWITCH_USER_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ UserInfo userInfo = UserInfo.CREATOR.createFromParcel(reply); | |
+ reply.recycle(); | |
+ data.recycle(); | |
+ return userInfo; | |
+ } | |
+ | |
public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
@@ -3538,5 +3800,55 @@ | |
reply.recycle(); | |
} | |
+ public boolean targetTaskAffinityMatchesActivity(IBinder token, String destAffinity) | |
+ throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeStrongBinder(token); | |
+ data.writeString(destAffinity); | |
+ mRemote.transact(TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ boolean result = reply.readInt() != 0; | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ return result; | |
+ } | |
+ | |
+ public boolean navigateUpTo(IBinder token, Intent target, int resultCode, Intent resultData) | |
+ throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeStrongBinder(token); | |
+ target.writeToParcel(data, 0); | |
+ data.writeInt(resultCode); | |
+ if (resultData != null) { | |
+ data.writeInt(1); | |
+ resultData.writeToParcel(data, 0); | |
+ } else { | |
+ data.writeInt(0); | |
+ } | |
+ mRemote.transact(NAVIGATE_UP_TO_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ boolean result = reply.readInt() != 0; | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ return result; | |
+ } | |
+ | |
+ public int getLaunchedFromUid(IBinder activityToken) throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ data.writeInterfaceToken(IActivityManager.descriptor); | |
+ data.writeStrongBinder(activityToken); | |
+ mRemote.transact(GET_LAUNCHED_FROM_UID_TRANSACTION, data, reply, 0); | |
+ reply.readException(); | |
+ int result = reply.readInt(); | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ return result; | |
+ } | |
+ | |
private IBinder mRemote; | |
} | |
diff -Nur android-15/android/app/ActivityOptions.java android-16/android/app/ActivityOptions.java | |
--- android-15/android/app/ActivityOptions.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/app/ActivityOptions.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -0,0 +1,494 @@ | |
+/* | |
+ * 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 android.app; | |
+ | |
+import android.content.Context; | |
+import android.graphics.Bitmap; | |
+import android.os.Bundle; | |
+import android.os.Handler; | |
+import android.os.IRemoteCallback; | |
+import android.os.RemoteException; | |
+import android.view.View; | |
+ | |
+/** | |
+ * Helper class for building an options Bundle that can be used with | |
+ * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle) | |
+ * Context.startActivity(Intent, Bundle)} and related methods. | |
+ */ | |
+public class ActivityOptions { | |
+ /** | |
+ * The package name that created the options. | |
+ * @hide | |
+ */ | |
+ public static final String KEY_PACKAGE_NAME = "android:packageName"; | |
+ | |
+ /** | |
+ * Type of animation that arguments specify. | |
+ * @hide | |
+ */ | |
+ public static final String KEY_ANIM_TYPE = "android:animType"; | |
+ | |
+ /** | |
+ * Custom enter animation resource ID. | |
+ * @hide | |
+ */ | |
+ public static final String KEY_ANIM_ENTER_RES_ID = "android:animEnterRes"; | |
+ | |
+ /** | |
+ * Custom exit animation resource ID. | |
+ * @hide | |
+ */ | |
+ public static final String KEY_ANIM_EXIT_RES_ID = "android:animExitRes"; | |
+ | |
+ /** | |
+ * Bitmap for thumbnail animation. | |
+ * @hide | |
+ */ | |
+ public static final String KEY_ANIM_THUMBNAIL = "android:animThumbnail"; | |
+ | |
+ /** | |
+ * Start X position of thumbnail animation. | |
+ * @hide | |
+ */ | |
+ public static final String KEY_ANIM_START_X = "android:animStartX"; | |
+ | |
+ /** | |
+ * Start Y position of thumbnail animation. | |
+ * @hide | |
+ */ | |
+ public static final String KEY_ANIM_START_Y = "android:animStartY"; | |
+ | |
+ /** | |
+ * Initial width of the animation. | |
+ * @hide | |
+ */ | |
+ public static final String KEY_ANIM_START_WIDTH = "android:animStartWidth"; | |
+ | |
+ /** | |
+ * Initial height of the animation. | |
+ * @hide | |
+ */ | |
+ public static final String KEY_ANIM_START_HEIGHT = "android:animStartHeight"; | |
+ | |
+ /** | |
+ * Callback for when animation is started. | |
+ * @hide | |
+ */ | |
+ public static final String KEY_ANIM_START_LISTENER = "android:animStartListener"; | |
+ | |
+ /** @hide */ | |
+ public static final int ANIM_NONE = 0; | |
+ /** @hide */ | |
+ public static final int ANIM_CUSTOM = 1; | |
+ /** @hide */ | |
+ public static final int ANIM_SCALE_UP = 2; | |
+ /** @hide */ | |
+ public static final int ANIM_THUMBNAIL = 3; | |
+ /** @hide */ | |
+ public static final int ANIM_THUMBNAIL_DELAYED = 4; | |
+ | |
+ private String mPackageName; | |
+ private int mAnimationType = ANIM_NONE; | |
+ private int mCustomEnterResId; | |
+ private int mCustomExitResId; | |
+ private Bitmap mThumbnail; | |
+ private int mStartX; | |
+ private int mStartY; | |
+ private int mStartWidth; | |
+ private int mStartHeight; | |
+ private IRemoteCallback mAnimationStartedListener; | |
+ | |
+ /** | |
+ * Create an ActivityOptions specifying a custom animation to run when | |
+ * the activity is displayed. | |
+ * | |
+ * @param context Who is defining this. This is the application that the | |
+ * animation resources will be loaded from. | |
+ * @param enterResId A resource ID of the animation resource to use for | |
+ * the incoming activity. Use 0 for no animation. | |
+ * @param exitResId A resource ID of the animation resource to use for | |
+ * the outgoing activity. Use 0 for no animation. | |
+ * @return Returns a new ActivityOptions object that you can use to | |
+ * supply these options as the options Bundle when starting an activity. | |
+ */ | |
+ public static ActivityOptions makeCustomAnimation(Context context, | |
+ int enterResId, int exitResId) { | |
+ return makeCustomAnimation(context, enterResId, exitResId, null, null); | |
+ } | |
+ | |
+ /** | |
+ * Create an ActivityOptions specifying a custom animation to run when | |
+ * the activity is displayed. | |
+ * | |
+ * @param context Who is defining this. This is the application that the | |
+ * animation resources will be loaded from. | |
+ * @param enterResId A resource ID of the animation resource to use for | |
+ * the incoming activity. Use 0 for no animation. | |
+ * @param exitResId A resource ID of the animation resource to use for | |
+ * the outgoing activity. Use 0 for no animation. | |
+ * @param handler If <var>listener</var> is non-null this must be a valid | |
+ * Handler on which to dispatch the callback; otherwise it should be null. | |
+ * @param listener Optional OnAnimationStartedListener to find out when the | |
+ * requested animation has started running. If for some reason the animation | |
+ * is not executed, the callback will happen immediately. | |
+ * @return Returns a new ActivityOptions object that you can use to | |
+ * supply these options as the options Bundle when starting an activity. | |
+ * @hide | |
+ */ | |
+ public static ActivityOptions makeCustomAnimation(Context context, | |
+ int enterResId, int exitResId, Handler handler, OnAnimationStartedListener listener) { | |
+ ActivityOptions opts = new ActivityOptions(); | |
+ opts.mPackageName = context.getPackageName(); | |
+ opts.mAnimationType = ANIM_CUSTOM; | |
+ opts.mCustomEnterResId = enterResId; | |
+ opts.mCustomExitResId = exitResId; | |
+ opts.setListener(handler, listener); | |
+ return opts; | |
+ } | |
+ | |
+ private void setListener(Handler handler, OnAnimationStartedListener listener) { | |
+ if (listener != null) { | |
+ final Handler h = handler; | |
+ final OnAnimationStartedListener finalListener = listener; | |
+ mAnimationStartedListener = new IRemoteCallback.Stub() { | |
+ @Override public void sendResult(Bundle data) throws RemoteException { | |
+ h.post(new Runnable() { | |
+ @Override public void run() { | |
+ finalListener.onAnimationStarted(); | |
+ } | |
+ }); | |
+ } | |
+ }; | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation} | |
+ * to find out when the given animation has started running. | |
+ * @hide | |
+ */ | |
+ public interface OnAnimationStartedListener { | |
+ void onAnimationStarted(); | |
+ } | |
+ | |
+ /** | |
+ * Create an ActivityOptions specifying an animation where the new | |
+ * activity is scaled from a small originating area of the screen to | |
+ * its final full representation. | |
+ * | |
+ * <p>If the Intent this is being used with has not set its | |
+ * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds}, | |
+ * those bounds will be filled in for you based on the initial | |
+ * bounds passed in here. | |
+ * | |
+ * @param source The View that the new activity is animating from. This | |
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>. | |
+ * @param startX The x starting location of the new activity, relative to <var>source</var>. | |
+ * @param startY The y starting location of the activity, relative to <var>source</var>. | |
+ * @param startWidth The initial width of the new activity. | |
+ * @param startHeight The initial height of the new activity. | |
+ * @return Returns a new ActivityOptions object that you can use to | |
+ * supply these options as the options Bundle when starting an activity. | |
+ */ | |
+ public static ActivityOptions makeScaleUpAnimation(View source, | |
+ int startX, int startY, int startWidth, int startHeight) { | |
+ ActivityOptions opts = new ActivityOptions(); | |
+ opts.mPackageName = source.getContext().getPackageName(); | |
+ opts.mAnimationType = ANIM_SCALE_UP; | |
+ int[] pts = new int[2]; | |
+ source.getLocationOnScreen(pts); | |
+ opts.mStartX = pts[0] + startX; | |
+ opts.mStartY = pts[1] + startY; | |
+ opts.mStartWidth = startWidth; | |
+ opts.mStartHeight = startHeight; | |
+ return opts; | |
+ } | |
+ | |
+ /** | |
+ * Create an ActivityOptions specifying an animation where a thumbnail | |
+ * is scaled from a given position to the new activity window that is | |
+ * being started. | |
+ * | |
+ * <p>If the Intent this is being used with has not set its | |
+ * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds}, | |
+ * those bounds will be filled in for you based on the initial | |
+ * thumbnail location and size provided here. | |
+ * | |
+ * @param source The View that this thumbnail is animating from. This | |
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>. | |
+ * @param thumbnail The bitmap that will be shown as the initial thumbnail | |
+ * of the animation. | |
+ * @param startX The x starting location of the bitmap, relative to <var>source</var>. | |
+ * @param startY The y starting location of the bitmap, relative to <var>source</var>. | |
+ * @return Returns a new ActivityOptions object that you can use to | |
+ * supply these options as the options Bundle when starting an activity. | |
+ */ | |
+ public static ActivityOptions makeThumbnailScaleUpAnimation(View source, | |
+ Bitmap thumbnail, int startX, int startY) { | |
+ return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, null); | |
+ } | |
+ | |
+ /** | |
+ * Create an ActivityOptions specifying an animation where a thumbnail | |
+ * is scaled from a given position to the new activity window that is | |
+ * being started. | |
+ * | |
+ * @param source The View that this thumbnail is animating from. This | |
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>. | |
+ * @param thumbnail The bitmap that will be shown as the initial thumbnail | |
+ * of the animation. | |
+ * @param startX The x starting location of the bitmap, relative to <var>source</var>. | |
+ * @param startY The y starting location of the bitmap, relative to <var>source</var>. | |
+ * @param listener Optional OnAnimationStartedListener to find out when the | |
+ * requested animation has started running. If for some reason the animation | |
+ * is not executed, the callback will happen immediately. | |
+ * @return Returns a new ActivityOptions object that you can use to | |
+ * supply these options as the options Bundle when starting an activity. | |
+ * @hide | |
+ */ | |
+ public static ActivityOptions makeThumbnailScaleUpAnimation(View source, | |
+ Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) { | |
+ return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, listener, false); | |
+ } | |
+ | |
+ /** | |
+ * Create an ActivityOptions specifying an animation where a thumbnail | |
+ * is scaled from a given position to the new activity window that is | |
+ * being started. Before the animation, there is a short delay. | |
+ * | |
+ * @param source The View that this thumbnail is animating from. This | |
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>. | |
+ * @param thumbnail The bitmap that will be shown as the initial thumbnail | |
+ * of the animation. | |
+ * @param startX The x starting location of the bitmap, relative to <var>source</var>. | |
+ * @param startY The y starting location of the bitmap, relative to <var>source</var>. | |
+ * @param listener Optional OnAnimationStartedListener to find out when the | |
+ * requested animation has started running. If for some reason the animation | |
+ * is not executed, the callback will happen immediately. | |
+ * @return Returns a new ActivityOptions object that you can use to | |
+ * supply these options as the options Bundle when starting an activity. | |
+ * @hide | |
+ */ | |
+ public static ActivityOptions makeDelayedThumbnailScaleUpAnimation(View source, | |
+ Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) { | |
+ return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, listener, true); | |
+ } | |
+ | |
+ private static ActivityOptions makeThumbnailScaleUpAnimation(View source, | |
+ Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener, | |
+ boolean delayed) { | |
+ ActivityOptions opts = new ActivityOptions(); | |
+ opts.mPackageName = source.getContext().getPackageName(); | |
+ opts.mAnimationType = delayed ? ANIM_THUMBNAIL_DELAYED : ANIM_THUMBNAIL; | |
+ opts.mThumbnail = thumbnail; | |
+ int[] pts = new int[2]; | |
+ source.getLocationOnScreen(pts); | |
+ opts.mStartX = pts[0] + startX; | |
+ opts.mStartY = pts[1] + startY; | |
+ opts.setListener(source.getHandler(), listener); | |
+ return opts; | |
+ } | |
+ | |
+ private ActivityOptions() { | |
+ } | |
+ | |
+ /** @hide */ | |
+ public ActivityOptions(Bundle opts) { | |
+ mPackageName = opts.getString(KEY_PACKAGE_NAME); | |
+ mAnimationType = opts.getInt(KEY_ANIM_TYPE); | |
+ if (mAnimationType == ANIM_CUSTOM) { | |
+ mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0); | |
+ mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0); | |
+ mAnimationStartedListener = IRemoteCallback.Stub.asInterface( | |
+ opts.getIBinder(KEY_ANIM_START_LISTENER)); | |
+ } else if (mAnimationType == ANIM_SCALE_UP) { | |
+ mStartX = opts.getInt(KEY_ANIM_START_X, 0); | |
+ mStartY = opts.getInt(KEY_ANIM_START_Y, 0); | |
+ mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0); | |
+ mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0); | |
+ } else if (mAnimationType == ANIM_THUMBNAIL || | |
+ mAnimationType == ANIM_THUMBNAIL_DELAYED) { | |
+ mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL); | |
+ mStartX = opts.getInt(KEY_ANIM_START_X, 0); | |
+ mStartY = opts.getInt(KEY_ANIM_START_Y, 0); | |
+ mAnimationStartedListener = IRemoteCallback.Stub.asInterface( | |
+ opts.getIBinder(KEY_ANIM_START_LISTENER)); | |
+ } | |
+ } | |
+ | |
+ /** @hide */ | |
+ public String getPackageName() { | |
+ return mPackageName; | |
+ } | |
+ | |
+ /** @hide */ | |
+ public int getAnimationType() { | |
+ return mAnimationType; | |
+ } | |
+ | |
+ /** @hide */ | |
+ public int getCustomEnterResId() { | |
+ return mCustomEnterResId; | |
+ } | |
+ | |
+ /** @hide */ | |
+ public int getCustomExitResId() { | |
+ return mCustomExitResId; | |
+ } | |
+ | |
+ /** @hide */ | |
+ public Bitmap getThumbnail() { | |
+ return mThumbnail; | |
+ } | |
+ | |
+ /** @hide */ | |
+ public int getStartX() { | |
+ return mStartX; | |
+ } | |
+ | |
+ /** @hide */ | |
+ public int getStartY() { | |
+ return mStartY; | |
+ } | |
+ | |
+ /** @hide */ | |
+ public int getStartWidth() { | |
+ return mStartWidth; | |
+ } | |
+ | |
+ /** @hide */ | |
+ public int getStartHeight() { | |
+ return mStartHeight; | |
+ } | |
+ | |
+ /** @hide */ | |
+ public IRemoteCallback getOnAnimationStartListener() { | |
+ return mAnimationStartedListener; | |
+ } | |
+ | |
+ /** @hide */ | |
+ public void abort() { | |
+ if (mAnimationStartedListener != null) { | |
+ try { | |
+ mAnimationStartedListener.sendResult(null); | |
+ } catch (RemoteException e) { | |
+ } | |
+ } | |
+ } | |
+ | |
+ /** @hide */ | |
+ public static void abort(Bundle options) { | |
+ if (options != null) { | |
+ (new ActivityOptions(options)).abort(); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Update the current values in this ActivityOptions from those supplied | |
+ * in <var>otherOptions</var>. Any values | |
+ * defined in <var>otherOptions</var> replace those in the base options. | |
+ */ | |
+ public void update(ActivityOptions otherOptions) { | |
+ if (otherOptions.mPackageName != null) { | |
+ mPackageName = otherOptions.mPackageName; | |
+ } | |
+ switch (otherOptions.mAnimationType) { | |
+ case ANIM_CUSTOM: | |
+ mAnimationType = otherOptions.mAnimationType; | |
+ mCustomEnterResId = otherOptions.mCustomEnterResId; | |
+ mCustomExitResId = otherOptions.mCustomExitResId; | |
+ mThumbnail = null; | |
+ if (otherOptions.mAnimationStartedListener != null) { | |
+ try { | |
+ otherOptions.mAnimationStartedListener.sendResult(null); | |
+ } catch (RemoteException e) { | |
+ } | |
+ } | |
+ mAnimationStartedListener = otherOptions.mAnimationStartedListener; | |
+ break; | |
+ case ANIM_SCALE_UP: | |
+ mAnimationType = otherOptions.mAnimationType; | |
+ mStartX = otherOptions.mStartX; | |
+ mStartY = otherOptions.mStartY; | |
+ mStartWidth = otherOptions.mStartWidth; | |
+ mStartHeight = otherOptions.mStartHeight; | |
+ if (otherOptions.mAnimationStartedListener != null) { | |
+ try { | |
+ otherOptions.mAnimationStartedListener.sendResult(null); | |
+ } catch (RemoteException e) { | |
+ } | |
+ } | |
+ mAnimationStartedListener = null; | |
+ break; | |
+ case ANIM_THUMBNAIL: | |
+ case ANIM_THUMBNAIL_DELAYED: | |
+ mAnimationType = otherOptions.mAnimationType; | |
+ mThumbnail = otherOptions.mThumbnail; | |
+ mStartX = otherOptions.mStartX; | |
+ mStartY = otherOptions.mStartY; | |
+ if (otherOptions.mAnimationStartedListener != null) { | |
+ try { | |
+ otherOptions.mAnimationStartedListener.sendResult(null); | |
+ } catch (RemoteException e) { | |
+ } | |
+ } | |
+ mAnimationStartedListener = otherOptions.mAnimationStartedListener; | |
+ break; | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Returns the created options as a Bundle, which can be passed to | |
+ * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle) | |
+ * Context.startActivity(Intent, Bundle)} and related methods. | |
+ * Note that the returned Bundle is still owned by the ActivityOptions | |
+ * object; you must not modify it, but can supply it to the startActivity | |
+ * methods that take an options Bundle. | |
+ */ | |
+ public Bundle toBundle() { | |
+ Bundle b = new Bundle(); | |
+ if (mPackageName != null) { | |
+ b.putString(KEY_PACKAGE_NAME, mPackageName); | |
+ } | |
+ switch (mAnimationType) { | |
+ case ANIM_CUSTOM: | |
+ b.putInt(KEY_ANIM_TYPE, mAnimationType); | |
+ b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId); | |
+ b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId); | |
+ b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener | |
+ != null ? mAnimationStartedListener.asBinder() : null); | |
+ break; | |
+ case ANIM_SCALE_UP: | |
+ b.putInt(KEY_ANIM_TYPE, mAnimationType); | |
+ b.putInt(KEY_ANIM_START_X, mStartX); | |
+ b.putInt(KEY_ANIM_START_Y, mStartY); | |
+ b.putInt(KEY_ANIM_START_WIDTH, mStartWidth); | |
+ b.putInt(KEY_ANIM_START_HEIGHT, mStartHeight); | |
+ break; | |
+ case ANIM_THUMBNAIL: | |
+ case ANIM_THUMBNAIL_DELAYED: | |
+ b.putInt(KEY_ANIM_TYPE, mAnimationType); | |
+ b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail); | |
+ b.putInt(KEY_ANIM_START_X, mStartX); | |
+ b.putInt(KEY_ANIM_START_Y, mStartY); | |
+ b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener | |
+ != null ? mAnimationStartedListener.asBinder() : null); | |
+ break; | |
+ } | |
+ return b; | |
+ } | |
+} | |
diff -Nur android-15/android/app/ActivityThread.java android-16/android/app/ActivityThread.java | |
--- android-15/android/app/ActivityThread.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/ActivityThread.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -47,6 +47,7 @@ | |
import android.net.ProxyProperties; | |
import android.opengl.GLUtils; | |
import android.os.AsyncTask; | |
+import android.os.Binder; | |
import android.os.Bundle; | |
import android.os.Debug; | |
import android.os.Handler; | |
@@ -60,11 +61,14 @@ | |
import android.os.ServiceManager; | |
import android.os.StrictMode; | |
import android.os.SystemClock; | |
+import android.os.Trace; | |
+import android.os.UserId; | |
import android.util.AndroidRuntimeException; | |
import android.util.DisplayMetrics; | |
import android.util.EventLog; | |
import android.util.Log; | |
import android.util.LogPrinter; | |
+import android.util.PrintWriterPrinter; | |
import android.util.Slog; | |
import android.view.Display; | |
import android.view.HardwareRenderer; | |
@@ -75,6 +79,7 @@ | |
import android.view.Window; | |
import android.view.WindowManager; | |
import android.view.WindowManagerImpl; | |
+import android.renderscript.RenderScript; | |
import com.android.internal.os.BinderInternal; | |
import com.android.internal.os.RuntimeInit; | |
@@ -98,6 +103,8 @@ | |
import java.util.TimeZone; | |
import java.util.regex.Pattern; | |
+import libcore.io.IoUtils; | |
+ | |
import dalvik.system.CloseGuard; | |
final class SuperNotCalledException extends AndroidRuntimeException { | |
@@ -129,8 +136,11 @@ | |
/** @hide */ | |
public static final boolean DEBUG_BROADCAST = false; | |
private static final boolean DEBUG_RESULTS = false; | |
- private static final boolean DEBUG_BACKUP = true; | |
+ private static final boolean DEBUG_BACKUP = false; | |
private static final boolean DEBUG_CONFIGURATION = false; | |
+ private static final boolean DEBUG_SERVICE = false; | |
+ private static final boolean DEBUG_MEMORY_TRIM = false; | |
+ private static final boolean DEBUG_PROVIDER = false; | |
private static final long MIN_TIME_BETWEEN_GCS = 5*1000; | |
private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";"); | |
private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003; | |
@@ -167,8 +177,10 @@ | |
static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal<ActivityThread>(); | |
Instrumentation mInstrumentation; | |
String mInstrumentationAppDir = null; | |
+ String mInstrumentationAppLibraryDir = null; | |
String mInstrumentationAppPackage = null; | |
String mInstrumentedAppDir = null; | |
+ String mInstrumentedAppLibraryDir = null; | |
boolean mSystemThread = false; | |
boolean mJitEnabled = false; | |
@@ -199,6 +211,8 @@ | |
= new HashMap<IBinder, ProviderRefCount>(); | |
final HashMap<IBinder, ProviderClientRecord> mLocalProviders | |
= new HashMap<IBinder, ProviderClientRecord>(); | |
+ final HashMap<ComponentName, ProviderClientRecord> mLocalProvidersByName | |
+ = new HashMap<ComponentName, ProviderClientRecord>(); | |
final HashMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners | |
= new HashMap<Activity, ArrayList<OnActivityPausedListener>>(); | |
@@ -264,7 +278,7 @@ | |
} | |
public String toString() { | |
- ComponentName componentName = intent.getComponent(); | |
+ ComponentName componentName = intent != null ? intent.getComponent() : null; | |
return "ActivityRecord{" | |
+ Integer.toHexString(System.identityHashCode(this)) | |
+ " token=" + token + " " + (componentName == null | |
@@ -273,20 +287,19 @@ | |
} | |
} | |
- final class ProviderClientRecord implements IBinder.DeathRecipient { | |
- final String mName; | |
+ final class ProviderClientRecord { | |
+ final String[] mNames; | |
final IContentProvider mProvider; | |
final ContentProvider mLocalProvider; | |
+ final IActivityManager.ContentProviderHolder mHolder; | |
- ProviderClientRecord(String name, IContentProvider provider, | |
- ContentProvider localProvider) { | |
- mName = name; | |
+ ProviderClientRecord(String[] names, IContentProvider provider, | |
+ ContentProvider localProvider, | |
+ IActivityManager.ContentProviderHolder holder) { | |
+ mNames = names; | |
mProvider = provider; | |
mLocalProvider = localProvider; | |
- } | |
- | |
- public void binderDied() { | |
- removeDeadProvider(mName, mProvider); | |
+ mHolder = holder; | |
} | |
} | |
@@ -369,6 +382,7 @@ | |
Bundle instrumentationArgs; | |
IInstrumentationWatcher instrumentationWatcher; | |
int debugMode; | |
+ boolean enableOpenGlTrace; | |
boolean restrictedBackupMode; | |
boolean persistent; | |
Configuration config; | |
@@ -396,6 +410,7 @@ | |
try { | |
fd.close(); | |
} catch (IOException e) { | |
+ // Ignore | |
} | |
} | |
return; | |
@@ -404,6 +419,7 @@ | |
try { | |
profileFd.close(); | |
} catch (IOException e) { | |
+ // Ignore | |
} | |
} | |
profileFile = file; | |
@@ -485,7 +501,6 @@ | |
private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s"; | |
private static final String ONE_COUNT_COLUMN = "%21s %8d"; | |
private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d"; | |
- private static final String TWO_COUNT_COLUMNS_DB = "%21s %8d %21s %8d"; | |
private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s"; | |
// Formatting for checkin service - update version if row format changes | |
@@ -635,6 +650,9 @@ | |
s.intent = intent; | |
s.rebind = rebind; | |
+ if (DEBUG_SERVICE) | |
+ Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid=" | |
+ + Binder.getCallingUid() + " pid=" + Binder.getCallingPid()); | |
queueOrSendMessage(H.BIND_SERVICE, s); | |
} | |
@@ -667,8 +685,8 @@ | |
ComponentName instrumentationName, String profileFile, | |
ParcelFileDescriptor profileFd, boolean autoStopProfiler, | |
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, | |
- int debugMode, boolean isRestrictedBackupMode, boolean persistent, | |
- Configuration config, CompatibilityInfo compatInfo, | |
+ int debugMode, boolean enableOpenGlTrace, boolean isRestrictedBackupMode, | |
+ boolean persistent, Configuration config, CompatibilityInfo compatInfo, | |
Map<String, IBinder> services, Bundle coreSettings) { | |
if (services != null) { | |
@@ -686,6 +704,7 @@ | |
data.instrumentationArgs = instrumentationArgs; | |
data.instrumentationWatcher = instrumentationWatcher; | |
data.debugMode = debugMode; | |
+ data.enableOpenGlTrace = enableOpenGlTrace; | |
data.restrictedBackupMode = isRestrictedBackupMode; | |
data.persistent = persistent; | |
data.config = config; | |
@@ -813,20 +832,32 @@ | |
} | |
} | |
+ public void dumpProvider(FileDescriptor fd, IBinder providertoken, | |
+ String[] args) { | |
+ DumpComponentInfo data = new DumpComponentInfo(); | |
+ try { | |
+ data.fd = ParcelFileDescriptor.dup(fd); | |
+ data.token = providertoken; | |
+ data.args = args; | |
+ queueOrSendMessage(H.DUMP_PROVIDER, data); | |
+ } catch (IOException e) { | |
+ Slog.w(TAG, "dumpProvider failed", e); | |
+ } | |
+ } | |
+ | |
@Override | |
public Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin, | |
boolean all, String[] args) { | |
FileOutputStream fout = new FileOutputStream(fd); | |
PrintWriter pw = new PrintWriter(fout); | |
try { | |
- return dumpMemInfo(pw, checkin, all, args); | |
+ return dumpMemInfo(pw, checkin, all); | |
} finally { | |
pw.flush(); | |
} | |
} | |
- private Debug.MemoryInfo dumpMemInfo(PrintWriter pw, boolean checkin, boolean all, | |
- String[] args) { | |
+ private Debug.MemoryInfo dumpMemInfo(PrintWriter pw, boolean checkin, boolean all) { | |
long nativeMax = Debug.getNativeHeapSize() / 1024; | |
long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; | |
long nativeFree = Debug.getNativeHeapFreeSize() / 1024; | |
@@ -853,7 +884,6 @@ | |
int binderProxyObjectCount = Debug.getBinderProxyObjectCount(); | |
int binderDeathObjectCount = Debug.getBinderDeathObjectCount(); | |
long openSslSocketCount = Debug.countInstancesOfClass(OpenSSLSocketImpl.class); | |
- long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024; | |
SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo(); | |
// For checkin, we print one long comma-separated list of values | |
@@ -921,9 +951,9 @@ | |
pw.print(openSslSocketCount); pw.print(','); | |
// SQL | |
- pw.print(sqliteAllocated); pw.print(','); | |
pw.print(stats.memoryUsed / 1024); pw.print(','); | |
- pw.print(stats.pageCacheOverflo / 1024); pw.print(','); | |
+ pw.print(stats.memoryUsed / 1024); pw.print(','); | |
+ pw.print(stats.pageCacheOverflow / 1024); pw.print(','); | |
pw.print(stats.largestMemAlloc / 1024); | |
for (int i = 0; i < stats.dbStats.size(); i++) { | |
DbStats dbStats = stats.dbStats.get(i); | |
@@ -989,10 +1019,9 @@ | |
// SQLite mem info | |
pw.println(" "); | |
pw.println(" SQL"); | |
- printRow(pw, TWO_COUNT_COLUMNS_DB, "heap:", sqliteAllocated, "MEMORY_USED:", | |
- stats.memoryUsed / 1024); | |
- printRow(pw, TWO_COUNT_COLUMNS_DB, "PAGECACHE_OVERFLOW:", | |
- stats.pageCacheOverflo / 1024, "MALLOC_SIZE:", stats.largestMemAlloc / 1024); | |
+ printRow(pw, ONE_COUNT_COLUMN, "MEMORY_USED:", stats.memoryUsed / 1024); | |
+ printRow(pw, TWO_COUNT_COLUMNS, "PAGECACHE_OVERFLOW:", | |
+ stats.pageCacheOverflow / 1024, "MALLOC_SIZE:", stats.largestMemAlloc / 1024); | |
pw.println(" "); | |
int N = stats.dbStats.size(); | |
if (N > 0) { | |
@@ -1026,6 +1055,19 @@ | |
WindowManagerImpl.getDefault().dumpGfxInfo(fd); | |
} | |
+ @Override | |
+ public void dumpDbInfo(FileDescriptor fd, String[] args) { | |
+ PrintWriter pw = new PrintWriter(new FileOutputStream(fd)); | |
+ PrintWriterPrinter printer = new PrintWriterPrinter(pw); | |
+ SQLiteDebug.dump(printer, args); | |
+ pw.flush(); | |
+ } | |
+ | |
+ @Override | |
+ public void unstableProviderDied(IBinder provider) { | |
+ queueOrSendMessage(H.UNSTABLE_PROVIDER_DIED, provider); | |
+ } | |
+ | |
private void printRow(PrintWriter pw, String format, Object...objs) { | |
pw.println(String.format(format, objs)); | |
} | |
@@ -1044,6 +1086,7 @@ | |
public void scheduleTrimMemory(int level) { | |
queueOrSendMessage(H.TRIM_MEMORY, null, level); | |
} | |
+ | |
} | |
private class H extends Handler { | |
@@ -1088,6 +1131,8 @@ | |
public static final int SET_CORE_SETTINGS = 138; | |
public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139; | |
public static final int TRIM_MEMORY = 140; | |
+ public static final int DUMP_PROVIDER = 141; | |
+ public static final int UNSTABLE_PROVIDER_DIED = 142; | |
String codeToString(int code) { | |
if (DEBUG_MESSAGES) { | |
switch (code) { | |
@@ -1132,57 +1177,83 @@ | |
case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS"; | |
case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO"; | |
case TRIM_MEMORY: return "TRIM_MEMORY"; | |
+ case DUMP_PROVIDER: return "DUMP_PROVIDER"; | |
+ case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED"; | |
} | |
} | |
- return "(unknown)"; | |
+ return Integer.toString(code); | |
} | |
public void handleMessage(Message msg) { | |
- if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what); | |
+ if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); | |
switch (msg.what) { | |
case LAUNCH_ACTIVITY: { | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); | |
ActivityClientRecord r = (ActivityClientRecord)msg.obj; | |
r.packageInfo = getPackageInfoNoCheck( | |
r.activityInfo.applicationInfo, r.compatInfo); | |
handleLaunchActivity(r, null); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
} break; | |
case RELAUNCH_ACTIVITY: { | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); | |
ActivityClientRecord r = (ActivityClientRecord)msg.obj; | |
handleRelaunchActivity(r); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
} break; | |
case PAUSE_ACTIVITY: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); | |
handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2); | |
maybeSnapshot(); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case PAUSE_ACTIVITY_FINISHING: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); | |
handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case STOP_ACTIVITY_SHOW: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); | |
handleStopActivity((IBinder)msg.obj, true, msg.arg2); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case STOP_ACTIVITY_HIDE: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); | |
handleStopActivity((IBinder)msg.obj, false, msg.arg2); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case SHOW_WINDOW: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow"); | |
handleWindowVisibility((IBinder)msg.obj, true); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case HIDE_WINDOW: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow"); | |
handleWindowVisibility((IBinder)msg.obj, false); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case RESUME_ACTIVITY: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); | |
handleResumeActivity((IBinder)msg.obj, true, | |
msg.arg1 != 0); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case SEND_RESULT: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult"); | |
handleSendResult((ResultData)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case DESTROY_ACTIVITY: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy"); | |
handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0, | |
msg.arg2, false); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case BIND_APPLICATION: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication"); | |
AppBindData data = (AppBindData)msg.obj; | |
handleBindApplication(data); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case EXIT_APPLICATION: | |
if (mInitialApplication != null) { | |
@@ -1191,33 +1262,51 @@ | |
Looper.myLooper().quit(); | |
break; | |
case NEW_INTENT: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent"); | |
handleNewIntent((NewIntentData)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case RECEIVER: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp"); | |
handleReceiver((ReceiverData)msg.obj); | |
maybeSnapshot(); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case CREATE_SERVICE: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceCreate"); | |
handleCreateService((CreateServiceData)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case BIND_SERVICE: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind"); | |
handleBindService((BindServiceData)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case UNBIND_SERVICE: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind"); | |
handleUnbindService((BindServiceData)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case SERVICE_ARGS: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStart"); | |
handleServiceArgs((ServiceArgsData)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case STOP_SERVICE: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop"); | |
handleStopService((IBinder)msg.obj); | |
maybeSnapshot(); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case REQUEST_THUMBNAIL: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestThumbnail"); | |
handleRequestThumbnail((IBinder)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case CONFIGURATION_CHANGED: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); | |
handleConfigurationChanged((Configuration)msg.obj, null); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case CLEAN_UP_CONTEXT: | |
ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj; | |
@@ -1230,31 +1319,43 @@ | |
handleDumpService((DumpComponentInfo)msg.obj); | |
break; | |
case LOW_MEMORY: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "lowMemory"); | |
handleLowMemory(); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case ACTIVITY_CONFIGURATION_CHANGED: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged"); | |
handleActivityConfigurationChanged((IBinder)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case PROFILER_CONTROL: | |
handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj, msg.arg2); | |
break; | |
case CREATE_BACKUP_AGENT: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupCreateAgent"); | |
handleCreateBackupAgent((CreateBackupAgentData)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case DESTROY_BACKUP_AGENT: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupDestroyAgent"); | |
handleDestroyBackupAgent((CreateBackupAgentData)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case SUICIDE: | |
Process.killProcess(Process.myPid()); | |
break; | |
case REMOVE_PROVIDER: | |
- completeRemoveProvider((IContentProvider)msg.obj); | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerRemove"); | |
+ completeRemoveProvider((ProviderRefCount)msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case ENABLE_JIT: | |
ensureJitEnabled(); | |
break; | |
case DISPATCH_PACKAGE_BROADCAST: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastPackage"); | |
handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case SCHEDULE_CRASH: | |
throw new RemoteServiceException((String)msg.obj); | |
@@ -1264,20 +1365,32 @@ | |
case DUMP_ACTIVITY: | |
handleDumpActivity((DumpComponentInfo)msg.obj); | |
break; | |
+ case DUMP_PROVIDER: | |
+ handleDumpProvider((DumpComponentInfo)msg.obj); | |
+ break; | |
case SLEEPING: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "sleeping"); | |
handleSleeping((IBinder)msg.obj, msg.arg1 != 0); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case SET_CORE_SETTINGS: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setCoreSettings"); | |
handleSetCoreSettings((Bundle) msg.obj); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
break; | |
case UPDATE_PACKAGE_COMPATIBILITY_INFO: | |
handleUpdatePackageCompatibilityInfo((UpdateCompatibilityData)msg.obj); | |
break; | |
case TRIM_MEMORY: | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory"); | |
handleTrimMemory(msg.arg1); | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
+ break; | |
+ case UNSTABLE_PROVIDER_DIED: | |
+ handleUnstableProviderDied((IBinder)msg.obj, false); | |
break; | |
} | |
- if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what); | |
+ if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what)); | |
} | |
private void maybeSnapshot() { | |
@@ -1422,17 +1535,6 @@ | |
return dm; | |
} | |
- static Configuration applyConfigCompat(Configuration config, CompatibilityInfo compat) { | |
- if (config == null) { | |
- return null; | |
- } | |
- if (compat != null && !compat.supportsScreen()) { | |
- config = new Configuration(config); | |
- compat.applyToConfiguration(config); | |
- } | |
- return config; | |
- } | |
- | |
private Configuration mMainThreadConfig = new Configuration(); | |
Configuration applyConfigCompatMainThread(Configuration config, CompatibilityInfo compat) { | |
if (config == null) { | |
@@ -1550,7 +1652,7 @@ | |
ApplicationInfo ai = null; | |
try { | |
ai = getPackageManager().getApplicationInfo(packageName, | |
- PackageManager.GET_SHARED_LIBRARY_FILES); | |
+ PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId()); | |
} catch (RemoteException e) { | |
// Ignore | |
} | |
@@ -1567,7 +1669,8 @@ | |
boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; | |
boolean securityViolation = includeCode && ai.uid != 0 | |
&& ai.uid != Process.SYSTEM_UID && (mBoundApplication != null | |
- ? ai.uid != mBoundApplication.appInfo.uid : true); | |
+ ? !UserId.isSameApp(ai.uid, mBoundApplication.appInfo.uid) | |
+ : true); | |
if ((flags&(Context.CONTEXT_INCLUDE_CODE | |
|Context.CONTEXT_IGNORE_SECURITY)) | |
== Context.CONTEXT_INCLUDE_CODE) { | |
@@ -1765,7 +1868,7 @@ | |
if (aInfo == null) { | |
// Throw an exception. | |
Instrumentation.checkStartActivityResult( | |
- IActivityManager.START_CLASS_NOT_FOUND, intent); | |
+ ActivityManager.START_CLASS_NOT_FOUND, intent); | |
} | |
return aInfo; | |
} | |
@@ -1999,8 +2102,15 @@ | |
r.activity.mCalled = false; | |
mInstrumentation.callActivityOnPause(r.activity); | |
// We need to keep around the original state, in case | |
- // we need to be created again. | |
- r.state = oldState; | |
+ // we need to be created again. But we only do this | |
+ // for pre-Honeycomb apps, which always save their state | |
+ // when pausing, so we can not have them save their state | |
+ // when restarting from a paused state. For HC and later, | |
+ // we want to (and can) let the state be saved as the normal | |
+ // part of stopping the activity. | |
+ if (r.isPreHoneycomb()) { | |
+ r.state = oldState; | |
+ } | |
if (!r.activity.mCalled) { | |
throw new SuperNotCalledException( | |
"Activity " + r.intent.getComponent().toShortString() + | |
@@ -2269,6 +2379,8 @@ | |
private void handleBindService(BindServiceData data) { | |
Service s = mServices.get(data.token); | |
+ if (DEBUG_SERVICE) | |
+ Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind); | |
if (s != null) { | |
try { | |
data.intent.setExtrasClassLoader(s.getClassLoader()); | |
@@ -2322,28 +2434,47 @@ | |
} | |
private void handleDumpService(DumpComponentInfo info) { | |
- Service s = mServices.get(info.token); | |
- if (s != null) { | |
- PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); | |
- s.dump(info.fd.getFileDescriptor(), pw, info.args); | |
- pw.flush(); | |
- try { | |
- info.fd.close(); | |
- } catch (IOException e) { | |
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); | |
+ try { | |
+ Service s = mServices.get(info.token); | |
+ if (s != null) { | |
+ PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); | |
+ s.dump(info.fd.getFileDescriptor(), pw, info.args); | |
+ pw.flush(); | |
} | |
+ } finally { | |
+ IoUtils.closeQuietly(info.fd); | |
+ StrictMode.setThreadPolicy(oldPolicy); | |
} | |
} | |
private void handleDumpActivity(DumpComponentInfo info) { | |
- ActivityClientRecord r = mActivities.get(info.token); | |
- if (r != null && r.activity != null) { | |
- PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); | |
- r.activity.dump(info.prefix, info.fd.getFileDescriptor(), pw, info.args); | |
- pw.flush(); | |
- try { | |
- info.fd.close(); | |
- } catch (IOException e) { | |
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); | |
+ try { | |
+ ActivityClientRecord r = mActivities.get(info.token); | |
+ if (r != null && r.activity != null) { | |
+ PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); | |
+ r.activity.dump(info.prefix, info.fd.getFileDescriptor(), pw, info.args); | |
+ pw.flush(); | |
+ } | |
+ } finally { | |
+ IoUtils.closeQuietly(info.fd); | |
+ StrictMode.setThreadPolicy(oldPolicy); | |
+ } | |
+ } | |
+ | |
+ private void handleDumpProvider(DumpComponentInfo info) { | |
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); | |
+ try { | |
+ ProviderClientRecord r = mLocalProviders.get(info.token); | |
+ if (r != null && r.mLocalProvider != null) { | |
+ PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd.getFileDescriptor())); | |
+ r.mLocalProvider.dump(info.fd.getFileDescriptor(), pw, info.args); | |
+ pw.flush(); | |
} | |
+ } finally { | |
+ IoUtils.closeQuietly(info.fd); | |
+ StrictMode.setThreadPolicy(oldPolicy); | |
} | |
} | |
@@ -2451,7 +2582,7 @@ | |
return r; | |
} | |
- final void cleanUpPendingRemoveWindows(ActivityClientRecord r) { | |
+ static final void cleanUpPendingRemoveWindows(ActivityClientRecord r) { | |
if (r.mPendingRemoveWindow != null) { | |
r.mPendingRemoveWindowManager.removeViewImmediate(r.mPendingRemoveWindow); | |
IBinder wtoken = r.mPendingRemoveWindow.getWindowToken(); | |
@@ -2527,6 +2658,7 @@ | |
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity " | |
+ r.activityInfo.name + " with newConfig " + r.newConfig); | |
performConfigurationChanged(r.activity, r.newConfig); | |
+ freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig)); | |
r.newConfig = null; | |
} | |
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" | |
@@ -2729,16 +2861,42 @@ | |
performStopActivityInner(r, null, false, saveState); | |
} | |
- private static class StopInfo { | |
+ private static class StopInfo implements Runnable { | |
+ ActivityClientRecord activity; | |
+ Bundle state; | |
Bitmap thumbnail; | |
CharSequence description; | |
+ | |
+ @Override public void run() { | |
+ // Tell activity manager we have been stopped. | |
+ try { | |
+ if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + activity); | |
+ ActivityManagerNative.getDefault().activityStopped( | |
+ activity.token, state, thumbnail, description); | |
+ } catch (RemoteException ex) { | |
+ } | |
+ } | |
} | |
private static final class ProviderRefCount { | |
- public int count; | |
- | |
- ProviderRefCount(int pCount) { | |
- count = pCount; | |
+ public final IActivityManager.ContentProviderHolder holder; | |
+ public final ProviderClientRecord client; | |
+ public int stableCount; | |
+ public int unstableCount; | |
+ | |
+ // When this is set, the stable and unstable ref counts are 0 and | |
+ // we have a pending operation scheduled to remove the ref count | |
+ // from the activity manager. On the activity manager we are still | |
+ // holding an unstable ref, though it is not reflected in the counts | |
+ // here. | |
+ public boolean removePending; | |
+ | |
+ ProviderRefCount(IActivityManager.ContentProviderHolder inHolder, | |
+ ProviderClientRecord inClient, int sCount, int uCount) { | |
+ holder = inHolder; | |
+ client = inClient; | |
+ stableCount = sCount; | |
+ unstableCount = uCount; | |
} | |
} | |
@@ -2831,6 +2989,7 @@ | |
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis " | |
+ r.activityInfo.name + " with new config " + r.newConfig); | |
performConfigurationChanged(r.activity, r.newConfig); | |
+ freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig)); | |
r.newConfig = null; | |
} | |
} else { | |
@@ -2861,12 +3020,14 @@ | |
QueuedWork.waitToFinish(); | |
} | |
- // Tell activity manager we have been stopped. | |
- try { | |
- ActivityManagerNative.getDefault().activityStopped( | |
- r.token, r.state, info.thumbnail, info.description); | |
- } catch (RemoteException ex) { | |
- } | |
+ // Schedule the call to tell the activity manager we have | |
+ // stopped. We don't do this immediately, because we want to | |
+ // have a chance for any other pending work (in particular memory | |
+ // trim requests) to complete before you tell the activity | |
+ // manager to proceed and allow us to go fully into the background. | |
+ info.activity = r; | |
+ info.state = r.state; | |
+ mH.post(info); | |
} | |
final void performRestartActivity(IBinder token) { | |
@@ -3379,15 +3540,12 @@ | |
= new ArrayList<ComponentCallbacks2>(); | |
if (mActivities.size() > 0) { | |
- Iterator<ActivityClientRecord> it = mActivities.values().iterator(); | |
- while (it.hasNext()) { | |
- ActivityClientRecord ar = it.next(); | |
+ for (ActivityClientRecord ar : mActivities.values()) { | |
Activity a = ar.activity; | |
if (a != null) { | |
Configuration thisConfig = applyConfigCompatMainThread(newConfig, | |
ar.packageInfo.mCompatibilityInfo.getIfNeeded()); | |
- if (!ar.activity.mFinished && (allActivities || | |
- (a != null && !ar.paused))) { | |
+ if (!ar.activity.mFinished && (allActivities || !ar.paused)) { | |
// If the activity is currently resumed, its configuration | |
// needs to change right now. | |
callbacks.add(a); | |
@@ -3397,24 +3555,24 @@ | |
// the activity manager may, before then, decide the | |
// activity needs to be destroyed to handle its new | |
// configuration. | |
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Setting activity " | |
- + ar.activityInfo.name + " newConfig=" + thisConfig); | |
+ if (DEBUG_CONFIGURATION) { | |
+ Slog.v(TAG, "Setting activity " | |
+ + ar.activityInfo.name + " newConfig=" + thisConfig); | |
+ } | |
ar.newConfig = thisConfig; | |
} | |
} | |
} | |
} | |
if (mServices.size() > 0) { | |
- Iterator<Service> it = mServices.values().iterator(); | |
- while (it.hasNext()) { | |
- callbacks.add(it.next()); | |
+ for (Service service : mServices.values()) { | |
+ callbacks.add(service); | |
} | |
} | |
synchronized (mProviderMap) { | |
if (mLocalProviders.size() > 0) { | |
- Iterator<ProviderClientRecord> it = mLocalProviders.values().iterator(); | |
- while (it.hasNext()) { | |
- callbacks.add(it.next().mLocalProvider); | |
+ for (ProviderClientRecord providerClientRecord : mLocalProviders.values()) { | |
+ callbacks.add(providerClientRecord.mLocalProvider); | |
} | |
} | |
} | |
@@ -3426,8 +3584,7 @@ | |
return callbacks; | |
} | |
- private final void performConfigurationChanged( | |
- ComponentCallbacks2 cb, Configuration config) { | |
+ private static void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) { | |
// Only for Activity objects, check that they actually call up to their | |
// superclass implementation. ComponentCallbacks2 is an interface, so | |
// we check the runtime type and act accordingly. | |
@@ -3547,6 +3704,7 @@ | |
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { | |
ArrayList<ComponentCallbacks2> callbacks = null; | |
+ int configDiff = 0; | |
synchronized (mPackages) { | |
if (mPendingConfiguration != null) { | |
@@ -3571,6 +3729,7 @@ | |
if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { | |
return; | |
} | |
+ configDiff = mConfiguration.diff(config); | |
mConfiguration.updateFrom(config); | |
config = applyCompatConfiguration(); | |
callbacks = collectComponentCallbacksLocked(false, config); | |
@@ -3579,6 +3738,8 @@ | |
// Cleanup hardware accelerated stuff | |
WindowManagerImpl.getDefault().trimLocalMemory(); | |
+ freeTextLayoutCachesIfNeeded(configDiff); | |
+ | |
if (callbacks != null) { | |
final int N = callbacks.size(); | |
for (int i=0; i<N; i++) { | |
@@ -3587,6 +3748,17 @@ | |
} | |
} | |
+ final void freeTextLayoutCachesIfNeeded(int configDiff) { | |
+ if (configDiff != 0) { | |
+ // Ask text layout engine to free its caches if there is a locale change | |
+ boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0); | |
+ if (hasLocaleConfigChange) { | |
+ Canvas.freeTextLayoutCaches(); | |
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Cleared TextLayout Caches"); | |
+ } | |
+ } | |
+ } | |
+ | |
final void handleActivityConfigurationChanged(IBinder token) { | |
ActivityClientRecord r = mActivities.get(token); | |
if (r == null || r.activity == null) { | |
@@ -3597,15 +3769,14 @@ | |
+ r.activityInfo.name); | |
performConfigurationChanged(r.activity, mCompatConfiguration); | |
+ | |
+ freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration)); | |
} | |
final void handleProfilerControl(boolean start, ProfilerControlData pcd, int profileType) { | |
if (start) { | |
try { | |
switch (profileType) { | |
- case 1: | |
- ViewDebug.startLooperProfiling(pcd.path, pcd.fd.getFileDescriptor()); | |
- break; | |
default: | |
mProfiler.setProfiler(pcd.path, pcd.fd); | |
mProfiler.autoStopProfiler = false; | |
@@ -3624,9 +3795,6 @@ | |
} | |
} else { | |
switch (profileType) { | |
- case 1: | |
- ViewDebug.stopLooperProfiling(); | |
- break; | |
default: | |
mProfiler.stopProfiling(); | |
break; | |
@@ -3634,7 +3802,7 @@ | |
} | |
} | |
- final void handleDumpHeap(boolean managed, DumpHeapData dhd) { | |
+ static final void handleDumpHeap(boolean managed, DumpHeapData dhd) { | |
if (managed) { | |
try { | |
Debug.dumpHprofData(dhd.path, dhd.fd.getFileDescriptor()); | |
@@ -3699,35 +3867,45 @@ | |
// Ask graphics to free up as much as possible (font/image caches) | |
Canvas.freeCaches(); | |
+ // Ask text layout engine to free also as much as possible | |
+ Canvas.freeTextLayoutCaches(); | |
+ | |
BinderInternal.forceGc("mem"); | |
} | |
final void handleTrimMemory(int level) { | |
- WindowManagerImpl.getDefault().trimMemory(level); | |
- ArrayList<ComponentCallbacks2> callbacks; | |
+ if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level); | |
+ | |
+ final WindowManagerImpl windowManager = WindowManagerImpl.getDefault(); | |
+ windowManager.startTrimMemory(level); | |
+ ArrayList<ComponentCallbacks2> callbacks; | |
synchronized (mPackages) { | |
callbacks = collectComponentCallbacksLocked(true, null); | |
} | |
final int N = callbacks.size(); | |
- for (int i=0; i<N; i++) { | |
+ for (int i = 0; i < N; i++) { | |
callbacks.get(i).onTrimMemory(level); | |
} | |
+ | |
+ windowManager.endTrimMemory(); | |
} | |
- private void setupGraphicsSupport(LoadedApk info) { | |
+ private void setupGraphicsSupport(LoadedApk info, File cacheDir) { | |
+ if (Process.isIsolated()) { | |
+ // Isolated processes aren't going to do UI. | |
+ return; | |
+ } | |
try { | |
int uid = Process.myUid(); | |
String[] packages = getPackageManager().getPackagesForUid(uid); | |
// If there are several packages in this application we won't | |
// initialize the graphics disk caches | |
- if (packages.length == 1) { | |
- ContextImpl appContext = new ContextImpl(); | |
- appContext.init(info, null, this); | |
- | |
- HardwareRenderer.setupDiskCache(appContext.getCacheDir()); | |
+ if (packages != null && packages.length == 1) { | |
+ HardwareRenderer.setupDiskCache(cacheDir); | |
+ RenderScript.setupDiskCache(cacheDir); | |
} | |
} catch (RemoteException e) { | |
// Ignore | |
@@ -3766,7 +3944,7 @@ | |
// implementation to use the pool executor. Normally, we use the | |
// serialized executor as the default. This has to happen in the | |
// main thread so the main looper is set right. | |
- if (data.appInfo.targetSdkVersion <= 12) { | |
+ if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) { | |
AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | |
} | |
@@ -3793,8 +3971,15 @@ | |
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); | |
- setupGraphicsSupport(data.info); | |
- | |
+ final ContextImpl appContext = new ContextImpl(); | |
+ appContext.init(data.info, null, this); | |
+ final File cacheDir = appContext.getCacheDir(); | |
+ | |
+ // Provide a usable directory for temporary files | |
+ System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath()); | |
+ | |
+ setupGraphicsSupport(data.info, cacheDir); | |
+ | |
/** | |
* For system applications on userdebug/eng builds, log stack | |
* traces of disk and network access to dropbox for analysis. | |
@@ -3850,6 +4035,11 @@ | |
} | |
} | |
+ // Enable OpenGL tracing if required | |
+ if (data.enableOpenGlTrace) { | |
+ GLUtils.enableTracing(); | |
+ } | |
+ | |
/** | |
* Initialize the default http proxy in this process for the reasons we set the time zone. | |
*/ | |
@@ -3866,8 +4056,6 @@ | |
} | |
if (data.instrumentationName != null) { | |
- ContextImpl appContext = new ContextImpl(); | |
- appContext.init(data.info, null, this); | |
InstrumentationInfo ii = null; | |
try { | |
ii = appContext.getPackageManager(). | |
@@ -3881,8 +4069,10 @@ | |
} | |
mInstrumentationAppDir = ii.sourceDir; | |
+ mInstrumentationAppLibraryDir = ii.nativeLibraryDir; | |
mInstrumentationAppPackage = ii.packageName; | |
mInstrumentedAppDir = data.info.getAppDir(); | |
+ mInstrumentedAppLibraryDir = data.info.getLibDir(); | |
ApplicationInfo instrApp = new ApplicationInfo(); | |
instrApp.packageName = ii.packageName; | |
@@ -3916,15 +4106,6 @@ | |
Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024); | |
} | |
- try { | |
- mInstrumentation.onCreate(data.instrumentationArgs); | |
- } | |
- catch (Exception e) { | |
- throw new RuntimeException( | |
- "Exception thrown in onCreate() of " | |
- + data.instrumentationName + ": " + e.toString(), e); | |
- } | |
- | |
} else { | |
mInstrumentation = new Instrumentation(); | |
} | |
@@ -3933,31 +4114,50 @@ | |
dalvik.system.VMRuntime.getRuntime().clearGrowthLimit(); | |
} | |
- // If the app is being launched for full backup or restore, bring it up in | |
- // a restricted environment with the base application class. | |
- Application app = data.info.makeApplication(data.restrictedBackupMode, null); | |
- mInitialApplication = app; | |
- | |
- // don't bring up providers in restricted mode; they may depend on the | |
- // app's custom Application class | |
- if (!data.restrictedBackupMode){ | |
- List<ProviderInfo> providers = data.providers; | |
- if (providers != null) { | |
- installContentProviders(app, providers); | |
- // For process that contains content providers, we want to | |
- // ensure that the JIT is enabled "at some point". | |
- mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000); | |
+ // Allow disk access during application and provider setup. This could | |
+ // block processing ordered broadcasts, but later processing would | |
+ // probably end up doing the same disk access. | |
+ final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); | |
+ try { | |
+ // If the app is being launched for full backup or restore, bring it up in | |
+ // a restricted environment with the base application class. | |
+ Application app = data.info.makeApplication(data.restrictedBackupMode, null); | |
+ mInitialApplication = app; | |
+ | |
+ // don't bring up providers in restricted mode; they may depend on the | |
+ // app's custom Application class | |
+ if (!data.restrictedBackupMode) { | |
+ List<ProviderInfo> providers = data.providers; | |
+ if (providers != null) { | |
+ installContentProviders(app, providers); | |
+ // For process that contains content providers, we want to | |
+ // ensure that the JIT is enabled "at some point". | |
+ mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000); | |
+ } | |
} | |
- } | |
- try { | |
- mInstrumentation.callApplicationOnCreate(app); | |
- } catch (Exception e) { | |
- if (!mInstrumentation.onException(app, e)) { | |
+ // Do this after providers, since instrumentation tests generally start their | |
+ // test thread at this point, and we don't want that racing. | |
+ try { | |
+ mInstrumentation.onCreate(data.instrumentationArgs); | |
+ } | |
+ catch (Exception e) { | |
throw new RuntimeException( | |
- "Unable to create application " + app.getClass().getName() | |
- + ": " + e.toString(), e); | |
+ "Exception thrown in onCreate() of " | |
+ + data.instrumentationName + ": " + e.toString(), e); | |
+ } | |
+ | |
+ try { | |
+ mInstrumentation.callApplicationOnCreate(app); | |
+ } catch (Exception e) { | |
+ if (!mInstrumentation.onException(app, e)) { | |
+ throw new RuntimeException( | |
+ "Unable to create application " + app.getClass().getName() | |
+ + ": " + e.toString(), e); | |
+ } | |
} | |
+ } finally { | |
+ StrictMode.setThreadPolicy(savedPolicy); | |
} | |
} | |
@@ -3980,21 +4180,16 @@ | |
final ArrayList<IActivityManager.ContentProviderHolder> results = | |
new ArrayList<IActivityManager.ContentProviderHolder>(); | |
- Iterator<ProviderInfo> i = providers.iterator(); | |
- while (i.hasNext()) { | |
- ProviderInfo cpi = i.next(); | |
+ for (ProviderInfo cpi : providers) { | |
StringBuilder buf = new StringBuilder(128); | |
buf.append("Pub "); | |
buf.append(cpi.authority); | |
buf.append(": "); | |
buf.append(cpi.name); | |
Log.i(TAG, buf.toString()); | |
- IContentProvider cp = installProvider(context, null, cpi, | |
- false /*noisy*/, true /*noReleaseNeeded*/); | |
- if (cp != null) { | |
- IActivityManager.ContentProviderHolder cph = | |
- new IActivityManager.ContentProviderHolder(cpi); | |
- cph.provider = cp; | |
+ IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi, | |
+ false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/); | |
+ if (cph != null) { | |
cph.noReleaseNeeded = true; | |
results.add(cph); | |
} | |
@@ -4007,8 +4202,8 @@ | |
} | |
} | |
- public final IContentProvider acquireProvider(Context c, String name) { | |
- IContentProvider provider = acquireExistingProvider(c, name); | |
+ public final IContentProvider acquireProvider(Context c, String name, boolean stable) { | |
+ IContentProvider provider = acquireExistingProvider(c, name, stable); | |
if (provider != null) { | |
return provider; | |
} | |
@@ -4022,7 +4217,7 @@ | |
IActivityManager.ContentProviderHolder holder = null; | |
try { | |
holder = ActivityManagerNative.getDefault().getContentProvider( | |
- getApplicationThread(), name); | |
+ getApplicationThread(), name, stable); | |
} catch (RemoteException ex) { | |
} | |
if (holder == null) { | |
@@ -4032,23 +4227,79 @@ | |
// Install provider will increment the reference count for us, and break | |
// any ties in the race. | |
- provider = installProvider(c, holder.provider, holder.info, | |
- true /*noisy*/, holder.noReleaseNeeded); | |
- if (holder.provider != null && provider != holder.provider) { | |
- if (localLOGV) { | |
- Slog.v(TAG, "acquireProvider: lost the race, releasing extraneous " | |
- + "reference to the content provider"); | |
+ holder = installProvider(c, holder, holder.info, | |
+ true /*noisy*/, holder.noReleaseNeeded, stable); | |
+ return holder.provider; | |
+ } | |
+ | |
+ private final void incProviderRefLocked(ProviderRefCount prc, boolean stable) { | |
+ if (stable) { | |
+ prc.stableCount += 1; | |
+ if (prc.stableCount == 1) { | |
+ // We are acquiring a new stable reference on the provider. | |
+ int unstableDelta; | |
+ if (prc.removePending) { | |
+ // We have a pending remove operation, which is holding the | |
+ // last unstable reference. At this point we are converting | |
+ // that unstable reference to our new stable reference. | |
+ unstableDelta = -1; | |
+ // Cancel the removal of the provider. | |
+ if (DEBUG_PROVIDER) { | |
+ Slog.v(TAG, "incProviderRef: stable " | |
+ + "snatched provider from the jaws of death"); | |
+ } | |
+ prc.removePending = false; | |
+ mH.removeMessages(H.REMOVE_PROVIDER, prc); | |
+ } else { | |
+ unstableDelta = 0; | |
+ } | |
+ try { | |
+ if (DEBUG_PROVIDER) { | |
+ Slog.v(TAG, "incProviderRef Now stable - " | |
+ + prc.holder.info.name + ": unstableDelta=" | |
+ + unstableDelta); | |
+ } | |
+ ActivityManagerNative.getDefault().refContentProvider( | |
+ prc.holder.connection, 1, unstableDelta); | |
+ } catch (RemoteException e) { | |
+ //do nothing content provider object is dead any way | |
+ } | |
} | |
- try { | |
- ActivityManagerNative.getDefault().removeContentProvider( | |
- getApplicationThread(), name); | |
- } catch (RemoteException ex) { | |
+ } else { | |
+ prc.unstableCount += 1; | |
+ if (prc.unstableCount == 1) { | |
+ // We are acquiring a new unstable reference on the provider. | |
+ if (prc.removePending) { | |
+ // Oh look, we actually have a remove pending for the | |
+ // provider, which is still holding the last unstable | |
+ // reference. We just need to cancel that to take new | |
+ // ownership of the reference. | |
+ if (DEBUG_PROVIDER) { | |
+ Slog.v(TAG, "incProviderRef: unstable " | |
+ + "snatched provider from the jaws of death"); | |
+ } | |
+ prc.removePending = false; | |
+ mH.removeMessages(H.REMOVE_PROVIDER, prc); | |
+ } else { | |
+ // First unstable ref, increment our count in the | |
+ // activity manager. | |
+ try { | |
+ if (DEBUG_PROVIDER) { | |
+ Slog.v(TAG, "incProviderRef: Now unstable - " | |
+ + prc.holder.info.name); | |
+ } | |
+ ActivityManagerNative.getDefault().refContentProvider( | |
+ prc.holder.connection, 0, 1); | |
+ } catch (RemoteException e) { | |
+ //do nothing content provider object is dead any way | |
+ } | |
+ } | |
} | |
} | |
- return provider; | |
} | |
- public final IContentProvider acquireExistingProvider(Context c, String name) { | |
+ public final IContentProvider acquireExistingProvider(Context c, String name, | |
+ boolean stable) { | |
synchronized (mProviderMap) { | |
ProviderClientRecord pr = mProviderMap.get(name); | |
if (pr == null) { | |
@@ -4062,23 +4313,14 @@ | |
// provider is not reference counted and never needs to be released. | |
ProviderRefCount prc = mProviderRefCountMap.get(jBinder); | |
if (prc != null) { | |
- prc.count += 1; | |
- if (prc.count == 1) { | |
- if (localLOGV) { | |
- Slog.v(TAG, "acquireExistingProvider: " | |
- + "snatched provider from the jaws of death"); | |
- } | |
- // Because the provider previously had a reference count of zero, | |
- // it was scheduled to be removed. Cancel that. | |
- mH.removeMessages(H.REMOVE_PROVIDER, provider); | |
- } | |
+ incProviderRefLocked(prc, stable); | |
} | |
return provider; | |
} | |
} | |
- public final boolean releaseProvider(IContentProvider provider) { | |
- if(provider == null) { | |
+ public final boolean releaseProvider(IContentProvider provider, boolean stable) { | |
+ if (provider == null) { | |
return false; | |
} | |
@@ -4090,47 +4332,98 @@ | |
return false; | |
} | |
- if (prc.count == 0) { | |
- if (localLOGV) Slog.v(TAG, "releaseProvider: ref count already 0, how?"); | |
- return false; | |
+ boolean lastRef = false; | |
+ if (stable) { | |
+ if (prc.stableCount == 0) { | |
+ if (DEBUG_PROVIDER) Slog.v(TAG, | |
+ "releaseProvider: stable ref count already 0, how?"); | |
+ return false; | |
+ } | |
+ prc.stableCount -= 1; | |
+ if (prc.stableCount == 0) { | |
+ // What we do at this point depends on whether there are | |
+ // any unstable refs left: if there are, we just tell the | |
+ // activity manager to decrement its stable count; if there | |
+ // aren't, we need to enqueue this provider to be removed, | |
+ // and convert to holding a single unstable ref while | |
+ // doing so. | |
+ lastRef = prc.unstableCount == 0; | |
+ try { | |
+ if (DEBUG_PROVIDER) { | |
+ Slog.v(TAG, "releaseProvider: No longer stable w/lastRef=" | |
+ + lastRef + " - " + prc.holder.info.name); | |
+ } | |
+ ActivityManagerNative.getDefault().refContentProvider( | |
+ prc.holder.connection, -1, lastRef ? 1 : 0); | |
+ } catch (RemoteException e) { | |
+ //do nothing content provider object is dead any way | |
+ } | |
+ } | |
+ } else { | |
+ if (prc.unstableCount == 0) { | |
+ if (DEBUG_PROVIDER) Slog.v(TAG, | |
+ "releaseProvider: unstable ref count already 0, how?"); | |
+ return false; | |
+ } | |
+ prc.unstableCount -= 1; | |
+ if (prc.unstableCount == 0) { | |
+ // If this is the last reference, we need to enqueue | |
+ // this provider to be removed instead of telling the | |
+ // activity manager to remove it at this point. | |
+ lastRef = prc.stableCount == 0; | |
+ if (!lastRef) { | |
+ try { | |
+ if (DEBUG_PROVIDER) { | |
+ Slog.v(TAG, "releaseProvider: No longer unstable - " | |
+ + prc.holder.info.name); | |
+ } | |
+ ActivityManagerNative.getDefault().refContentProvider( | |
+ prc.holder.connection, 0, -1); | |
+ } catch (RemoteException e) { | |
+ //do nothing content provider object is dead any way | |
+ } | |
+ } | |
+ } | |
} | |
- prc.count -= 1; | |
- if (prc.count == 0) { | |
- // Schedule the actual remove asynchronously, since we don't know the context | |
- // this will be called in. | |
- // TODO: it would be nice to post a delayed message, so | |
- // if we come back and need the same provider quickly | |
- // we will still have it available. | |
- Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, provider); | |
- mH.sendMessage(msg); | |
+ if (lastRef) { | |
+ if (!prc.removePending) { | |
+ // Schedule the actual remove asynchronously, since we don't know the context | |
+ // this will be called in. | |
+ // TODO: it would be nice to post a delayed message, so | |
+ // if we come back and need the same provider quickly | |
+ // we will still have it available. | |
+ if (DEBUG_PROVIDER) { | |
+ Slog.v(TAG, "releaseProvider: Enqueueing pending removal - " | |
+ + prc.holder.info.name); | |
+ } | |
+ prc.removePending = true; | |
+ Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, prc); | |
+ mH.sendMessage(msg); | |
+ } else { | |
+ Slog.w(TAG, "Duplicate remove pending of provider " + prc.holder.info.name); | |
+ } | |
} | |
return true; | |
} | |
} | |
- final void completeRemoveProvider(IContentProvider provider) { | |
- IBinder jBinder = provider.asBinder(); | |
- String remoteProviderName = null; | |
- synchronized(mProviderMap) { | |
- ProviderRefCount prc = mProviderRefCountMap.get(jBinder); | |
- if (prc == null) { | |
- // Either no release is needed (so we shouldn't be here) or the | |
- // provider was already released. | |
- if (localLOGV) Slog.v(TAG, "completeRemoveProvider: release not needed"); | |
- return; | |
- } | |
- | |
- if (prc.count != 0) { | |
+ final void completeRemoveProvider(ProviderRefCount prc) { | |
+ synchronized (mProviderMap) { | |
+ if (!prc.removePending) { | |
// There was a race! Some other client managed to acquire | |
// the provider before the removal was completed. | |
// Abort the removal. We will do it later. | |
- if (localLOGV) Slog.v(TAG, "completeRemoveProvider: lost the race, " | |
+ if (DEBUG_PROVIDER) Slog.v(TAG, "completeRemoveProvider: lost the race, " | |
+ "provider still in use"); | |
return; | |
} | |
- mProviderRefCountMap.remove(jBinder); | |
+ final IBinder jBinder = prc.holder.provider.asBinder(); | |
+ ProviderRefCount existingPrc = mProviderRefCountMap.get(jBinder); | |
+ if (existingPrc == prc) { | |
+ mProviderRefCountMap.remove(jBinder); | |
+ } | |
Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator(); | |
while (iter.hasNext()) { | |
@@ -4138,43 +4431,72 @@ | |
IBinder myBinder = pr.mProvider.asBinder(); | |
if (myBinder == jBinder) { | |
iter.remove(); | |
- if (pr.mLocalProvider == null) { | |
- myBinder.unlinkToDeath(pr, 0); | |
- if (remoteProviderName == null) { | |
- remoteProviderName = pr.mName; | |
- } | |
- } | |
} | |
} | |
} | |
- if (remoteProviderName != null) { | |
- try { | |
- if (localLOGV) { | |
- Slog.v(TAG, "removeProvider: Invoking ActivityManagerNative." | |
- + "removeContentProvider(" + remoteProviderName + ")"); | |
- } | |
- ActivityManagerNative.getDefault().removeContentProvider( | |
- getApplicationThread(), remoteProviderName); | |
- } catch (RemoteException e) { | |
- //do nothing content provider object is dead any way | |
+ try { | |
+ if (DEBUG_PROVIDER) { | |
+ Slog.v(TAG, "removeProvider: Invoking ActivityManagerNative." | |
+ + "removeContentProvider(" + prc.holder.info.name + ")"); | |
} | |
+ ActivityManagerNative.getDefault().removeContentProvider( | |
+ prc.holder.connection, false); | |
+ } catch (RemoteException e) { | |
+ //do nothing content provider object is dead any way | |
} | |
} | |
- final void removeDeadProvider(String name, IContentProvider provider) { | |
+ final void handleUnstableProviderDied(IBinder provider, boolean fromClient) { | |
synchronized(mProviderMap) { | |
- ProviderClientRecord pr = mProviderMap.get(name); | |
- if (pr != null && pr.mProvider.asBinder() == provider.asBinder()) { | |
- Slog.i(TAG, "Removing dead content provider: " + name); | |
- ProviderClientRecord removed = mProviderMap.remove(name); | |
- if (removed != null) { | |
- removed.mProvider.asBinder().unlinkToDeath(removed, 0); | |
+ ProviderRefCount prc = mProviderRefCountMap.get(provider); | |
+ if (prc != null) { | |
+ if (DEBUG_PROVIDER) Slog.v(TAG, "Cleaning up dead provider " | |
+ + provider + " " + prc.holder.info.name); | |
+ mProviderRefCountMap.remove(provider); | |
+ if (prc.client != null && prc.client.mNames != null) { | |
+ for (String name : prc.client.mNames) { | |
+ ProviderClientRecord pr = mProviderMap.get(name); | |
+ if (pr != null && pr.mProvider.asBinder() == provider) { | |
+ Slog.i(TAG, "Removing dead content provider: " + name); | |
+ mProviderMap.remove(name); | |
+ } | |
+ } | |
+ } | |
+ if (fromClient) { | |
+ // We found out about this due to execution in our client | |
+ // code. Tell the activity manager about it now, to ensure | |
+ // that the next time we go to do anything with the provider | |
+ // it knows it is dead (so we don't race with its death | |
+ // notification). | |
+ try { | |
+ ActivityManagerNative.getDefault().unstableProviderDied( | |
+ prc.holder.connection); | |
+ } catch (RemoteException e) { | |
+ //do nothing content provider object is dead any way | |
+ } | |
} | |
} | |
} | |
} | |
+ private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider, | |
+ ContentProvider localProvider,IActivityManager.ContentProviderHolder holder) { | |
+ String names[] = PATTERN_SEMICOLON.split(holder.info.authority); | |
+ ProviderClientRecord pcr = new ProviderClientRecord(names, provider, | |
+ localProvider, holder); | |
+ for (int i = 0; i < names.length; i++) { | |
+ ProviderClientRecord existing = mProviderMap.get(names[i]); | |
+ if (existing != null) { | |
+ Slog.w(TAG, "Content provider " + pcr.mHolder.info.name | |
+ + " already published as " + names[i]); | |
+ } else { | |
+ mProviderMap.put(names[i], pcr); | |
+ } | |
+ } | |
+ return pcr; | |
+ } | |
+ | |
/** | |
* Installs the provider. | |
* | |
@@ -4189,12 +4511,13 @@ | |
* and returns the existing provider. This can happen due to concurrent | |
* attempts to acquire the same provider. | |
*/ | |
- private IContentProvider installProvider(Context context, | |
- IContentProvider provider, ProviderInfo info, | |
- boolean noisy, boolean noReleaseNeeded) { | |
+ private IActivityManager.ContentProviderHolder installProvider(Context context, | |
+ IActivityManager.ContentProviderHolder holder, ProviderInfo info, | |
+ boolean noisy, boolean noReleaseNeeded, boolean stable) { | |
ContentProvider localProvider = null; | |
- if (provider == null) { | |
- if (noisy) { | |
+ IContentProvider provider; | |
+ if (holder == null || holder.provider == null) { | |
+ if (DEBUG_PROVIDER || noisy) { | |
Slog.d(TAG, "Loading provider " + info.authority + ": " | |
+ info.name); | |
} | |
@@ -4231,7 +4554,7 @@ | |
info.applicationInfo.sourceDir); | |
return null; | |
} | |
- if (false) Slog.v( | |
+ if (DEBUG_PROVIDER) Slog.v( | |
TAG, "Instantiating local provider " + info.name); | |
// XXX Need to create the correct context for this provider. | |
localProvider.attachInfo(c, info); | |
@@ -4243,76 +4566,72 @@ | |
} | |
return null; | |
} | |
- } else if (localLOGV) { | |
- Slog.v(TAG, "Installing external provider " + info.authority + ": " | |
+ } else { | |
+ provider = holder.provider; | |
+ if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": " | |
+ info.name); | |
} | |
+ IActivityManager.ContentProviderHolder retHolder; | |
+ | |
synchronized (mProviderMap) { | |
- // There is a possibility that this thread raced with another thread to | |
- // add the provider. If we find another thread got there first then we | |
- // just get out of the way and return the original provider. | |
+ if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider | |
+ + " / " + info.name); | |
IBinder jBinder = provider.asBinder(); | |
- String names[] = PATTERN_SEMICOLON.split(info.authority); | |
- for (int i = 0; i < names.length; i++) { | |
- ProviderClientRecord pr = mProviderMap.get(names[i]); | |
- if (pr != null) { | |
- if (localLOGV) { | |
- Slog.v(TAG, "installProvider: lost the race, " | |
- + "using existing named provider"); | |
- } | |
- provider = pr.mProvider; | |
- } else { | |
- pr = new ProviderClientRecord(names[i], provider, localProvider); | |
- if (localProvider == null) { | |
- try { | |
- jBinder.linkToDeath(pr, 0); | |
- } catch (RemoteException e) { | |
- // Provider already dead. Bail out of here without making | |
- // any changes to the provider map or other data structures. | |
- return null; | |
- } | |
- } | |
- mProviderMap.put(names[i], pr); | |
- } | |
- } | |
- | |
if (localProvider != null) { | |
- ProviderClientRecord pr = mLocalProviders.get(jBinder); | |
+ ComponentName cname = new ComponentName(info.packageName, info.name); | |
+ ProviderClientRecord pr = mLocalProvidersByName.get(cname); | |
if (pr != null) { | |
- if (localLOGV) { | |
+ if (DEBUG_PROVIDER) { | |
Slog.v(TAG, "installProvider: lost the race, " | |
+ "using existing local provider"); | |
} | |
provider = pr.mProvider; | |
} else { | |
- pr = new ProviderClientRecord(null, provider, localProvider); | |
+ holder = new IActivityManager.ContentProviderHolder(info); | |
+ holder.provider = provider; | |
+ holder.noReleaseNeeded = true; | |
+ pr = installProviderAuthoritiesLocked(provider, localProvider, holder); | |
mLocalProviders.put(jBinder, pr); | |
+ mLocalProvidersByName.put(cname, pr); | |
} | |
- } | |
- | |
- if (!noReleaseNeeded) { | |
+ retHolder = pr.mHolder; | |
+ } else { | |
ProviderRefCount prc = mProviderRefCountMap.get(jBinder); | |
if (prc != null) { | |
- if (localLOGV) { | |
- Slog.v(TAG, "installProvider: lost the race, incrementing ref count"); | |
+ if (DEBUG_PROVIDER) { | |
+ Slog.v(TAG, "installProvider: lost the race, updating ref count"); | |
} | |
- prc.count += 1; | |
- if (prc.count == 1) { | |
- if (localLOGV) { | |
- Slog.v(TAG, "installProvider: " | |
- + "snatched provider from the jaws of death"); | |
+ // We need to transfer our new reference to the existing | |
+ // ref count, releasing the old one... but only if | |
+ // release is needed (that is, it is not running in the | |
+ // system process). | |
+ if (!noReleaseNeeded) { | |
+ incProviderRefLocked(prc, stable); | |
+ try { | |
+ ActivityManagerNative.getDefault().removeContentProvider( | |
+ holder.connection, stable); | |
+ } catch (RemoteException e) { | |
+ //do nothing content provider object is dead any way | |
} | |
- // Because the provider previously had a reference count of zero, | |
- // it was scheduled to be removed. Cancel that. | |
- mH.removeMessages(H.REMOVE_PROVIDER, provider); | |
} | |
} else { | |
- mProviderRefCountMap.put(jBinder, new ProviderRefCount(1)); | |
+ ProviderClientRecord client = installProviderAuthoritiesLocked( | |
+ provider, localProvider, holder); | |
+ if (noReleaseNeeded) { | |
+ prc = new ProviderRefCount(holder, client, 1000, 1000); | |
+ } else { | |
+ prc = stable | |
+ ? new ProviderRefCount(holder, client, 1, 0) | |
+ : new ProviderRefCount(holder, client, 0, 1); | |
+ } | |
+ mProviderRefCountMap.put(jBinder, prc); | |
} | |
+ retHolder = prc.holder; | |
} | |
} | |
- return provider; | |
+ | |
+ return retHolder; | |
} | |
private void attach(boolean system) { | |
@@ -4375,7 +4694,7 @@ | |
}); | |
} | |
- public static final ActivityThread systemMain() { | |
+ public static ActivityThread systemMain() { | |
HardwareRenderer.disable(true); | |
ActivityThread thread = new ActivityThread(); | |
thread.attach(true); | |
@@ -4416,6 +4735,8 @@ | |
ActivityThread thread = new ActivityThread(); | |
thread.attach(false); | |
+ AsyncTask.init(); | |
+ | |
if (false) { | |
Looper.myLooper().setMessageLogging(new | |
LogPrinter(Log.DEBUG, "ActivityThread")); | |
diff -Nur android-15/android/app/AlarmManager.java android-16/android/app/AlarmManager.java | |
--- android-15/android/app/AlarmManager.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/app/AlarmManager.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -16,10 +16,8 @@ | |
package android.app; | |
-import android.content.Context; | |
import android.content.Intent; | |
import android.os.RemoteException; | |
-import android.os.ServiceManager; | |
/** | |
* This class provides access to the system alarm services. These allow you | |
@@ -117,8 +115,8 @@ | |
* | |
* @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC or | |
* RTC_WAKEUP. | |
- * @param triggerAtTime Time the alarm should go off, using the | |
- * appropriate clock (depending on the alarm type). | |
+ * @param triggerAtMillis time in milliseconds that the alarm should go | |
+ * off, using the appropriate clock (depending on the alarm type). | |
* @param operation Action to perform when the alarm goes off; | |
* typically comes from {@link PendingIntent#getBroadcast | |
* IntentSender.getBroadcast()}. | |
@@ -134,9 +132,9 @@ | |
* @see #RTC | |
* @see #RTC_WAKEUP | |
*/ | |
- public void set(int type, long triggerAtTime, PendingIntent operation) { | |
+ public void set(int type, long triggerAtMillis, PendingIntent operation) { | |
try { | |
- mService.set(type, triggerAtTime, operation); | |
+ mService.set(type, triggerAtMillis, operation); | |
} catch (RemoteException ex) { | |
} | |
} | |
@@ -169,9 +167,10 @@ | |
* | |
* @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP}, RTC or | |
* RTC_WAKEUP. | |
- * @param triggerAtTime Time the alarm should first go off, using the | |
- * appropriate clock (depending on the alarm type). | |
- * @param interval Interval between subsequent repeats of the alarm. | |
+ * @param triggerAtMillis time in milliseconds that the alarm should first | |
+ * go off, using the appropriate clock (depending on the alarm type). | |
+ * @param intervalMillis interval in milliseconds between subsequent repeats | |
+ * of the alarm. | |
* @param operation Action to perform when the alarm goes off; | |
* typically comes from {@link PendingIntent#getBroadcast | |
* IntentSender.getBroadcast()}. | |
@@ -187,10 +186,10 @@ | |
* @see #RTC | |
* @see #RTC_WAKEUP | |
*/ | |
- public void setRepeating(int type, long triggerAtTime, long interval, | |
- PendingIntent operation) { | |
+ public void setRepeating(int type, long triggerAtMillis, | |
+ long intervalMillis, PendingIntent operation) { | |
try { | |
- mService.setRepeating(type, triggerAtTime, interval, operation); | |
+ mService.setRepeating(type, triggerAtMillis, intervalMillis, operation); | |
} catch (RemoteException ex) { | |
} | |
} | |
@@ -219,20 +218,20 @@ | |
* requested, the time between any two successive firings of the alarm | |
* may vary. If your application demands very low jitter, use | |
* {@link #setRepeating} instead. | |
- * | |
+ * | |
* @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP}, RTC or | |
* RTC_WAKEUP. | |
- * @param triggerAtTime Time the alarm should first go off, using the | |
- * appropriate clock (depending on the alarm type). This | |
- * is inexact: the alarm will not fire before this time, | |
- * but there may be a delay of almost an entire alarm | |
- * interval before the first invocation of the alarm. | |
- * @param interval Interval between subsequent repeats of the alarm. If | |
- * this is one of INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, | |
- * INTERVAL_HOUR, INTERVAL_HALF_DAY, or INTERVAL_DAY then the | |
- * alarm will be phase-aligned with other alarms to reduce | |
- * the number of wakeups. Otherwise, the alarm will be set | |
- * as though the application had called {@link #setRepeating}. | |
+ * @param triggerAtMillis time in milliseconds that the alarm should first | |
+ * go off, using the appropriate clock (depending on the alarm type). This | |
+ * is inexact: the alarm will not fire before this time, but there may be a | |
+ * delay of almost an entire alarm interval before the first invocation of | |
+ * the alarm. | |
+ * @param intervalMillis interval in milliseconds between subsequent repeats | |
+ * of the alarm. If this is one of INTERVAL_FIFTEEN_MINUTES, | |
+ * INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_HALF_DAY, or INTERVAL_DAY | |
+ * then the alarm will be phase-aligned with other alarms to reduce the | |
+ * number of wakeups. Otherwise, the alarm will be set as though the | |
+ * application had called {@link #setRepeating}. | |
* @param operation Action to perform when the alarm goes off; | |
* typically comes from {@link PendingIntent#getBroadcast | |
* IntentSender.getBroadcast()}. | |
@@ -253,10 +252,10 @@ | |
* @see #INTERVAL_HALF_DAY | |
* @see #INTERVAL_DAY | |
*/ | |
- public void setInexactRepeating(int type, long triggerAtTime, long interval, | |
- PendingIntent operation) { | |
+ public void setInexactRepeating(int type, long triggerAtMillis, | |
+ long intervalMillis, PendingIntent operation) { | |
try { | |
- mService.setInexactRepeating(type, triggerAtTime, interval, operation); | |
+ mService.setInexactRepeating(type, triggerAtMillis, intervalMillis, operation); | |
} catch (RemoteException ex) { | |
} | |
} | |
diff -Nur android-15/android/app/ApplicationPackageManager.java android-16/android/app/ApplicationPackageManager.java | |
--- android-15/android/app/ApplicationPackageManager.java 2012-06-18 20:00:44.000000000 +0900 | |
+++ android-16/android/app/ApplicationPackageManager.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -24,6 +24,7 @@ | |
import android.content.pm.ActivityInfo; | |
import android.content.pm.ApplicationInfo; | |
import android.content.pm.ComponentInfo; | |
+import android.content.pm.ContainerEncryptionParams; | |
import android.content.pm.FeatureInfo; | |
import android.content.pm.IPackageDataObserver; | |
import android.content.pm.IPackageDeleteObserver; | |
@@ -49,6 +50,7 @@ | |
import android.net.Uri; | |
import android.os.Process; | |
import android.os.RemoteException; | |
+import android.os.UserId; | |
import android.util.Log; | |
import java.lang.ref.WeakReference; | |
@@ -67,7 +69,7 @@ | |
public PackageInfo getPackageInfo(String packageName, int flags) | |
throws NameNotFoundException { | |
try { | |
- PackageInfo pi = mPM.getPackageInfo(packageName, flags); | |
+ PackageInfo pi = mPM.getPackageInfo(packageName, flags, UserId.myUserId()); | |
if (pi != null) { | |
return pi; | |
} | |
@@ -197,7 +199,7 @@ | |
public ApplicationInfo getApplicationInfo(String packageName, int flags) | |
throws NameNotFoundException { | |
try { | |
- ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags); | |
+ ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags, UserId.myUserId()); | |
if (ai != null) { | |
return ai; | |
} | |
@@ -212,7 +214,7 @@ | |
public ActivityInfo getActivityInfo(ComponentName className, int flags) | |
throws NameNotFoundException { | |
try { | |
- ActivityInfo ai = mPM.getActivityInfo(className, flags); | |
+ ActivityInfo ai = mPM.getActivityInfo(className, flags, UserId.myUserId()); | |
if (ai != null) { | |
return ai; | |
} | |
@@ -227,7 +229,7 @@ | |
public ActivityInfo getReceiverInfo(ComponentName className, int flags) | |
throws NameNotFoundException { | |
try { | |
- ActivityInfo ai = mPM.getReceiverInfo(className, flags); | |
+ ActivityInfo ai = mPM.getReceiverInfo(className, flags, UserId.myUserId()); | |
if (ai != null) { | |
return ai; | |
} | |
@@ -242,7 +244,7 @@ | |
public ServiceInfo getServiceInfo(ComponentName className, int flags) | |
throws NameNotFoundException { | |
try { | |
- ServiceInfo si = mPM.getServiceInfo(className, flags); | |
+ ServiceInfo si = mPM.getServiceInfo(className, flags, UserId.myUserId()); | |
if (si != null) { | |
return si; | |
} | |
@@ -257,7 +259,7 @@ | |
public ProviderInfo getProviderInfo(ComponentName className, int flags) | |
throws NameNotFoundException { | |
try { | |
- ProviderInfo pi = mPM.getProviderInfo(className, flags); | |
+ ProviderInfo pi = mPM.getProviderInfo(className, flags, UserId.myUserId()); | |
if (pi != null) { | |
return pi; | |
} | |
@@ -332,6 +334,24 @@ | |
} | |
@Override | |
+ public void grantPermission(String packageName, String permissionName) { | |
+ try { | |
+ mPM.grantPermission(packageName, permissionName); | |
+ } catch (RemoteException e) { | |
+ throw new RuntimeException("Package manager has died", e); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void revokePermission(String packageName, String permissionName) { | |
+ try { | |
+ mPM.revokePermission(packageName, permissionName); | |
+ } catch (RemoteException e) { | |
+ throw new RuntimeException("Package manager has died", e); | |
+ } | |
+ } | |
+ | |
+ @Override | |
public int checkSignatures(String pkg1, String pkg2) { | |
try { | |
return mPM.checkSignatures(pkg1, pkg2); | |
@@ -404,6 +424,7 @@ | |
@SuppressWarnings("unchecked") | |
@Override | |
public List<ApplicationInfo> getInstalledApplications(int flags) { | |
+ int userId = UserId.getUserId(Process.myUid()); | |
try { | |
final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>(); | |
ApplicationInfo lastItem = null; | |
@@ -411,7 +432,7 @@ | |
do { | |
final String lastKey = lastItem != null ? lastItem.packageName : null; | |
- slice = mPM.getInstalledApplications(flags, lastKey); | |
+ slice = mPM.getInstalledApplications(flags, lastKey, userId); | |
lastItem = slice.populateList(applicationInfos, ApplicationInfo.CREATOR); | |
} while (!slice.isLastSlice()); | |
@@ -427,7 +448,7 @@ | |
return mPM.resolveIntent( | |
intent, | |
intent.resolveTypeIfNeeded(mContext.getContentResolver()), | |
- flags); | |
+ flags, UserId.myUserId()); | |
} catch (RemoteException e) { | |
throw new RuntimeException("Package manager has died", e); | |
} | |
@@ -440,7 +461,8 @@ | |
return mPM.queryIntentActivities( | |
intent, | |
intent.resolveTypeIfNeeded(mContext.getContentResolver()), | |
- flags); | |
+ flags, | |
+ UserId.myUserId()); | |
} catch (RemoteException e) { | |
throw new RuntimeException("Package manager has died", e); | |
} | |
@@ -472,7 +494,7 @@ | |
try { | |
return mPM.queryIntentActivityOptions(caller, specifics, | |
specificTypes, intent, intent.resolveTypeIfNeeded(resolver), | |
- flags); | |
+ flags, UserId.myUserId()); | |
} catch (RemoteException e) { | |
throw new RuntimeException("Package manager has died", e); | |
} | |
@@ -484,7 +506,8 @@ | |
return mPM.queryIntentReceivers( | |
intent, | |
intent.resolveTypeIfNeeded(mContext.getContentResolver()), | |
- flags); | |
+ flags, | |
+ UserId.myUserId()); | |
} catch (RemoteException e) { | |
throw new RuntimeException("Package manager has died", e); | |
} | |
@@ -496,7 +519,8 @@ | |
return mPM.resolveService( | |
intent, | |
intent.resolveTypeIfNeeded(mContext.getContentResolver()), | |
- flags); | |
+ flags, | |
+ UserId.myUserId()); | |
} catch (RemoteException e) { | |
throw new RuntimeException("Package manager has died", e); | |
} | |
@@ -508,7 +532,8 @@ | |
return mPM.queryIntentServices( | |
intent, | |
intent.resolveTypeIfNeeded(mContext.getContentResolver()), | |
- flags); | |
+ flags, | |
+ UserId.myUserId()); | |
} catch (RemoteException e) { | |
throw new RuntimeException("Package manager has died", e); | |
} | |
@@ -518,7 +543,7 @@ | |
public ProviderInfo resolveContentProvider(String name, | |
int flags) { | |
try { | |
- return mPM.resolveContentProvider(name, flags); | |
+ return mPM.resolveContentProvider(name, flags, UserId.myUserId()); | |
} catch (RemoteException e) { | |
throw new RuntimeException("Package manager has died", e); | |
} | |
@@ -949,10 +974,10 @@ | |
@Override | |
public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer, | |
int flags, String installerPackageName, Uri verificationURI, | |
- ManifestDigest manifestDigest) { | |
+ ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) { | |
try { | |
mPM.installPackageWithVerification(packageURI, observer, flags, installerPackageName, | |
- verificationURI, manifestDigest); | |
+ verificationURI, manifestDigest, encryptionParams); | |
} catch (RemoteException e) { | |
// Should never happen! | |
} | |
@@ -1008,7 +1033,7 @@ | |
public void clearApplicationUserData(String packageName, | |
IPackageDataObserver observer) { | |
try { | |
- mPM.clearApplicationUserData(packageName, observer); | |
+ mPM.clearApplicationUserData(packageName, observer, UserId.myUserId()); | |
} catch (RemoteException e) { | |
// Should never happen! | |
} | |
@@ -1121,7 +1146,7 @@ | |
public void setComponentEnabledSetting(ComponentName componentName, | |
int newState, int flags) { | |
try { | |
- mPM.setComponentEnabledSetting(componentName, newState, flags); | |
+ mPM.setComponentEnabledSetting(componentName, newState, flags, UserId.myUserId()); | |
} catch (RemoteException e) { | |
// Should never happen! | |
} | |
@@ -1130,7 +1155,7 @@ | |
@Override | |
public int getComponentEnabledSetting(ComponentName componentName) { | |
try { | |
- return mPM.getComponentEnabledSetting(componentName); | |
+ return mPM.getComponentEnabledSetting(componentName, UserId.myUserId()); | |
} catch (RemoteException e) { | |
// Should never happen! | |
} | |
@@ -1141,7 +1166,7 @@ | |
public void setApplicationEnabledSetting(String packageName, | |
int newState, int flags) { | |
try { | |
- mPM.setApplicationEnabledSetting(packageName, newState, flags); | |
+ mPM.setApplicationEnabledSetting(packageName, newState, flags, UserId.myUserId()); | |
} catch (RemoteException e) { | |
// Should never happen! | |
} | |
@@ -1150,7 +1175,7 @@ | |
@Override | |
public int getApplicationEnabledSetting(String packageName) { | |
try { | |
- return mPM.getApplicationEnabledSetting(packageName); | |
+ return mPM.getApplicationEnabledSetting(packageName, UserId.myUserId()); | |
} catch (RemoteException e) { | |
// Should never happen! | |
} | |
@@ -1177,13 +1202,26 @@ | |
*/ | |
@Override | |
public List<UserInfo> getUsers() { | |
- // TODO: | |
- // Dummy code, always returns just the primary user | |
- ArrayList<UserInfo> users = new ArrayList<UserInfo>(); | |
- UserInfo primary = new UserInfo(0, "Root!", | |
- UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); | |
- users.add(primary); | |
- return users; | |
+ try { | |
+ return mPM.getUsers(); | |
+ } catch (RemoteException re) { | |
+ ArrayList<UserInfo> users = new ArrayList<UserInfo>(); | |
+ UserInfo primary = new UserInfo(0, "Root!", UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); | |
+ users.add(primary); | |
+ return users; | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * @hide | |
+ */ | |
+ @Override | |
+ public UserInfo getUser(int userId) { | |
+ try { | |
+ return mPM.getUser(userId); | |
+ } catch (RemoteException re) { | |
+ return null; | |
+ } | |
} | |
/** | |
@@ -1203,7 +1241,10 @@ | |
*/ | |
@Override | |
public void updateUserName(int id, String name) { | |
- // TODO: | |
+ try { | |
+ mPM.updateUserName(id, name); | |
+ } catch (RemoteException re) { | |
+ } | |
} | |
/** | |
diff -Nur android-15/android/app/ApplicationThreadNative.java android-16/android/app/ApplicationThreadNative.java | |
--- android-15/android/app/ApplicationThreadNative.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/app/ApplicationThreadNative.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -267,6 +267,7 @@ | |
IBinder binder = data.readStrongBinder(); | |
IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder); | |
int testMode = data.readInt(); | |
+ boolean openGlTrace = data.readInt() != 0; | |
boolean restrictedBackupMode = (data.readInt() != 0); | |
boolean persistent = (data.readInt() != 0); | |
Configuration config = Configuration.CREATOR.createFromParcel(data); | |
@@ -275,11 +276,11 @@ | |
Bundle coreSettings = data.readBundle(); | |
bindApplication(packageName, info, | |
providers, testName, profileName, profileFd, autoStopProfiler, | |
- testArgs, testWatcher, testMode, restrictedBackupMode, persistent, | |
- config, compatInfo, services, coreSettings); | |
+ testArgs, testWatcher, testMode, openGlTrace, restrictedBackupMode, | |
+ persistent, config, compatInfo, services, coreSettings); | |
return true; | |
} | |
- | |
+ | |
case SCHEDULE_EXIT_TRANSACTION: | |
{ | |
data.enforceInterface(IApplicationThread.descriptor); | |
@@ -352,6 +353,21 @@ | |
return true; | |
} | |
+ case DUMP_PROVIDER_TRANSACTION: { | |
+ data.enforceInterface(IApplicationThread.descriptor); | |
+ ParcelFileDescriptor fd = data.readFileDescriptor(); | |
+ final IBinder service = data.readStrongBinder(); | |
+ final String[] args = data.readStringArray(); | |
+ if (fd != null) { | |
+ dumpProvider(fd.getFileDescriptor(), service, args); | |
+ try { | |
+ fd.close(); | |
+ } catch (IOException e) { | |
+ } | |
+ } | |
+ return true; | |
+ } | |
+ | |
case SCHEDULE_REGISTERED_RECEIVER_TRANSACTION: { | |
data.enforceInterface(IApplicationThread.descriptor); | |
IIntentReceiver receiver = IIntentReceiver.Stub.asInterface( | |
@@ -539,6 +555,35 @@ | |
reply.writeNoException(); | |
return true; | |
} | |
+ | |
+ case DUMP_DB_INFO_TRANSACTION: | |
+ { | |
+ data.enforceInterface(IApplicationThread.descriptor); | |
+ ParcelFileDescriptor fd = data.readFileDescriptor(); | |
+ String[] args = data.readStringArray(); | |
+ if (fd != null) { | |
+ try { | |
+ dumpDbInfo(fd.getFileDescriptor(), args); | |
+ } finally { | |
+ try { | |
+ fd.close(); | |
+ } catch (IOException e) { | |
+ // swallowed, not propagated back to the caller | |
+ } | |
+ } | |
+ } | |
+ reply.writeNoException(); | |
+ return true; | |
+ } | |
+ | |
+ case UNSTABLE_PROVIDER_DIED_TRANSACTION: | |
+ { | |
+ data.enforceInterface(IApplicationThread.descriptor); | |
+ IBinder provider = data.readStrongBinder(); | |
+ unstableProviderDied(provider); | |
+ reply.writeNoException(); | |
+ return true; | |
+ } | |
} | |
return super.onTransact(code, data, reply, flags); | |
@@ -814,8 +859,9 @@ | |
public final void bindApplication(String packageName, ApplicationInfo info, | |
List<ProviderInfo> providers, ComponentName testName, String profileName, | |
ParcelFileDescriptor profileFd, boolean autoStopProfiler, Bundle testArgs, | |
- IInstrumentationWatcher testWatcher, int debugMode, boolean restrictedBackupMode, | |
- boolean persistent, Configuration config, CompatibilityInfo compatInfo, | |
+ IInstrumentationWatcher testWatcher, int debugMode, boolean openGlTrace, | |
+ boolean restrictedBackupMode, boolean persistent, | |
+ Configuration config, CompatibilityInfo compatInfo, | |
Map<String, IBinder> services, Bundle coreSettings) throws RemoteException { | |
Parcel data = Parcel.obtain(); | |
data.writeInterfaceToken(IApplicationThread.descriptor); | |
@@ -839,6 +885,7 @@ | |
data.writeBundle(testArgs); | |
data.writeStrongInterface(testWatcher); | |
data.writeInt(debugMode); | |
+ data.writeInt(openGlTrace ? 1 : 0); | |
data.writeInt(restrictedBackupMode ? 1 : 0); | |
data.writeInt(persistent ? 1 : 0); | |
config.writeToParcel(data, 0); | |
@@ -931,6 +978,17 @@ | |
data.recycle(); | |
} | |
+ public void dumpProvider(FileDescriptor fd, IBinder token, String[] args) | |
+ throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ data.writeInterfaceToken(IApplicationThread.descriptor); | |
+ data.writeFileDescriptor(fd); | |
+ data.writeStrongBinder(token); | |
+ data.writeStringArray(args); | |
+ mRemote.transact(DUMP_PROVIDER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); | |
+ data.recycle(); | |
+ } | |
+ | |
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, | |
int resultCode, String dataStr, Bundle extras, boolean ordered, boolean sticky) | |
throws RemoteException { | |
@@ -1105,4 +1163,21 @@ | |
mRemote.transact(DUMP_GFX_INFO_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); | |
data.recycle(); | |
} | |
+ | |
+ public void dumpDbInfo(FileDescriptor fd, String[] args) throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ data.writeInterfaceToken(IApplicationThread.descriptor); | |
+ data.writeFileDescriptor(fd); | |
+ data.writeStringArray(args); | |
+ mRemote.transact(DUMP_DB_INFO_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); | |
+ data.recycle(); | |
+ } | |
+ | |
+ public void unstableProviderDied(IBinder provider) throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ data.writeInterfaceToken(IApplicationThread.descriptor); | |
+ data.writeStrongBinder(provider); | |
+ mRemote.transact(UNSTABLE_PROVIDER_DIED_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); | |
+ data.recycle(); | |
+ } | |
} | |
diff -Nur android-15/android/app/BackStackRecord.java android-16/android/app/BackStackRecord.java | |
--- android-15/android/app/BackStackRecord.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/app/BackStackRecord.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -53,7 +53,7 @@ | |
int pos = 0; | |
while (op != null) { | |
mOps[pos++] = op.cmd; | |
- mOps[pos++] = op.fragment.mIndex; | |
+ mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1; | |
mOps[pos++] = op.enterAnim; | |
mOps[pos++] = op.exitAnim; | |
mOps[pos++] = op.popEnterAnim; | |
@@ -99,8 +99,13 @@ | |
op.cmd = mOps[pos++]; | |
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, | |
"BSE " + bse + " set base fragment #" + mOps[pos]); | |
- Fragment f = fm.mActive.get(mOps[pos++]); | |
- op.fragment = f; | |
+ int findex = mOps[pos++]; | |
+ if (findex >= 0) { | |
+ Fragment f = fm.mActive.get(findex); | |
+ op.fragment = f; | |
+ } else { | |
+ op.fragment = null; | |
+ } | |
op.enterAnim = mOps[pos++]; | |
op.exitAnim = mOps[pos++]; | |
op.popEnterAnim = mOps[pos++]; | |
@@ -506,9 +511,11 @@ | |
+ " by " + amt); | |
Op op = mHead; | |
while (op != null) { | |
- op.fragment.mBackStackNesting += amt; | |
- if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " | |
- + op.fragment + " to " + op.fragment.mBackStackNesting); | |
+ if (op.fragment != null) { | |
+ op.fragment.mBackStackNesting += amt; | |
+ if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " | |
+ + op.fragment + " to " + op.fragment.mBackStackNesting); | |
+ } | |
if (op.removed != null) { | |
for (int i=op.removed.size()-1; i>=0; i--) { | |
Fragment r = op.removed.get(i); | |
@@ -568,23 +575,29 @@ | |
Fragment old = mManager.mAdded.get(i); | |
if (FragmentManagerImpl.DEBUG) Log.v(TAG, | |
"OP_REPLACE: adding=" + f + " old=" + old); | |
- if (old.mContainerId == f.mContainerId) { | |
- if (op.removed == null) { | |
- op.removed = new ArrayList<Fragment>(); | |
+ if (f == null || old.mContainerId == f.mContainerId) { | |
+ if (old == f) { | |
+ op.fragment = f = null; | |
+ } else { | |
+ if (op.removed == null) { | |
+ op.removed = new ArrayList<Fragment>(); | |
+ } | |
+ op.removed.add(old); | |
+ old.mNextAnim = op.exitAnim; | |
+ if (mAddToBackStack) { | |
+ old.mBackStackNesting += 1; | |
+ if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " | |
+ + old + " to " + old.mBackStackNesting); | |
+ } | |
+ mManager.removeFragment(old, mTransition, mTransitionStyle); | |
} | |
- op.removed.add(old); | |
- old.mNextAnim = op.exitAnim; | |
- if (mAddToBackStack) { | |
- old.mBackStackNesting += 1; | |
- if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " | |
- + old + " to " + old.mBackStackNesting); | |
- } | |
- mManager.removeFragment(old, mTransition, mTransitionStyle); | |
} | |
} | |
} | |
- f.mNextAnim = op.enterAnim; | |
- mManager.addFragment(f, false); | |
+ if (f != null) { | |
+ f.mNextAnim = op.enterAnim; | |
+ mManager.addFragment(f, false); | |
+ } | |
} break; | |
case OP_REMOVE: { | |
Fragment f = op.fragment; | |
@@ -644,10 +657,12 @@ | |
} break; | |
case OP_REPLACE: { | |
Fragment f = op.fragment; | |
- f.mNextAnim = op.popExitAnim; | |
- mManager.removeFragment(f, | |
- FragmentManagerImpl.reverseTransit(mTransition), | |
- mTransitionStyle); | |
+ if (f != null) { | |
+ f.mNextAnim = op.popExitAnim; | |
+ mManager.removeFragment(f, | |
+ FragmentManagerImpl.reverseTransit(mTransition), | |
+ mTransitionStyle); | |
+ } | |
if (op.removed != null) { | |
for (int i=0; i<op.removed.size(); i++) { | |
Fragment old = op.removed.get(i); | |
diff -Nur android-15/android/app/ContextImpl.java android-16/android/app/ContextImpl.java | |
--- android-15/android/app/ContextImpl.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/app/ContextImpl.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -42,7 +42,12 @@ | |
import android.database.sqlite.SQLiteDatabase.CursorFactory; | |
import android.graphics.Bitmap; | |
import android.graphics.drawable.Drawable; | |
+import android.hardware.ISerialManager; | |
import android.hardware.SensorManager; | |
+import android.hardware.SerialManager; | |
+import android.hardware.SystemSensorManager; | |
+import android.hardware.input.IInputManager; | |
+import android.hardware.input.InputManager; | |
import android.hardware.usb.IUsbManager; | |
import android.hardware.usb.UsbManager; | |
import android.location.CountryDetector; | |
@@ -50,6 +55,7 @@ | |
import android.location.ILocationManager; | |
import android.location.LocationManager; | |
import android.media.AudioManager; | |
+import android.media.MediaRouter; | |
import android.net.ConnectivityManager; | |
import android.net.IConnectivityManager; | |
import android.net.INetworkPolicyManager; | |
@@ -57,6 +63,8 @@ | |
import android.net.ThrottleManager; | |
import android.net.IThrottleManager; | |
import android.net.Uri; | |
+import android.net.nsd.INsdManager; | |
+import android.net.nsd.NsdManager; | |
import android.net.wifi.IWifiManager; | |
import android.net.wifi.WifiManager; | |
import android.net.wifi.p2p.IWifiP2pManager; | |
@@ -75,7 +83,8 @@ | |
import android.os.Process; | |
import android.os.RemoteException; | |
import android.os.ServiceManager; | |
-import android.os.Vibrator; | |
+import android.os.UserId; | |
+import android.os.SystemVibrator; | |
import android.os.storage.StorageManager; | |
import android.telephony.TelephonyManager; | |
import android.content.ClipboardManager; | |
@@ -280,6 +289,11 @@ | |
return new AudioManager(ctx); | |
}}); | |
+ registerService(MEDIA_ROUTER_SERVICE, new ServiceFetcher() { | |
+ public Object createService(ContextImpl ctx) { | |
+ return new MediaRouter(ctx); | |
+ }}); | |
+ | |
registerService(CLIPBOARD_SERVICE, new ServiceFetcher() { | |
public Object createService(ContextImpl ctx) { | |
return new ClipboardManager(ctx.getOuterContext(), | |
@@ -318,6 +332,11 @@ | |
return createDropBoxManager(); | |
}}); | |
+ registerService(INPUT_SERVICE, new StaticServiceFetcher() { | |
+ public Object createStaticService() { | |
+ return InputManager.getInstance(); | |
+ }}); | |
+ | |
registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() { | |
public Object createService(ContextImpl ctx) { | |
return InputMethodManager.getInstance(ctx); | |
@@ -369,6 +388,14 @@ | |
ctx.mMainThread.getHandler()); | |
}}); | |
+ registerService(NSD_SERVICE, new ServiceFetcher() { | |
+ @Override | |
+ public Object createService(ContextImpl ctx) { | |
+ IBinder b = ServiceManager.getService(NSD_SERVICE); | |
+ INsdManager service = INsdManager.Stub.asInterface(b); | |
+ return new NsdManager(ctx.getOuterContext(), service); | |
+ }}); | |
+ | |
// Note: this was previously cached in a static variable, but | |
// constructed using mMainThread.getHandler(), so converting | |
// it to be a regular Context-cached service... | |
@@ -387,7 +414,7 @@ | |
registerService(SENSOR_SERVICE, new ServiceFetcher() { | |
public Object createService(ContextImpl ctx) { | |
- return new SensorManager(ctx.mMainThread.getHandler().getLooper()); | |
+ return new SystemSensorManager(ctx.mMainThread.getHandler().getLooper()); | |
}}); | |
registerService(STATUS_BAR_SERVICE, new ServiceFetcher() { | |
@@ -427,9 +454,15 @@ | |
return new UsbManager(ctx, IUsbManager.Stub.asInterface(b)); | |
}}); | |
+ registerService(SERIAL_SERVICE, new ServiceFetcher() { | |
+ public Object createService(ContextImpl ctx) { | |
+ IBinder b = ServiceManager.getService(SERIAL_SERVICE); | |
+ return new SerialManager(ctx, ISerialManager.Stub.asInterface(b)); | |
+ }}); | |
+ | |
registerService(VIBRATOR_SERVICE, new ServiceFetcher() { | |
public Object createService(ContextImpl ctx) { | |
- return new Vibrator(); | |
+ return new SystemVibrator(); | |
}}); | |
registerService(WALLPAPER_SERVICE, WALLPAPER_FETCHER); | |
@@ -757,17 +790,18 @@ | |
@Override | |
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) { | |
- File f = validateFilePath(name, true); | |
- SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f, factory); | |
- setFilePermissionsFromMode(f.getPath(), mode, 0); | |
- return db; | |
+ return openOrCreateDatabase(name, mode, factory, null); | |
} | |
@Override | |
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, | |
DatabaseErrorHandler errorHandler) { | |
File f = validateFilePath(name, true); | |
- SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f.getPath(), factory, errorHandler); | |
+ int flags = SQLiteDatabase.CREATE_IF_NECESSARY; | |
+ if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) { | |
+ flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING; | |
+ } | |
+ SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), factory, flags, errorHandler); | |
setFilePermissionsFromMode(f.getPath(), mode, 0); | |
return db; | |
} | |
@@ -776,7 +810,7 @@ | |
public boolean deleteDatabase(String name) { | |
try { | |
File f = validateFilePath(name, false); | |
- return f.delete(); | |
+ return SQLiteDatabase.deleteDatabase(f); | |
} catch (Exception e) { | |
} | |
return false; | |
@@ -843,6 +877,11 @@ | |
@Override | |
public void startActivity(Intent intent) { | |
+ startActivity(intent, null); | |
+ } | |
+ | |
+ @Override | |
+ public void startActivity(Intent intent, Bundle options) { | |
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { | |
throw new AndroidRuntimeException( | |
"Calling startActivity() from outside of an Activity " | |
@@ -851,11 +890,16 @@ | |
} | |
mMainThread.getInstrumentation().execStartActivity( | |
getOuterContext(), mMainThread.getApplicationThread(), null, | |
- (Activity)null, intent, -1); | |
+ (Activity)null, intent, -1, options); | |
} | |
@Override | |
public void startActivities(Intent[] intents) { | |
+ startActivities(intents, null); | |
+ } | |
+ | |
+ @Override | |
+ public void startActivities(Intent[] intents, Bundle options) { | |
if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { | |
throw new AndroidRuntimeException( | |
"Calling startActivities() from outside of an Activity " | |
@@ -864,13 +908,20 @@ | |
} | |
mMainThread.getInstrumentation().execStartActivities( | |
getOuterContext(), mMainThread.getApplicationThread(), null, | |
- (Activity)null, intents); | |
+ (Activity)null, intents, options); | |
} | |
@Override | |
public void startIntentSender(IntentSender intent, | |
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) | |
throws IntentSender.SendIntentException { | |
+ startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags, null); | |
+ } | |
+ | |
+ @Override | |
+ public void startIntentSender(IntentSender intent, Intent fillInIntent, | |
+ int flagsMask, int flagsValues, int extraFlags, Bundle options) | |
+ throws IntentSender.SendIntentException { | |
try { | |
String resolvedType = null; | |
if (fillInIntent != null) { | |
@@ -880,8 +931,8 @@ | |
int result = ActivityManagerNative.getDefault() | |
.startActivityIntentSender(mMainThread.getApplicationThread(), intent, | |
fillInIntent, resolvedType, null, null, | |
- 0, flagsMask, flagsValues); | |
- if (result == IActivityManager.START_CANCELED) { | |
+ 0, flagsMask, flagsValues, options); | |
+ if (result == ActivityManager.START_CANCELED) { | |
throw new IntentSender.SendIntentException(); | |
} | |
Instrumentation.checkStartActivityResult(result, null); | |
@@ -896,7 +947,21 @@ | |
intent.setAllowFds(false); | |
ActivityManagerNative.getDefault().broadcastIntent( | |
mMainThread.getApplicationThread(), intent, resolvedType, null, | |
- Activity.RESULT_OK, null, null, null, false, false); | |
+ Activity.RESULT_OK, null, null, null, false, false, | |
+ Binder.getOrigCallingUser()); | |
+ } catch (RemoteException e) { | |
+ } | |
+ } | |
+ | |
+ /** @hide */ | |
+ @Override | |
+ public void sendBroadcast(Intent intent, int userId) { | |
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); | |
+ try { | |
+ intent.setAllowFds(false); | |
+ ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(), | |
+ intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false, | |
+ userId); | |
} catch (RemoteException e) { | |
} | |
} | |
@@ -908,7 +973,8 @@ | |
intent.setAllowFds(false); | |
ActivityManagerNative.getDefault().broadcastIntent( | |
mMainThread.getApplicationThread(), intent, resolvedType, null, | |
- Activity.RESULT_OK, null, null, receiverPermission, false, false); | |
+ Activity.RESULT_OK, null, null, receiverPermission, false, false, | |
+ Binder.getOrigCallingUser()); | |
} catch (RemoteException e) { | |
} | |
} | |
@@ -921,7 +987,8 @@ | |
intent.setAllowFds(false); | |
ActivityManagerNative.getDefault().broadcastIntent( | |
mMainThread.getApplicationThread(), intent, resolvedType, null, | |
- Activity.RESULT_OK, null, null, receiverPermission, true, false); | |
+ Activity.RESULT_OK, null, null, receiverPermission, true, false, | |
+ Binder.getOrigCallingUser()); | |
} catch (RemoteException e) { | |
} | |
} | |
@@ -954,7 +1021,7 @@ | |
ActivityManagerNative.getDefault().broadcastIntent( | |
mMainThread.getApplicationThread(), intent, resolvedType, rd, | |
initialCode, initialData, initialExtras, receiverPermission, | |
- true, false); | |
+ true, false, Binder.getOrigCallingUser()); | |
} catch (RemoteException e) { | |
} | |
} | |
@@ -966,7 +1033,8 @@ | |
intent.setAllowFds(false); | |
ActivityManagerNative.getDefault().broadcastIntent( | |
mMainThread.getApplicationThread(), intent, resolvedType, null, | |
- Activity.RESULT_OK, null, null, null, false, true); | |
+ Activity.RESULT_OK, null, null, null, false, true, | |
+ Binder.getOrigCallingUser()); | |
} catch (RemoteException e) { | |
} | |
} | |
@@ -999,7 +1067,7 @@ | |
ActivityManagerNative.getDefault().broadcastIntent( | |
mMainThread.getApplicationThread(), intent, resolvedType, rd, | |
initialCode, initialData, initialExtras, null, | |
- true, true); | |
+ true, true, Binder.getOrigCallingUser()); | |
} catch (RemoteException e) { | |
} | |
} | |
@@ -1014,7 +1082,7 @@ | |
try { | |
intent.setAllowFds(false); | |
ActivityManagerNative.getDefault().unbroadcastIntent( | |
- mMainThread.getApplicationThread(), intent); | |
+ mMainThread.getApplicationThread(), intent, Binder.getOrigCallingUser()); | |
} catch (RemoteException e) { | |
} | |
} | |
@@ -1112,6 +1180,12 @@ | |
@Override | |
public boolean bindService(Intent service, ServiceConnection conn, | |
int flags) { | |
+ return bindService(service, conn, flags, UserId.getUserId(Process.myUid())); | |
+ } | |
+ | |
+ /** @hide */ | |
+ @Override | |
+ public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) { | |
IServiceConnection sd; | |
if (mPackageInfo != null) { | |
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), | |
@@ -1130,7 +1204,7 @@ | |
int res = ActivityManagerNative.getDefault().bindService( | |
mMainThread.getApplicationThread(), getActivityToken(), | |
service, service.resolveTypeIfNeeded(getContentResolver()), | |
- sd, flags); | |
+ sd, flags, userId); | |
if (res < 0) { | |
throw new SecurityException( | |
"Not allowed to bind to service " + service); | |
@@ -1215,8 +1289,7 @@ | |
int pid = Binder.getCallingPid(); | |
if (pid != Process.myPid()) { | |
- return checkPermission(permission, pid, | |
- Binder.getCallingUid()); | |
+ return checkPermission(permission, pid, Binder.getCallingUid()); | |
} | |
return PackageManager.PERMISSION_DENIED; | |
} | |
@@ -1384,7 +1457,8 @@ | |
Uri uri, int modeFlags, String message) { | |
enforceForUri( | |
modeFlags, checkCallingUriPermission(uri, modeFlags), | |
- false, Binder.getCallingUid(), uri, message); | |
+ false, | |
+ Binder.getCallingUid(), uri, message); | |
} | |
public void enforceCallingOrSelfUriPermission( | |
@@ -1412,7 +1486,9 @@ | |
public Context createPackageContext(String packageName, int flags) | |
throws PackageManager.NameNotFoundException { | |
if (packageName.equals("system") || packageName.equals("android")) { | |
- return new ContextImpl(mMainThread.getSystemContext()); | |
+ final ContextImpl context = new ContextImpl(mMainThread.getSystemContext()); | |
+ context.mBasePackageName = mBasePackageName; | |
+ return context; | |
} | |
LoadedApk pi = | |
@@ -1609,17 +1685,32 @@ | |
@Override | |
protected IContentProvider acquireProvider(Context context, String name) { | |
- return mMainThread.acquireProvider(context, name); | |
+ return mMainThread.acquireProvider(context, name, true); | |
} | |
@Override | |
protected IContentProvider acquireExistingProvider(Context context, String name) { | |
- return mMainThread.acquireExistingProvider(context, name); | |
+ return mMainThread.acquireExistingProvider(context, name, true); | |
} | |
@Override | |
public boolean releaseProvider(IContentProvider provider) { | |
- return mMainThread.releaseProvider(provider); | |
+ return mMainThread.releaseProvider(provider, true); | |
+ } | |
+ | |
+ @Override | |
+ protected IContentProvider acquireUnstableProvider(Context c, String name) { | |
+ return mMainThread.acquireProvider(c, name, false); | |
+ } | |
+ | |
+ @Override | |
+ public boolean releaseUnstableProvider(IContentProvider icp) { | |
+ return mMainThread.releaseProvider(icp, false); | |
+ } | |
+ | |
+ @Override | |
+ public void unstableProviderDied(IContentProvider icp) { | |
+ mMainThread.handleUnstableProviderDied(icp.asBinder(), true); | |
} | |
private final ActivityThread mMainThread; | |
diff -Nur android-15/android/app/DatePickerDialog.java android-16/android/app/DatePickerDialog.java | |
--- android-15/android/app/DatePickerDialog.java 2012-06-18 20:00:46.000000000 +0900 | |
+++ android-16/android/app/DatePickerDialog.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -16,17 +16,20 @@ | |
package android.app; | |
-import com.android.internal.R; | |
- | |
import android.content.Context; | |
import android.content.DialogInterface; | |
import android.content.DialogInterface.OnClickListener; | |
import android.os.Bundle; | |
+import android.text.format.DateUtils; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.widget.DatePicker; | |
import android.widget.DatePicker.OnDateChangedListener; | |
+import com.android.internal.R; | |
+ | |
+import java.util.Calendar; | |
+ | |
/** | |
* A simple dialog containing an {@link android.widget.DatePicker}. | |
* | |
@@ -42,6 +45,9 @@ | |
private final DatePicker mDatePicker; | |
private final OnDateSetListener mCallBack; | |
+ private final Calendar mCalendar; | |
+ | |
+ private boolean mTitleNeedsUpdate = true; | |
/** | |
* The callback used to indicate the user is done filling in the date. | |
@@ -91,11 +97,11 @@ | |
mCallBack = callBack; | |
+ mCalendar = Calendar.getInstance(); | |
+ | |
Context themeContext = getContext(); | |
- setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_set), this); | |
- setButton(BUTTON_NEGATIVE, themeContext.getText(R.string.cancel), (OnClickListener) null); | |
+ setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this); | |
setIcon(0); | |
- setTitle(R.string.date_picker_dialog_title); | |
LayoutInflater inflater = | |
(LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); | |
@@ -103,19 +109,17 @@ | |
setView(view); | |
mDatePicker = (DatePicker) view.findViewById(R.id.datePicker); | |
mDatePicker.init(year, monthOfYear, dayOfMonth, this); | |
+ updateTitle(year, monthOfYear, dayOfMonth); | |
} | |
public void onClick(DialogInterface dialog, int which) { | |
- if (mCallBack != null) { | |
- mDatePicker.clearFocus(); | |
- mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(), | |
- mDatePicker.getMonth(), mDatePicker.getDayOfMonth()); | |
- } | |
+ tryNotifyDateSet(); | |
} | |
public void onDateChanged(DatePicker view, int year, | |
int month, int day) { | |
- mDatePicker.init(year, month, day, null); | |
+ mDatePicker.init(year, month, day, this); | |
+ updateTitle(year, month, day); | |
} | |
/** | |
@@ -138,6 +142,42 @@ | |
mDatePicker.updateDate(year, monthOfYear, dayOfMonth); | |
} | |
+ private void tryNotifyDateSet() { | |
+ if (mCallBack != null) { | |
+ mDatePicker.clearFocus(); | |
+ mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(), | |
+ mDatePicker.getMonth(), mDatePicker.getDayOfMonth()); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ protected void onStop() { | |
+ tryNotifyDateSet(); | |
+ super.onStop(); | |
+ } | |
+ | |
+ private void updateTitle(int year, int month, int day) { | |
+ if (!mDatePicker.getCalendarViewShown()) { | |
+ mCalendar.set(Calendar.YEAR, year); | |
+ mCalendar.set(Calendar.MONTH, month); | |
+ mCalendar.set(Calendar.DAY_OF_MONTH, day); | |
+ String title = DateUtils.formatDateTime(mContext, | |
+ mCalendar.getTimeInMillis(), | |
+ DateUtils.FORMAT_SHOW_DATE | |
+ | DateUtils.FORMAT_SHOW_WEEKDAY | |
+ | DateUtils.FORMAT_SHOW_YEAR | |
+ | DateUtils.FORMAT_ABBREV_MONTH | |
+ | DateUtils.FORMAT_ABBREV_WEEKDAY); | |
+ setTitle(title); | |
+ mTitleNeedsUpdate = true; | |
+ } else { | |
+ if (mTitleNeedsUpdate) { | |
+ mTitleNeedsUpdate = false; | |
+ setTitle(R.string.date_picker_dialog_title); | |
+ } | |
+ } | |
+ } | |
+ | |
@Override | |
public Bundle onSaveInstanceState() { | |
Bundle state = super.onSaveInstanceState(); | |
diff -Nur android-15/android/app/Dialog.java android-16/android/app/Dialog.java | |
--- android-15/android/app/Dialog.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/Dialog.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -27,6 +27,7 @@ | |
import android.net.Uri; | |
import android.os.Bundle; | |
import android.os.Handler; | |
+import android.os.Looper; | |
import android.os.Message; | |
import android.util.Log; | |
import android.util.TypedValue; | |
@@ -103,7 +104,6 @@ | |
private boolean mShowing = false; | |
private boolean mCanceled = false; | |
- private final Thread mUiThread; | |
private final Handler mHandler = new Handler(); | |
private static final int DISMISS = 0x43; | |
@@ -162,7 +162,6 @@ | |
w.setCallback(this); | |
w.setWindowManager(mWindowManager, null, null); | |
w.setGravity(Gravity.CENTER); | |
- mUiThread = Thread.currentThread(); | |
mListenersHandler = new ListenersHandler(this); | |
} | |
@@ -299,11 +298,10 @@ | |
* that in {@link #onStop}. | |
*/ | |
public void dismiss() { | |
- if (Thread.currentThread() != mUiThread) { | |
- mHandler.post(mDismissAction); | |
+ if (Looper.myLooper() == mHandler.getLooper()) { | |
+ dismissDialog(); | |
} else { | |
- mHandler.removeCallbacks(mDismissAction); | |
- mDismissAction.run(); | |
+ mHandler.post(mDismissAction); | |
} | |
} | |
@@ -591,7 +589,7 @@ | |
} | |
/** | |
- * Called when an key shortcut event is not handled by any of the views in the Dialog. | |
+ * Called when a key shortcut event is not handled by any of the views in the Dialog. | |
* Override this method to implement global key shortcuts for the Dialog. | |
* Key shortcuts can also be implemented by setting the | |
* {@link MenuItem#setShortcut(char, char) shortcut} property of menu items. | |
diff -Nur android-15/android/app/DownloadManager.java android-16/android/app/DownloadManager.java | |
--- android-15/android/app/DownloadManager.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/app/DownloadManager.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -51,6 +51,9 @@ | |
* Apps that request downloads through this API should register a broadcast receiver for | |
* {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running | |
* download in a notification or from the downloads UI. | |
+ * | |
+ * Note that the application must have the {@link android.Manifest.permission#INTERNET} | |
+ * permission to use this class. | |
*/ | |
public class DownloadManager { | |
@@ -347,8 +350,9 @@ | |
private CharSequence mTitle; | |
private CharSequence mDescription; | |
private String mMimeType; | |
- private boolean mRoamingAllowed = true; | |
private int mAllowedNetworkTypes = ~0; // default to all network types allowed | |
+ private boolean mRoamingAllowed = true; | |
+ private boolean mMeteredAllowed = true; | |
private boolean mIsVisibleInDownloadsUi = true; | |
private boolean mScannable = false; | |
private boolean mUseSystemCache = false; | |
@@ -609,8 +613,11 @@ | |
} | |
/** | |
- * Restrict the types of networks over which this download may proceed. By default, all | |
- * network types are allowed. | |
+ * Restrict the types of networks over which this download may proceed. | |
+ * By default, all network types are allowed. Consider using | |
+ * {@link #setAllowedOverMetered(boolean)} instead, since it's more | |
+ * flexible. | |
+ * | |
* @param flags any combination of the NETWORK_* bit flags. | |
* @return this object | |
*/ | |
@@ -631,6 +638,17 @@ | |
} | |
/** | |
+ * Set whether this download may proceed over a metered network | |
+ * connection. By default, metered networks are allowed. | |
+ * | |
+ * @see ConnectivityManager#isActiveNetworkMetered() | |
+ */ | |
+ public Request setAllowedOverMetered(boolean allow) { | |
+ mMeteredAllowed = allow; | |
+ return this; | |
+ } | |
+ | |
+ /** | |
* Set whether this download should be displayed in the system's Downloads UI. True by | |
* default. | |
* @param isVisible whether to display this download in the Downloads UI | |
@@ -675,6 +693,7 @@ | |
values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility); | |
values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes); | |
values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed); | |
+ values.put(Downloads.Impl.COLUMN_ALLOW_METERED, mMeteredAllowed); | |
values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi); | |
return values; | |
@@ -1097,6 +1116,18 @@ | |
} | |
} | |
+ /** {@hide} */ | |
+ public static boolean isActiveNetworkExpensive(Context context) { | |
+ // TODO: connect to NetworkPolicyManager | |
+ return false; | |
+ } | |
+ | |
+ /** {@hide} */ | |
+ public static long getActiveNetworkWarningBytes(Context context) { | |
+ // TODO: connect to NetworkPolicyManager | |
+ return -1; | |
+ } | |
+ | |
/** | |
* Adds a file to the downloads database system, so it could appear in Downloads App | |
* (and thus become eligible for management by the Downloads App). | |
@@ -1127,7 +1158,7 @@ | |
validateArgumentIsNonEmpty("description", description); | |
validateArgumentIsNonEmpty("path", path); | |
validateArgumentIsNonEmpty("mimeType", mimeType); | |
- if (length <= 0) { | |
+ if (length < 0) { | |
throw new IllegalArgumentException(" invalid value for param: totalBytes"); | |
} | |
@@ -1312,9 +1343,6 @@ | |
case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR: | |
return ERROR_FILE_ALREADY_EXISTS; | |
- case Downloads.Impl.STATUS_BLOCKED: | |
- return ERROR_BLOCKED; | |
- | |
default: | |
return ERROR_UNKNOWN; | |
} | |
diff -Nur android-15/android/app/DownloadManagerBaseTest.java android-16/android/app/DownloadManagerBaseTest.java | |
--- android-15/android/app/DownloadManagerBaseTest.java 2012-06-18 20:00:44.000000000 +0900 | |
+++ android-16/android/app/DownloadManagerBaseTest.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -16,12 +16,8 @@ | |
package android.app; | |
-import coretestutils.http.MockResponse; | |
-import coretestutils.http.MockWebServer; | |
- | |
import android.app.DownloadManager.Query; | |
import android.app.DownloadManager.Request; | |
-import android.app.DownloadManagerBaseTest.DataType; | |
import android.content.BroadcastReceiver; | |
import android.content.Context; | |
import android.content.Intent; | |
@@ -39,10 +35,14 @@ | |
import android.test.InstrumentationTestCase; | |
import android.util.Log; | |
+import com.google.mockwebserver.MockResponse; | |
+import com.google.mockwebserver.MockWebServer; | |
+ | |
import java.io.DataInputStream; | |
import java.io.DataOutputStream; | |
import java.io.File; | |
import java.io.FileInputStream; | |
+import java.io.FileNotFoundException; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.net.URL; | |
@@ -53,13 +53,15 @@ | |
import java.util.Set; | |
import java.util.concurrent.TimeoutException; | |
+import libcore.io.Streams; | |
+ | |
/** | |
* Base class for Instrumented tests for the Download Manager. | |
*/ | |
public class DownloadManagerBaseTest extends InstrumentationTestCase { | |
private static final String TAG = "DownloadManagerBaseTest"; | |
protected DownloadManager mDownloadManager = null; | |
- protected MockWebServer mServer = null; | |
+ private MockWebServer mServer = null; | |
protected String mFileType = "text/plain"; | |
protected Context mContext = null; | |
protected MultipleDownloadsCompletedReceiver mReceiver = null; | |
@@ -237,63 +239,57 @@ | |
mContext = getInstrumentation().getContext(); | |
mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE); | |
mServer = new MockWebServer(); | |
+ mServer.play(); | |
mReceiver = registerNewMultipleDownloadsReceiver(); | |
// Note: callers overriding this should call mServer.play() with the desired port # | |
} | |
/** | |
- * Helper to enqueue a response from the MockWebServer with no body. | |
+ * Helper to build a response from the MockWebServer with no body. | |
* | |
* @param status The HTTP status code to return for this response | |
* @return Returns the mock web server response that was queued (which can be modified) | |
*/ | |
- protected MockResponse enqueueResponse(int status) { | |
- return doEnqueueResponse(status); | |
- | |
+ protected MockResponse buildResponse(int status) { | |
+ MockResponse response = new MockResponse().setResponseCode(status); | |
+ response.setHeader("Content-type", mFileType); | |
+ return response; | |
} | |
/** | |
- * Helper to enqueue a response from the MockWebServer. | |
+ * Helper to build a response from the MockWebServer. | |
* | |
* @param status The HTTP status code to return for this response | |
* @param body The body to return in this response | |
* @return Returns the mock web server response that was queued (which can be modified) | |
*/ | |
- protected MockResponse enqueueResponse(int status, byte[] body) { | |
- return doEnqueueResponse(status).setBody(body); | |
- | |
+ protected MockResponse buildResponse(int status, byte[] body) { | |
+ return buildResponse(status).setBody(body); | |
} | |
/** | |
- * Helper to enqueue a response from the MockWebServer. | |
+ * Helper to build a response from the MockWebServer. | |
* | |
* @param status The HTTP status code to return for this response | |
* @param bodyFile The body to return in this response | |
* @return Returns the mock web server response that was queued (which can be modified) | |
*/ | |
- protected MockResponse enqueueResponse(int status, File bodyFile) { | |
- return doEnqueueResponse(status).setBody(bodyFile); | |
+ protected MockResponse buildResponse(int status, File bodyFile) | |
+ throws FileNotFoundException, IOException { | |
+ final byte[] body = Streams.readFully(new FileInputStream(bodyFile)); | |
+ return buildResponse(status).setBody(body); | |
} | |
- /** | |
- * Helper for enqueue'ing a response from the MockWebServer. | |
- * | |
- * @param status The HTTP status code to return for this response | |
- * @return Returns the mock web server response that was queued (which can be modified) | |
- */ | |
- protected MockResponse doEnqueueResponse(int status) { | |
- MockResponse response = new MockResponse().setResponseCode(status); | |
- response.addHeader("Content-type", mFileType); | |
- mServer.enqueue(response); | |
- return response; | |
+ protected void enqueueResponse(MockResponse resp) { | |
+ mServer.enqueue(resp); | |
} | |
/** | |
* Helper to generate a random blob of bytes. | |
* | |
* @param size The size of the data to generate | |
- * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or | |
- * {@link DataType.BINARY}. | |
+ * @param type The type of data to generate: currently, one of {@link DataType#TEXT} or | |
+ * {@link DataType#BINARY}. | |
* @return The random data that is generated. | |
*/ | |
protected byte[] generateData(int size, DataType type) { | |
@@ -304,8 +300,8 @@ | |
* Helper to generate a random blob of bytes using a given RNG. | |
* | |
* @param size The size of the data to generate | |
- * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or | |
- * {@link DataType.BINARY}. | |
+ * @param type The type of data to generate: currently, one of {@link DataType#TEXT} or | |
+ * {@link DataType#BINARY}. | |
* @param rng (optional) The RNG to use; pass null to use | |
* @return The random data that is generated. | |
*/ | |
@@ -492,8 +488,6 @@ | |
assertEquals(1, cursor.getCount()); | |
assertTrue(cursor.moveToFirst()); | |
- mServer.checkForExceptions(); | |
- | |
verifyFileSize(pfd, fileSize); | |
verifyFileContents(pfd, fileData); | |
} finally { | |
@@ -928,7 +922,7 @@ | |
protected long enqueueDownloadRequest(byte[] body, int location) throws Exception { | |
// Prepare the mock server with a standard response | |
- enqueueResponse(HTTP_OK, body); | |
+ mServer.enqueue(buildResponse(HTTP_OK, body)); | |
return doEnqueue(location); | |
} | |
@@ -943,7 +937,7 @@ | |
protected long enqueueDownloadRequest(File body, int location) throws Exception { | |
// Prepare the mock server with a standard response | |
- enqueueResponse(HTTP_OK, body); | |
+ mServer.enqueue(buildResponse(HTTP_OK, body)); | |
return doEnqueue(location); | |
} | |
@@ -1035,4 +1029,4 @@ | |
assertEquals(1, mReceiver.numDownloadsCompleted()); | |
return dlRequest; | |
} | |
-} | |
\ No newline at end of file | |
+} | |
diff -Nur android-15/android/app/DownloadManagerFunctionalTest.java android-16/android/app/DownloadManagerFunctionalTest.java | |
--- android-15/android/app/DownloadManagerFunctionalTest.java 2012-06-18 20:00:44.000000000 +0900 | |
+++ android-16/android/app/DownloadManagerFunctionalTest.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -16,8 +16,6 @@ | |
package android.app; | |
-import coretestutils.http.MockResponse; | |
- | |
import android.app.DownloadManager.Query; | |
import android.app.DownloadManager.Request; | |
import android.database.Cursor; | |
@@ -26,6 +24,8 @@ | |
import android.os.ParcelFileDescriptor; | |
import android.test.suitebuilder.annotation.LargeTest; | |
+import com.google.mockwebserver.MockResponse; | |
+ | |
import java.io.File; | |
import java.util.Iterator; | |
import java.util.Set; | |
@@ -47,7 +47,6 @@ | |
public void setUp() throws Exception { | |
super.setUp(); | |
setWiFiStateOn(true); | |
- mServer.play(); | |
removeAllCurrentDownloads(); | |
} | |
@@ -132,8 +131,6 @@ | |
assertEquals(1, cursor.getCount()); | |
assertTrue(cursor.moveToFirst()); | |
- mServer.checkForExceptions(); | |
- | |
verifyFileSize(pfd, fileSize); | |
verifyFileContents(pfd, fileData); | |
int colIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME); | |
@@ -154,7 +151,7 @@ | |
byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); | |
// Prepare the mock server with a standard response | |
- enqueueResponse(HTTP_OK, blobData); | |
+ enqueueResponse(buildResponse(HTTP_OK, blobData)); | |
try { | |
Uri uri = getServerUri(DEFAULT_FILENAME); | |
@@ -193,7 +190,7 @@ | |
byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); | |
// Prepare the mock server with a standard response | |
- enqueueResponse(HTTP_OK, blobData); | |
+ enqueueResponse(buildResponse(HTTP_OK, blobData)); | |
Uri uri = getServerUri(DEFAULT_FILENAME); | |
Request request = new Request(uri); | |
@@ -224,7 +221,7 @@ | |
byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); | |
// Prepare the mock server with a standard response | |
- enqueueResponse(HTTP_OK, blobData); | |
+ enqueueResponse(buildResponse(HTTP_OK, blobData)); | |
Uri uri = getServerUri(DEFAULT_FILENAME); | |
Request request = new Request(uri); | |
@@ -251,7 +248,7 @@ | |
public void testGetDownloadIdOnNotification() throws Exception { | |
byte[] blobData = generateData(3000, DataType.TEXT); // file size = 3000 bytes | |
- MockResponse response = enqueueResponse(HTTP_OK, blobData); | |
+ enqueueResponse(buildResponse(HTTP_OK, blobData)); | |
long dlRequest = doCommonStandardEnqueue(); | |
waitForDownloadOrTimeout(dlRequest); | |
@@ -271,8 +268,9 @@ | |
// force 6 redirects | |
for (int i = 0; i < 6; ++i) { | |
- MockResponse response = enqueueResponse(HTTP_REDIRECT); | |
- response.addHeader("Location", uri.toString()); | |
+ final MockResponse resp = buildResponse(HTTP_REDIRECT); | |
+ resp.setHeader("Location", uri.toString()); | |
+ enqueueResponse(resp); | |
} | |
doErrorTest(uri, DownloadManager.ERROR_TOO_MANY_REDIRECTS); | |
} | |
@@ -283,7 +281,7 @@ | |
@LargeTest | |
public void testErrorUnhandledHttpCode() throws Exception { | |
Uri uri = getServerUri(DEFAULT_FILENAME); | |
- MockResponse response = enqueueResponse(HTTP_PARTIAL_CONTENT); | |
+ enqueueResponse(buildResponse(HTTP_PARTIAL_CONTENT)); | |
doErrorTest(uri, DownloadManager.ERROR_UNHANDLED_HTTP_CODE); | |
} | |
@@ -294,8 +292,9 @@ | |
@LargeTest | |
public void testErrorHttpDataError_invalidRedirect() throws Exception { | |
Uri uri = getServerUri(DEFAULT_FILENAME); | |
- MockResponse response = enqueueResponse(HTTP_REDIRECT); | |
- response.addHeader("Location", "://blah.blah.blah.com"); | |
+ final MockResponse resp = buildResponse(HTTP_REDIRECT); | |
+ resp.setHeader("Location", "://blah.blah.blah.com"); | |
+ enqueueResponse(resp); | |
doErrorTest(uri, DownloadManager.ERROR_HTTP_DATA_ERROR); | |
} | |
@@ -327,7 +326,7 @@ | |
public void testSetTitle() throws Exception { | |
int fileSize = 1024; | |
byte[] blobData = generateData(fileSize, DataType.BINARY); | |
- MockResponse response = enqueueResponse(HTTP_OK, blobData); | |
+ enqueueResponse(buildResponse(HTTP_OK, blobData)); | |
// An arbitrary unicode string title | |
final String title = "\u00a5123;\"\u0152\u017d \u054b \u0a07 \ucce0 \u6820\u03a8\u5c34" + | |
@@ -359,7 +358,7 @@ | |
byte[] blobData = generateData(fileSize, DataType.TEXT); | |
setWiFiStateOn(false); | |
- enqueueResponse(HTTP_OK, blobData); | |
+ enqueueResponse(buildResponse(HTTP_OK, blobData)); | |
try { | |
Uri uri = getServerUri(DEFAULT_FILENAME); | |
@@ -383,32 +382,16 @@ | |
} | |
/** | |
- * Tests when the server drops the connection after all headers (but before any data send). | |
- */ | |
- @LargeTest | |
- public void testDropConnection_headers() throws Exception { | |
- byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); | |
- | |
- MockResponse response = enqueueResponse(HTTP_OK, blobData); | |
- response.setCloseConnectionAfterHeader("content-length"); | |
- long dlRequest = doCommonStandardEnqueue(); | |
- | |
- // Download will never complete when header is dropped | |
- boolean success = waitForDownloadOrTimeoutNoThrow(dlRequest, DEFAULT_WAIT_POLL_TIME, | |
- DEFAULT_MAX_WAIT_TIME); | |
- | |
- assertFalse(success); | |
- } | |
- | |
- /** | |
* Tests that we get an error code when the server drops the connection during a download. | |
*/ | |
@LargeTest | |
public void testServerDropConnection_body() throws Exception { | |
byte[] blobData = generateData(25000, DataType.TEXT); // file size = 25000 bytes | |
- MockResponse response = enqueueResponse(HTTP_OK, blobData); | |
- response.setCloseConnectionAfterXBytes(15382); | |
+ final MockResponse resp = buildResponse(HTTP_OK, blobData); | |
+ resp.setHeader("Content-Length", "50000"); | |
+ enqueueResponse(resp); | |
+ | |
long dlRequest = doCommonStandardEnqueue(); | |
waitForDownloadOrTimeout(dlRequest); | |
diff -Nur android-15/android/app/DownloadManagerStressTest.java android-16/android/app/DownloadManagerStressTest.java | |
--- android-15/android/app/DownloadManagerStressTest.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/DownloadManagerStressTest.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -46,7 +46,6 @@ | |
public void setUp() throws Exception { | |
super.setUp(); | |
setWiFiStateOn(true); | |
- mServer.play(); | |
removeAllCurrentDownloads(); | |
} | |
@@ -85,7 +84,7 @@ | |
request.setTitle(String.format("%s--%d", DEFAULT_FILENAME + i, i)); | |
// Prepare the mock server with a standard response | |
- enqueueResponse(HTTP_OK, blobData); | |
+ enqueueResponse(buildResponse(HTTP_OK, blobData)); | |
long requestID = mDownloadManager.enqueue(request); | |
} | |
@@ -127,7 +126,7 @@ | |
try { | |
long dlRequest = doStandardEnqueue(largeFile); | |
- // wait for the download to complete | |
+ // wait for the download to complete | |
waitForDownloadOrTimeout(dlRequest); | |
ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest); | |
diff -Nur android-15/android/app/Fragment.java android-16/android/app/Fragment.java | |
--- android-15/android/app/Fragment.java 2012-06-18 20:00:44.000000000 +0900 | |
+++ android-16/android/app/Fragment.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -28,6 +28,7 @@ | |
import android.util.AndroidRuntimeException; | |
import android.util.AttributeSet; | |
import android.util.DebugUtils; | |
+import android.util.Log; | |
import android.util.SparseArray; | |
import android.view.ContextMenu; | |
import android.view.ContextMenu.ContextMenuInfo; | |
@@ -108,7 +109,9 @@ | |
mInstance.mRetainInstance = mRetainInstance; | |
mInstance.mDetached = mDetached; | |
mInstance.mFragmentManager = activity.mFragments; | |
- | |
+ if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, | |
+ "Instantiated fragment " + mInstance); | |
+ | |
return mInstance; | |
} | |
@@ -203,7 +206,7 @@ | |
* <li> {@link #onCreateView} creates and returns the view hierarchy associated | |
* with the fragment. | |
* <li> {@link #onActivityCreated} tells the fragment that its activity has | |
- * completed its own {@link Activity#onCreate Activity.onCreaate}. | |
+ * completed its own {@link Activity#onCreate Activity.onCreate()}. | |
* <li> {@link #onStart} makes the fragment visible to the user (based on its | |
* containing activity being started). | |
* <li> {@link #onResume} makes the fragment interacting with the user (based on its | |
@@ -961,27 +964,62 @@ | |
mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, true); | |
return mLoaderManager; | |
} | |
- | |
+ | |
/** | |
* Call {@link Activity#startActivity(Intent)} on the fragment's | |
* containing Activity. | |
+ * | |
+ * @param intent The intent to start. | |
*/ | |
public void startActivity(Intent intent) { | |
+ startActivity(intent, null); | |
+ } | |
+ | |
+ /** | |
+ * Call {@link Activity#startActivity(Intent, Bundle)} on the fragment's | |
+ * containing Activity. | |
+ * | |
+ * @param intent The intent to start. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. | |
+ */ | |
+ public void startActivity(Intent intent, Bundle options) { | |
if (mActivity == null) { | |
throw new IllegalStateException("Fragment " + this + " not attached to Activity"); | |
} | |
- mActivity.startActivityFromFragment(this, intent, -1); | |
+ if (options != null) { | |
+ mActivity.startActivityFromFragment(this, intent, -1, options); | |
+ } else { | |
+ // Note we want to go through this call for compatibility with | |
+ // applications that may have overridden the method. | |
+ mActivity.startActivityFromFragment(this, intent, -1); | |
+ } | |
} | |
- | |
+ | |
/** | |
* Call {@link Activity#startActivityForResult(Intent, int)} on the fragment's | |
* containing Activity. | |
*/ | |
public void startActivityForResult(Intent intent, int requestCode) { | |
+ startActivityForResult(intent, requestCode, null); | |
+ } | |
+ | |
+ /** | |
+ * Call {@link Activity#startActivityForResult(Intent, int, Bundle)} on the fragment's | |
+ * containing Activity. | |
+ */ | |
+ public void startActivityForResult(Intent intent, int requestCode, Bundle options) { | |
if (mActivity == null) { | |
throw new IllegalStateException("Fragment " + this + " not attached to Activity"); | |
} | |
- mActivity.startActivityFromFragment(this, intent, requestCode); | |
+ if (options != null) { | |
+ mActivity.startActivityFromFragment(this, intent, requestCode, options); | |
+ } else { | |
+ // Note we want to go through this call for compatibility with | |
+ // applications that may have overridden the method. | |
+ mActivity.startActivityFromFragment(this, intent, requestCode, options); | |
+ } | |
} | |
/** | |
@@ -1465,7 +1503,7 @@ | |
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { | |
writer.print(prefix); writer.print("mFragmentId=#"); | |
writer.print(Integer.toHexString(mFragmentId)); | |
- writer.print(" mContainerId#="); | |
+ writer.print(" mContainerId=#"); | |
writer.print(Integer.toHexString(mContainerId)); | |
writer.print(" mTag="); writer.println(mTag); | |
writer.print(prefix); writer.print("mState="); writer.print(mState); | |
diff -Nur android-15/android/app/FragmentManager.java android-16/android/app/FragmentManager.java | |
--- android-15/android/app/FragmentManager.java 2012-06-18 20:00:45.000000000 +0900 | |
+++ android-16/android/app/FragmentManager.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -726,11 +726,12 @@ | |
return; | |
} | |
f.mDeferStart = false; | |
- moveToState(f, mCurState, 0, 0); | |
+ moveToState(f, mCurState, 0, 0, false); | |
} | |
} | |
- void moveToState(Fragment f, int newState, int transit, int transitionStyle) { | |
+ void moveToState(Fragment f, int newState, int transit, int transitionStyle, | |
+ boolean keepActive) { | |
// Fragments that are not currently added will sit in the onCreate() state. | |
if (!f.mAdded && newState > Fragment.CREATED) { | |
newState = Fragment.CREATED; | |
@@ -757,7 +758,7 @@ | |
// animation, move to whatever the final state should be once | |
// the animation is done, and then we can proceed from there. | |
f.mAnimatingAway = null; | |
- moveToState(f, f.mStateAfterAnimating, 0, 0); | |
+ moveToState(f, f.mStateAfterAnimating, 0, 0, true); | |
} | |
switch (f.mState) { | |
case Fragment.INITIALIZING: | |
@@ -940,7 +941,7 @@ | |
if (fragment.mAnimatingAway != null) { | |
fragment.mAnimatingAway = null; | |
moveToState(fragment, fragment.mStateAfterAnimating, | |
- 0, 0); | |
+ 0, 0, false); | |
} | |
} | |
}); | |
@@ -992,11 +993,13 @@ | |
throw new SuperNotCalledException("Fragment " + f | |
+ " did not call through to super.onDetach()"); | |
} | |
- if (!f.mRetaining) { | |
- makeInactive(f); | |
- } else { | |
- f.mActivity = null; | |
- f.mFragmentManager = null; | |
+ if (!keepActive) { | |
+ if (!f.mRetaining) { | |
+ makeInactive(f); | |
+ } else { | |
+ f.mActivity = null; | |
+ f.mFragmentManager = null; | |
+ } | |
} | |
} | |
} | |
@@ -1007,7 +1010,7 @@ | |
} | |
void moveToState(Fragment f) { | |
- moveToState(f, mCurState, 0, 0); | |
+ moveToState(f, mCurState, 0, 0, false); | |
} | |
void moveToState(int newState, boolean always) { | |
@@ -1029,7 +1032,7 @@ | |
for (int i=0; i<mActive.size(); i++) { | |
Fragment f = mActive.get(i); | |
if (f != null) { | |
- moveToState(f, newState, transit, transitStyle); | |
+ moveToState(f, newState, transit, transitStyle, false); | |
if (f.mLoaderManager != null) { | |
loadersRunning |= f.mLoaderManager.hasRunningLoaders(); | |
} | |
@@ -1074,6 +1077,7 @@ | |
f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1)); | |
mActive.set(f.mIndex, f); | |
} | |
+ if (DEBUG) Log.v(TAG, "Allocated fragment index " + f); | |
} | |
void makeInactive(Fragment f) { | |
@@ -1081,7 +1085,7 @@ | |
return; | |
} | |
- if (DEBUG) Log.v(TAG, "Freeing fragment index " + f.mIndex); | |
+ if (DEBUG) Log.v(TAG, "Freeing fragment index " + f); | |
mActive.set(f.mIndex, null); | |
if (mAvailIndices == null) { | |
mAvailIndices = new ArrayList<Integer>(); | |
@@ -1114,14 +1118,16 @@ | |
if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting); | |
final boolean inactive = !fragment.isInBackStack(); | |
if (!fragment.mDetached || inactive) { | |
- mAdded.remove(fragment); | |
+ if (mAdded != null) { | |
+ mAdded.remove(fragment); | |
+ } | |
if (fragment.mHasMenu && fragment.mMenuVisible) { | |
mNeedMenuInvalidate = true; | |
} | |
fragment.mAdded = false; | |
fragment.mRemoving = true; | |
moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED, | |
- transition, transitionStyle); | |
+ transition, transitionStyle, false); | |
} | |
} | |
@@ -1183,12 +1189,14 @@ | |
fragment.mDetached = true; | |
if (fragment.mAdded) { | |
// We are not already in back stack, so need to remove the fragment. | |
- mAdded.remove(fragment); | |
+ if (mAdded != null) { | |
+ mAdded.remove(fragment); | |
+ } | |
if (fragment.mHasMenu && fragment.mMenuVisible) { | |
mNeedMenuInvalidate = true; | |
} | |
fragment.mAdded = false; | |
- moveToState(fragment, Fragment.CREATED, transition, transitionStyle); | |
+ moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false); | |
} | |
} | |
} | |
@@ -1198,18 +1206,21 @@ | |
if (fragment.mDetached) { | |
fragment.mDetached = false; | |
if (!fragment.mAdded) { | |
+ if (mAdded == null) { | |
+ mAdded = new ArrayList<Fragment>(); | |
+ } | |
mAdded.add(fragment); | |
fragment.mAdded = true; | |
if (fragment.mHasMenu && fragment.mMenuVisible) { | |
mNeedMenuInvalidate = true; | |
} | |
- moveToState(fragment, mCurState, transition, transitionStyle); | |
+ moveToState(fragment, mCurState, transition, transitionStyle, false); | |
} | |
} | |
} | |
public Fragment findFragmentById(int id) { | |
- if (mActive != null) { | |
+ if (mAdded != null) { | |
// First look through added fragments. | |
for (int i=mAdded.size()-1; i>=0; i--) { | |
Fragment f = mAdded.get(i); | |
@@ -1217,6 +1228,8 @@ | |
return f; | |
} | |
} | |
+ } | |
+ if (mActive != null) { | |
// Now for any known fragment. | |
for (int i=mActive.size()-1; i>=0; i--) { | |
Fragment f = mActive.get(i); | |
@@ -1229,7 +1242,7 @@ | |
} | |
public Fragment findFragmentByTag(String tag) { | |
- if (mActive != null && tag != null) { | |
+ if (mAdded != null && tag != null) { | |
// First look through added fragments. | |
for (int i=mAdded.size()-1; i>=0; i--) { | |
Fragment f = mAdded.get(i); | |
@@ -1237,6 +1250,8 @@ | |
return f; | |
} | |
} | |
+ } | |
+ if (mActive != null && tag != null) { | |
// Now for any known fragment. | |
for (int i=mActive.size()-1; i>=0; i--) { | |
Fragment f = mActive.get(i); | |
@@ -1493,6 +1508,7 @@ | |
fragments.add(f); | |
f.mRetaining = true; | |
f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1; | |
+ if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f); | |
} | |
} | |
} | |
@@ -1538,6 +1554,9 @@ | |
FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState); | |
} | |
if (!f.mUserVisibleHint) { | |
+ if (result == null) { | |
+ result = new Bundle(); | |
+ } | |
// Only add this if it's not the default value | |
result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint); | |
} | |
@@ -1563,8 +1582,17 @@ | |
for (int i=0; i<N; i++) { | |
Fragment f = mActive.get(i); | |
if (f != null) { | |
+ if (f.mIndex < 0) { | |
+ String msg = "Failure saving state: active " + f | |
+ + " has cleared index: " + f.mIndex; | |
+ Slog.e(TAG, msg); | |
+ dump(" ", null, new PrintWriter(new LogWriter( | |
+ Log.ERROR, TAG, Log.LOG_ID_SYSTEM)), new String[] { }); | |
+ throw new IllegalStateException(msg); | |
+ } | |
+ | |
haveFragments = true; | |
- | |
+ | |
FragmentState fs = new FragmentState(f); | |
active[i] = fs; | |
@@ -1616,6 +1644,14 @@ | |
added = new int[N]; | |
for (int i=0; i<N; i++) { | |
added[i] = mAdded.get(i).mIndex; | |
+ if (added[i] < 0) { | |
+ String msg = "Failure saving state: active " + mAdded.get(i) | |
+ + " has cleared index: " + added[i]; | |
+ Slog.e(TAG, msg); | |
+ dump(" ", null, new PrintWriter(new LogWriter( | |
+ Log.ERROR, TAG, Log.LOG_ID_SYSTEM)), new String[] { }); | |
+ throw new IllegalStateException(msg); | |
+ } | |
if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i | |
+ ": " + mAdded.get(i)); | |
} | |
@@ -1792,7 +1828,7 @@ | |
} | |
public void dispatchConfigurationChanged(Configuration newConfig) { | |
- if (mActive != null) { | |
+ if (mAdded != null) { | |
for (int i=0; i<mAdded.size(); i++) { | |
Fragment f = mAdded.get(i); | |
if (f != null) { | |
@@ -1803,7 +1839,7 @@ | |
} | |
public void dispatchLowMemory() { | |
- if (mActive != null) { | |
+ if (mAdded != null) { | |
for (int i=0; i<mAdded.size(); i++) { | |
Fragment f = mAdded.get(i); | |
if (f != null) { | |
@@ -1814,7 +1850,7 @@ | |
} | |
public void dispatchTrimMemory(int level) { | |
- if (mActive != null) { | |
+ if (mAdded != null) { | |
for (int i=0; i<mAdded.size(); i++) { | |
Fragment f = mAdded.get(i); | |
if (f != null) { | |
@@ -1827,7 +1863,7 @@ | |
public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { | |
boolean show = false; | |
ArrayList<Fragment> newMenus = null; | |
- if (mActive != null) { | |
+ if (mAdded != null) { | |
for (int i=0; i<mAdded.size(); i++) { | |
Fragment f = mAdded.get(i); | |
if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) { | |
@@ -1857,7 +1893,7 @@ | |
public boolean dispatchPrepareOptionsMenu(Menu menu) { | |
boolean show = false; | |
- if (mActive != null) { | |
+ if (mAdded != null) { | |
for (int i=0; i<mAdded.size(); i++) { | |
Fragment f = mAdded.get(i); | |
if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) { | |
@@ -1870,7 +1906,7 @@ | |
} | |
public boolean dispatchOptionsItemSelected(MenuItem item) { | |
- if (mActive != null) { | |
+ if (mAdded != null) { | |
for (int i=0; i<mAdded.size(); i++) { | |
Fragment f = mAdded.get(i); | |
if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) { | |
@@ -1884,10 +1920,10 @@ | |
} | |
public boolean dispatchContextItemSelected(MenuItem item) { | |
- if (mActive != null) { | |
+ if (mAdded != null) { | |
for (int i=0; i<mAdded.size(); i++) { | |
Fragment f = mAdded.get(i); | |
- if (f != null && !f.mHidden) { | |
+ if (f != null && !f.mHidden && f.mUserVisibleHint) { | |
if (f.onContextItemSelected(item)) { | |
return true; | |
} | |
@@ -1898,7 +1934,7 @@ | |
} | |
public void dispatchOptionsMenuClosed(Menu menu) { | |
- if (mActive != null) { | |
+ if (mAdded != null) { | |
for (int i=0; i<mAdded.size(); i++) { | |
Fragment f = mAdded.get(i); | |
if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) { | |
diff -Nur android-15/android/app/IActivityManager.java android-16/android/app/IActivityManager.java | |
--- android-15/android/app/IActivityManager.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/app/IActivityManager.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -28,6 +28,7 @@ | |
import android.content.pm.ConfigurationInfo; | |
import android.content.pm.IPackageDataObserver; | |
import android.content.pm.ProviderInfo; | |
+import android.content.pm.UserInfo; | |
import android.content.res.Configuration; | |
import android.graphics.Bitmap; | |
import android.net.Uri; | |
@@ -50,72 +51,38 @@ | |
* {@hide} | |
*/ | |
public interface IActivityManager extends IInterface { | |
- /** | |
- * Returned by startActivity() if the start request was canceled because | |
- * app switches are temporarily canceled to ensure the user's last request | |
- * (such as pressing home) is performed. | |
- */ | |
- public static final int START_SWITCHES_CANCELED = 4; | |
- /** | |
- * Returned by startActivity() if an activity wasn't really started, but | |
- * the given Intent was given to the existing top activity. | |
- */ | |
- public static final int START_DELIVERED_TO_TOP = 3; | |
- /** | |
- * Returned by startActivity() if an activity wasn't really started, but | |
- * a task was simply brought to the foreground. | |
- */ | |
- public static final int START_TASK_TO_FRONT = 2; | |
- /** | |
- * Returned by startActivity() if the caller asked that the Intent not | |
- * be executed if it is the recipient, and that is indeed the case. | |
- */ | |
- public static final int START_RETURN_INTENT_TO_CALLER = 1; | |
- /** | |
- * Activity was started successfully as normal. | |
- */ | |
- public static final int START_SUCCESS = 0; | |
- public static final int START_INTENT_NOT_RESOLVED = -1; | |
- public static final int START_CLASS_NOT_FOUND = -2; | |
- public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3; | |
- public static final int START_PERMISSION_DENIED = -4; | |
- public static final int START_NOT_ACTIVITY = -5; | |
- public static final int START_CANCELED = -6; | |
public int startActivity(IApplicationThread caller, | |
- Intent intent, String resolvedType, Uri[] grantedUriPermissions, | |
- int grantedMode, IBinder resultTo, String resultWho, int requestCode, | |
- boolean onlyIfNeeded, boolean debug, String profileFile, | |
- ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException; | |
+ Intent intent, String resolvedType, IBinder resultTo, String resultWho, | |
+ int requestCode, int flags, String profileFile, | |
+ ParcelFileDescriptor profileFd, Bundle options) throws RemoteException; | |
public WaitResult startActivityAndWait(IApplicationThread caller, | |
- Intent intent, String resolvedType, Uri[] grantedUriPermissions, | |
- int grantedMode, IBinder resultTo, String resultWho, int requestCode, | |
- boolean onlyIfNeeded, boolean debug, String profileFile, | |
- ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException; | |
+ Intent intent, String resolvedType, IBinder resultTo, String resultWho, | |
+ int requestCode, int flags, String profileFile, | |
+ ParcelFileDescriptor profileFd, Bundle options) throws RemoteException; | |
public int startActivityWithConfig(IApplicationThread caller, | |
- Intent intent, String resolvedType, Uri[] grantedUriPermissions, | |
- int grantedMode, IBinder resultTo, String resultWho, int requestCode, | |
- boolean onlyIfNeeded, boolean debug, Configuration newConfig) throws RemoteException; | |
+ Intent intent, String resolvedType, IBinder resultTo, String resultWho, | |
+ int requestCode, int startFlags, Configuration newConfig, | |
+ Bundle options) throws RemoteException; | |
public int startActivityIntentSender(IApplicationThread caller, | |
IntentSender intent, Intent fillInIntent, String resolvedType, | |
IBinder resultTo, String resultWho, int requestCode, | |
- int flagsMask, int flagsValues) throws RemoteException; | |
+ int flagsMask, int flagsValues, Bundle options) throws RemoteException; | |
public boolean startNextMatchingActivity(IBinder callingActivity, | |
- Intent intent) throws RemoteException; | |
+ Intent intent, Bundle options) throws RemoteException; | |
public boolean finishActivity(IBinder token, int code, Intent data) | |
throws RemoteException; | |
public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException; | |
+ public boolean finishActivityAffinity(IBinder token) throws RemoteException; | |
public boolean willActivityBeVisible(IBinder token) throws RemoteException; | |
public Intent registerReceiver(IApplicationThread caller, String callerPackage, | |
IIntentReceiver receiver, IntentFilter filter, | |
String requiredPermission) throws RemoteException; | |
public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException; | |
- public static final int BROADCAST_SUCCESS = 0; | |
- public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1; | |
public int broadcastIntent(IApplicationThread caller, Intent intent, | |
String resolvedType, IIntentReceiver resultTo, int resultCode, | |
String resultData, Bundle map, String requiredPermission, | |
- boolean serialized, boolean sticky) throws RemoteException; | |
- public void unbroadcastIntent(IApplicationThread caller, Intent intent) throws RemoteException; | |
+ boolean serialized, boolean sticky, int userId) throws RemoteException; | |
+ public void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) throws RemoteException; | |
/* oneway */ | |
public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException; | |
public void attachApplication(IApplicationThread app) throws RemoteException; | |
@@ -140,21 +107,25 @@ | |
public List getServices(int maxNum, int flags) throws RemoteException; | |
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() | |
throws RemoteException; | |
- public void moveTaskToFront(int task, int flags) throws RemoteException; | |
+ public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException; | |
public void moveTaskToBack(int task) throws RemoteException; | |
public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException; | |
public void moveTaskBackwards(int task) throws RemoteException; | |
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException; | |
- public void finishOtherInstances(IBinder token, ComponentName className) throws RemoteException; | |
/* oneway */ | |
public void reportThumbnail(IBinder token, | |
Bitmap thumbnail, CharSequence description) throws RemoteException; | |
public ContentProviderHolder getContentProvider(IApplicationThread caller, | |
- String name) throws RemoteException; | |
- public void removeContentProvider(IApplicationThread caller, | |
- String name) throws RemoteException; | |
+ String name, boolean stable) throws RemoteException; | |
+ public ContentProviderHolder getContentProviderExternal(String name, IBinder token) | |
+ throws RemoteException; | |
+ public void removeContentProvider(IBinder connection, boolean stable) throws RemoteException; | |
+ public void removeContentProviderExternal(String name, IBinder token) throws RemoteException; | |
public void publishContentProviders(IApplicationThread caller, | |
List<ContentProviderHolder> providers) throws RemoteException; | |
+ public boolean refContentProvider(IBinder connection, int stableDelta, int unstableDelta) | |
+ throws RemoteException; | |
+ public void unstableProviderDied(IBinder connection) throws RemoteException; | |
public PendingIntent getRunningServiceControlPanel(ComponentName service) | |
throws RemoteException; | |
public ComponentName startService(IApplicationThread caller, Intent service, | |
@@ -167,7 +138,7 @@ | |
int id, Notification notification, boolean keepNotification) throws RemoteException; | |
public int bindService(IApplicationThread caller, IBinder token, | |
Intent service, String resolvedType, | |
- IServiceConnection connection, int flags) throws RemoteException; | |
+ IServiceConnection connection, int flags, int userId) throws RemoteException; | |
public boolean unbindService(IServiceConnection connection) throws RemoteException; | |
public void publishService(IBinder token, | |
Intent intent, IBinder service) throws RemoteException; | |
@@ -199,18 +170,15 @@ | |
public ComponentName getActivityClassForToken(IBinder token) throws RemoteException; | |
public String getPackageForToken(IBinder token) throws RemoteException; | |
- public static final int INTENT_SENDER_BROADCAST = 1; | |
- public static final int INTENT_SENDER_ACTIVITY = 2; | |
- public static final int INTENT_SENDER_ACTIVITY_RESULT = 3; | |
- public static final int INTENT_SENDER_SERVICE = 4; | |
public IIntentSender getIntentSender(int type, | |
String packageName, IBinder token, String resultWho, | |
int requestCode, Intent[] intents, String[] resolvedTypes, | |
- int flags) throws RemoteException; | |
+ int flags, Bundle options) throws RemoteException; | |
public void cancelIntentSender(IIntentSender sender) throws RemoteException; | |
public boolean clearApplicationUserData(final String packageName, | |
- final IPackageDataObserver observer) throws RemoteException; | |
+ final IPackageDataObserver observer, int userId) throws RemoteException; | |
public String getPackageForIntentSender(IIntentSender sender) throws RemoteException; | |
+ public int getUidForIntentSender(IIntentSender sender) throws RemoteException; | |
public void setProcessLimit(int max) throws RemoteException; | |
public int getProcessLimit() throws RemoteException; | |
@@ -240,7 +208,8 @@ | |
// Note: probably don't want to allow applications access to these. | |
public void goingToSleep() throws RemoteException; | |
public void wakingUp() throws RemoteException; | |
- | |
+ public void setLockScreenShown(boolean shown) throws RemoteException; | |
+ | |
public void unhandledBack() throws RemoteException; | |
public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException; | |
public void setDebugApp( | |
@@ -253,9 +222,10 @@ | |
public void enterSafeMode() throws RemoteException; | |
public void noteWakeupAlarm(IIntentSender sender) throws RemoteException; | |
- | |
+ | |
public boolean killPids(int[] pids, String reason, boolean secure) throws RemoteException; | |
- | |
+ public boolean killProcessesBelowForeground(String reason) throws RemoteException; | |
+ | |
// Special low-level communication with activity manager. | |
public void startRunning(String pkg, String cls, String action, | |
String data) throws RemoteException; | |
@@ -276,13 +246,16 @@ | |
* SIGUSR1 is delivered. All others are ignored. | |
*/ | |
public void signalPersistentProcesses(int signal) throws RemoteException; | |
- // Retrieve info of applications installed on external media that are currently | |
- // running. | |
+ // Retrieve running application processes in the system | |
public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() | |
throws RemoteException; | |
- // Retrieve running application processes in the system | |
+ // Retrieve info of applications installed on external media that are currently | |
+ // running. | |
public List<ApplicationInfo> getRunningExternalApplications() | |
throws RemoteException; | |
+ // Get memory information about the calling process. | |
+ public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outInfo) | |
+ throws RemoteException; | |
// Get device configuration | |
public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException; | |
@@ -295,14 +268,9 @@ | |
public void stopAppSwitches() throws RemoteException; | |
public void resumeAppSwitches() throws RemoteException; | |
- public void registerActivityWatcher(IActivityWatcher watcher) | |
- throws RemoteException; | |
- public void unregisterActivityWatcher(IActivityWatcher watcher) | |
- throws RemoteException; | |
- | |
public int startActivityInPackage(int uid, | |
Intent intent, String resolvedType, IBinder resultTo, | |
- String resultWho, int requestCode, boolean onlyIfNeeded) | |
+ String resultWho, int requestCode, int startFlags, Bundle options) | |
throws RemoteException; | |
public void killApplicationWithUid(String pkg, int uid) throws RemoteException; | |
@@ -342,9 +310,11 @@ | |
ParcelFileDescriptor fd) throws RemoteException; | |
public int startActivities(IApplicationThread caller, | |
- Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException; | |
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo, | |
+ Bundle options) throws RemoteException; | |
public int startActivitiesInPackage(int uid, | |
- Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException; | |
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo, | |
+ Bundle options) throws RemoteException; | |
public int getFrontActivityScreenCompatMode() throws RemoteException; | |
public void setFrontActivityScreenCompatMode(int mode) throws RemoteException; | |
@@ -354,10 +324,11 @@ | |
public boolean getPackageAskScreenCompat(String packageName) throws RemoteException; | |
public void setPackageAskScreenCompat(String packageName, boolean ask) | |
throws RemoteException; | |
- | |
+ | |
// Multi-user APIs | |
public boolean switchUser(int userid) throws RemoteException; | |
- | |
+ public UserInfo getCurrentUser() throws RemoteException; | |
+ | |
public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException; | |
public boolean removeTask(int taskId, int flags) throws RemoteException; | |
@@ -375,6 +346,16 @@ | |
public void dismissKeyguardOnNextActivity() throws RemoteException; | |
+ public boolean targetTaskAffinityMatchesActivity(IBinder token, String destAffinity) | |
+ throws RemoteException; | |
+ | |
+ public boolean navigateUpTo(IBinder token, Intent target, int resultCode, Intent resultData) | |
+ throws RemoteException; | |
+ | |
+ // This is not public because you need to be very careful in how you | |
+ // manage your activity to make sure it is always the uid you expect. | |
+ public int getLaunchedFromUid(IBinder activityToken) throws RemoteException; | |
+ | |
/* | |
* Private non-Binder interfaces | |
*/ | |
@@ -384,6 +365,7 @@ | |
public static class ContentProviderHolder implements Parcelable { | |
public final ProviderInfo info; | |
public IContentProvider provider; | |
+ public IBinder connection; | |
public boolean noReleaseNeeded; | |
public ContentProviderHolder(ProviderInfo _info) { | |
@@ -401,6 +383,7 @@ | |
} else { | |
dest.writeStrongBinder(null); | |
} | |
+ dest.writeStrongBinder(connection); | |
dest.writeInt(noReleaseNeeded ? 1:0); | |
} | |
@@ -419,9 +402,10 @@ | |
info = ProviderInfo.CREATOR.createFromParcel(source); | |
provider = ContentProviderNative.asInterface( | |
source.readStrongBinder()); | |
+ connection = source.readStrongBinder(); | |
noReleaseNeeded = source.readInt() != 0; | |
} | |
- }; | |
+ } | |
/** Information returned after waiting for an activity start. */ | |
public static class WaitResult implements Parcelable { | |
@@ -497,7 +481,7 @@ | |
int REPORT_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27; | |
int GET_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28; | |
int PUBLISH_CONTENT_PROVIDERS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29; | |
- | |
+ int REF_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30; | |
int FINISH_SUB_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31; | |
int GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32; | |
int START_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33; | |
@@ -505,7 +489,7 @@ | |
int BIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35; | |
int UNBIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+36; | |
int PUBLISH_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+37; | |
- int FINISH_OTHER_INSTANCES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+38; | |
+ | |
int GOING_TO_SLEEP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+39; | |
int WAKING_UP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+40; | |
int SET_DEBUG_APP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+41; | |
@@ -559,8 +543,9 @@ | |
int START_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+89; | |
int BACKUP_AGENT_CREATED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+90; | |
int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91; | |
- int REGISTER_ACTIVITY_WATCHER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+92; | |
- int UNREGISTER_ACTIVITY_WATCHER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+93; | |
+ int GET_UID_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+92; | |
+ | |
+ | |
int START_ACTIVITY_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94; | |
int KILL_APPLICATION_WITH_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+95; | |
int CLOSE_SYSTEM_DIALOGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+96; | |
@@ -607,4 +592,15 @@ | |
int SHOW_BOOT_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+137; | |
int DISMISS_KEYGUARD_ON_NEXT_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+138; | |
int KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+139; | |
+ int GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+140; | |
+ int REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+141; | |
+ int GET_MY_MEMORY_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+142; | |
+ int KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+143; | |
+ int GET_CURRENT_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+144; | |
+ int TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+145; | |
+ int NAVIGATE_UP_TO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+146; | |
+ int SET_LOCK_SCREEN_SHOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+147; | |
+ int FINISH_ACTIVITY_AFFINITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+148; | |
+ int GET_LAUNCHED_FROM_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+149; | |
+ int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+150; | |
} | |
diff -Nur android-15/android/app/IApplicationThread.java android-16/android/app/IApplicationThread.java | |
--- android-15/android/app/IApplicationThread.java 2012-06-18 20:00:45.000000000 +0900 | |
+++ android-16/android/app/IApplicationThread.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -89,7 +89,7 @@ | |
void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers, | |
ComponentName testName, String profileName, ParcelFileDescriptor profileFd, | |
boolean autoStopProfiler, Bundle testArguments, IInstrumentationWatcher testWatcher, | |
- int debugMode, boolean restrictedBackupMode, boolean persistent, | |
+ int debugMode, boolean openGlTrace, boolean restrictedBackupMode, boolean persistent, | |
Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services, | |
Bundle coreSettings) throws RemoteException; | |
void scheduleExit() throws RemoteException; | |
@@ -102,6 +102,8 @@ | |
void processInBackground() throws RemoteException; | |
void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) | |
throws RemoteException; | |
+ void dumpProvider(FileDescriptor fd, IBinder servicetoken, String[] args) | |
+ throws RemoteException; | |
void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, | |
int resultCode, String data, Bundle extras, boolean ordered, boolean sticky) | |
throws RemoteException; | |
@@ -125,6 +127,8 @@ | |
Debug.MemoryInfo dumpMemInfo(FileDescriptor fd, boolean checkin, boolean all, | |
String[] args) throws RemoteException; | |
void dumpGfxInfo(FileDescriptor fd, String[] args) throws RemoteException; | |
+ void dumpDbInfo(FileDescriptor fd, String[] args) throws RemoteException; | |
+ void unstableProviderDied(IBinder provider) throws RemoteException; | |
String descriptor = "android.app.IApplicationThread"; | |
@@ -171,4 +175,7 @@ | |
int SCHEDULE_TRIM_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+41; | |
int DUMP_MEM_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+42; | |
int DUMP_GFX_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+43; | |
+ int DUMP_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44; | |
+ int DUMP_DB_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+45; | |
+ int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+46; | |
} | |
diff -Nur android-15/android/app/Instrumentation.java android-16/android/app/Instrumentation.java | |
--- android-15/android/app/Instrumentation.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/Instrumentation.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -23,24 +23,25 @@ | |
import android.content.IntentFilter; | |
import android.content.pm.ActivityInfo; | |
import android.content.res.Configuration; | |
+import android.hardware.input.InputManager; | |
import android.os.Bundle; | |
-import android.os.PerformanceCollector; | |
-import android.os.RemoteException; | |
import android.os.Debug; | |
import android.os.IBinder; | |
import android.os.MessageQueue; | |
+import android.os.PerformanceCollector; | |
import android.os.Process; | |
-import android.os.SystemClock; | |
+import android.os.RemoteException; | |
import android.os.ServiceManager; | |
+import android.os.SystemClock; | |
import android.util.AndroidRuntimeException; | |
import android.util.Log; | |
import android.view.IWindowManager; | |
+import android.view.InputDevice; | |
import android.view.KeyCharacterMap; | |
import android.view.KeyEvent; | |
import android.view.MotionEvent; | |
import android.view.ViewConfiguration; | |
import android.view.Window; | |
-import android.view.inputmethod.InputMethodManager; | |
import java.io.File; | |
import java.util.ArrayList; | |
@@ -834,16 +835,21 @@ | |
return; | |
} | |
KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); | |
- | |
+ | |
KeyEvent[] events = keyCharacterMap.getEvents(text.toCharArray()); | |
- | |
+ | |
if (events != null) { | |
for (int i = 0; i < events.length; i++) { | |
- sendKeySync(events[i]); | |
+ // We have to change the time of an event before injecting it because | |
+ // all KeyEvents returned by KeyCharacterMap.getEvents() have the same | |
+ // time stamp and the system rejects too old events. Hence, it is | |
+ // possible for an event to become stale before it is injected if it | |
+ // takes too long to inject the preceding ones. | |
+ sendKeySync(KeyEvent.changeTimeRepeat(events[i], SystemClock.uptimeMillis(), 0)); | |
} | |
- } | |
+ } | |
} | |
- | |
+ | |
/** | |
* Send a key event to the currently focused window/view and wait for it to | |
* be processed. Finished at some point after the recipient has returned | |
@@ -855,11 +861,30 @@ | |
*/ | |
public void sendKeySync(KeyEvent event) { | |
validateNotAppThread(); | |
- try { | |
- (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))) | |
- .injectKeyEvent(event, true); | |
- } catch (RemoteException e) { | |
- } | |
+ | |
+ long downTime = event.getDownTime(); | |
+ long eventTime = event.getEventTime(); | |
+ int action = event.getAction(); | |
+ int code = event.getKeyCode(); | |
+ int repeatCount = event.getRepeatCount(); | |
+ int metaState = event.getMetaState(); | |
+ int deviceId = event.getDeviceId(); | |
+ int scancode = event.getScanCode(); | |
+ int source = event.getSource(); | |
+ int flags = event.getFlags(); | |
+ if (source == InputDevice.SOURCE_UNKNOWN) { | |
+ source = InputDevice.SOURCE_KEYBOARD; | |
+ } | |
+ if (eventTime == 0) { | |
+ eventTime = SystemClock.uptimeMillis(); | |
+ } | |
+ if (downTime == 0) { | |
+ downTime = eventTime; | |
+ } | |
+ KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState, | |
+ deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM, source); | |
+ InputManager.getInstance().injectInputEvent(newEvent, | |
+ InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); | |
} | |
/** | |
@@ -898,11 +923,11 @@ | |
*/ | |
public void sendPointerSync(MotionEvent event) { | |
validateNotAppThread(); | |
- try { | |
- (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))) | |
- .injectPointerEvent(event, true); | |
- } catch (RemoteException e) { | |
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { | |
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN); | |
} | |
+ InputManager.getInstance().injectInputEvent(event, | |
+ InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); | |
} | |
/** | |
@@ -918,11 +943,11 @@ | |
*/ | |
public void sendTrackballEventSync(MotionEvent event) { | |
validateNotAppThread(); | |
- try { | |
- (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))) | |
- .injectTrackballEvent(event, true); | |
- } catch (RemoteException e) { | |
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) { | |
+ event.setSource(InputDevice.SOURCE_TRACKBALL); | |
} | |
+ InputManager.getInstance().injectInputEvent(event, | |
+ InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); | |
} | |
/** | |
@@ -962,7 +987,12 @@ | |
/** | |
* Perform calling of the application's {@link Application#onCreate} | |
* method. The default implementation simply calls through to that method. | |
- * | |
+ * | |
+ * <p>Note: This method will be called immediately after {@link #onCreate(Bundle)}. | |
+ * Often instrumentation tests start their test thread in onCreate(); you | |
+ * need to be careful of races between these. (Well between it and | |
+ * everything else, but let's start here.) | |
+ * | |
* @param app The application being created. | |
*/ | |
public void callApplicationOnCreate(Application app) { | |
@@ -1342,6 +1372,7 @@ | |
* @param intent The actual Intent to start. | |
* @param requestCode Identifier for this request's result; less than zero | |
* if the caller is not expecting a result. | |
+ * @param options Addition options. | |
* | |
* @return To force the return of a particular result, return an | |
* ActivityResult object containing the desired data; otherwise | |
@@ -1357,7 +1388,7 @@ | |
*/ | |
public ActivityResult execStartActivity( | |
Context who, IBinder contextThread, IBinder token, Activity target, | |
- Intent intent, int requestCode) { | |
+ Intent intent, int requestCode, Bundle options) { | |
IApplicationThread whoThread = (IApplicationThread) contextThread; | |
if (mActivityMonitors != null) { | |
synchronized (mSync) { | |
@@ -1376,11 +1407,12 @@ | |
} | |
try { | |
intent.setAllowFds(false); | |
+ intent.migrateExtraStreamToClipData(); | |
int result = ActivityManagerNative.getDefault() | |
.startActivity(whoThread, intent, | |
intent.resolveTypeIfNeeded(who.getContentResolver()), | |
- null, 0, token, target != null ? target.mEmbeddedID : null, | |
- requestCode, false, false, null, null, false); | |
+ token, target != null ? target.mEmbeddedID : null, | |
+ requestCode, 0, null, null, options); | |
checkStartActivityResult(result, intent); | |
} catch (RemoteException e) { | |
} | |
@@ -1396,7 +1428,7 @@ | |
* {@hide} | |
*/ | |
public void execStartActivities(Context who, IBinder contextThread, | |
- IBinder token, Activity target, Intent[] intents) { | |
+ IBinder token, Activity target, Intent[] intents, Bundle options) { | |
IApplicationThread whoThread = (IApplicationThread) contextThread; | |
if (mActivityMonitors != null) { | |
synchronized (mSync) { | |
@@ -1420,7 +1452,7 @@ | |
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver()); | |
} | |
int result = ActivityManagerNative.getDefault() | |
- .startActivities(whoThread, intents, resolvedTypes, token); | |
+ .startActivities(whoThread, intents, resolvedTypes, token, options); | |
checkStartActivityResult(result, intents[0]); | |
} catch (RemoteException e) { | |
} | |
@@ -1455,7 +1487,7 @@ | |
*/ | |
public ActivityResult execStartActivity( | |
Context who, IBinder contextThread, IBinder token, Fragment target, | |
- Intent intent, int requestCode) { | |
+ Intent intent, int requestCode, Bundle options) { | |
IApplicationThread whoThread = (IApplicationThread) contextThread; | |
if (mActivityMonitors != null) { | |
synchronized (mSync) { | |
@@ -1474,11 +1506,12 @@ | |
} | |
try { | |
intent.setAllowFds(false); | |
+ intent.migrateExtraStreamToClipData(); | |
int result = ActivityManagerNative.getDefault() | |
.startActivity(whoThread, intent, | |
intent.resolveTypeIfNeeded(who.getContentResolver()), | |
- null, 0, token, target != null ? target.mWho : null, | |
- requestCode, false, false, null, null, false); | |
+ token, target != null ? target.mWho : null, | |
+ requestCode, 0, null, null, options); | |
checkStartActivityResult(result, intent); | |
} catch (RemoteException e) { | |
} | |
@@ -1497,13 +1530,13 @@ | |
} | |
/*package*/ static void checkStartActivityResult(int res, Object intent) { | |
- if (res >= IActivityManager.START_SUCCESS) { | |
+ if (res >= ActivityManager.START_SUCCESS) { | |
return; | |
} | |
switch (res) { | |
- case IActivityManager.START_INTENT_NOT_RESOLVED: | |
- case IActivityManager.START_CLASS_NOT_FOUND: | |
+ case ActivityManager.START_INTENT_NOT_RESOLVED: | |
+ case ActivityManager.START_CLASS_NOT_FOUND: | |
if (intent instanceof Intent && ((Intent)intent).getComponent() != null) | |
throw new ActivityNotFoundException( | |
"Unable to find explicit activity class " | |
@@ -1511,13 +1544,13 @@ | |
+ "; have you declared this activity in your AndroidManifest.xml?"); | |
throw new ActivityNotFoundException( | |
"No Activity found to handle " + intent); | |
- case IActivityManager.START_PERMISSION_DENIED: | |
+ case ActivityManager.START_PERMISSION_DENIED: | |
throw new SecurityException("Not allowed to start activity " | |
+ intent); | |
- case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: | |
+ case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: | |
throw new AndroidRuntimeException( | |
"FORWARD_RESULT_FLAG used while also requesting a result"); | |
- case IActivityManager.START_NOT_ACTIVITY: | |
+ case ActivityManager.START_NOT_ACTIVITY: | |
throw new IllegalArgumentException( | |
"PendingIntent is not an activity"); | |
default: | |
diff -Nur android-15/android/app/KeyguardManager.java android-16/android/app/KeyguardManager.java | |
--- android-15/android/app/KeyguardManager.java 2012-06-18 20:00:46.000000000 +0900 | |
+++ android-16/android/app/KeyguardManager.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -28,7 +28,7 @@ | |
* Class that can be used to lock and unlock the keyboard. Get an instance of this | |
* class by calling {@link android.content.Context#getSystemService(java.lang.String)} | |
* with argument {@link android.content.Context#KEYGUARD_SERVICE}. The | |
- * Actual class to control the keyboard locking is | |
+ * actual class to control the keyboard locking is | |
* {@link android.app.KeyguardManager.KeyguardLock}. | |
*/ | |
public class KeyguardManager { | |
@@ -62,6 +62,9 @@ | |
* Note: This call has no effect while any {@link android.app.admin.DevicePolicyManager} | |
* is enabled that requires a password. | |
* | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#DISABLE_KEYGUARD}. | |
+ * | |
* @see #reenableKeyguard() | |
*/ | |
public void disableKeyguard() { | |
@@ -73,13 +76,16 @@ | |
/** | |
* Reenable the keyguard. The keyguard will reappear if the previous | |
- * call to {@link #disableKeyguard()} caused it it to be hidden. | |
+ * call to {@link #disableKeyguard()} caused it to be hidden. | |
* | |
* A good place to call this is from {@link android.app.Activity#onPause()} | |
* | |
* Note: This call has no effect while any {@link android.app.admin.DevicePolicyManager} | |
* is enabled that requires a password. | |
* | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#DISABLE_KEYGUARD}. | |
+ * | |
* @see #disableKeyguard() | |
*/ | |
public void reenableKeyguard() { | |
@@ -130,13 +136,9 @@ | |
} | |
/** | |
- * isKeyguardLocked | |
- * | |
* Return whether the keyguard is currently locked. | |
* | |
- * @return true if in keyguard is locked. | |
- * | |
- * @hide | |
+ * @return true if keyguard is locked. | |
*/ | |
public boolean isKeyguardLocked() { | |
try { | |
@@ -147,13 +149,9 @@ | |
} | |
/** | |
- * isKeyguardSecure | |
- * | |
* Return whether the keyguard requires a password to unlock. | |
* | |
- * @return true if in keyguard is secure. | |
- * | |
- * @hide | |
+ * @return true if keyguard is secure. | |
*/ | |
public boolean isKeyguardSecure() { | |
try { | |
@@ -196,6 +194,9 @@ | |
* This will, if the keyguard is secure, bring up the unlock screen of | |
* the keyguard. | |
* | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#DISABLE_KEYGUARD}. | |
+ * | |
* @param callback Let's you know whether the operation was succesful and | |
* it is safe to launch anything that would normally be considered safe | |
* once the user has gotten past the keyguard. | |
diff -Nur android-15/android/app/LoadedApk.java android-16/android/app/LoadedApk.java | |
--- android-15/android/app/LoadedApk.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/LoadedApk.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -36,6 +36,8 @@ | |
import android.os.Process; | |
import android.os.RemoteException; | |
import android.os.StrictMode; | |
+import android.os.Trace; | |
+import android.os.UserId; | |
import android.util.AndroidRuntimeException; | |
import android.util.Slog; | |
import android.view.CompatibilityInfoHolder; | |
@@ -67,6 +69,8 @@ | |
*/ | |
public final class LoadedApk { | |
+ private static final String TAG = "LoadedApk"; | |
+ | |
private final ActivityThread mActivityThread; | |
private final ApplicationInfo mApplicationInfo; | |
final String mPackageName; | |
@@ -113,8 +117,13 @@ | |
mApplicationInfo = aInfo; | |
mPackageName = aInfo.packageName; | |
mAppDir = aInfo.sourceDir; | |
- mResDir = aInfo.uid == Process.myUid() ? aInfo.sourceDir | |
+ final int myUid = Process.myUid(); | |
+ mResDir = aInfo.uid == myUid ? aInfo.sourceDir | |
: aInfo.publicSourceDir; | |
+ if (!UserId.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) { | |
+ aInfo.dataDir = PackageManager.getDataDirForUser(UserId.getUserId(myUid), | |
+ mPackageName); | |
+ } | |
mSharedLibraries = aInfo.sharedLibraryFiles; | |
mDataDir = aInfo.dataDir; | |
mDataDirFile = mDataDir != null ? new File(mDataDir) : null; | |
@@ -186,7 +195,7 @@ | |
ApplicationInfo ai = null; | |
try { | |
ai = ActivityThread.getPackageManager().getApplicationInfo(packageName, | |
- PackageManager.GET_SHARED_LIBRARY_FILES); | |
+ PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId()); | |
} catch (RemoteException e) { | |
throw new AssertionError(e); | |
} | |
@@ -253,6 +262,7 @@ | |
if (mIncludeCode && !mPackageName.equals("android")) { | |
String zip = mAppDir; | |
+ String libraryPath = mLibDir; | |
/* | |
* The following is a bit of a hack to inject | |
@@ -265,15 +275,20 @@ | |
String instrumentationAppDir = | |
mActivityThread.mInstrumentationAppDir; | |
+ String instrumentationAppLibraryDir = | |
+ mActivityThread.mInstrumentationAppLibraryDir; | |
String instrumentationAppPackage = | |
mActivityThread.mInstrumentationAppPackage; | |
String instrumentedAppDir = | |
mActivityThread.mInstrumentedAppDir; | |
+ String instrumentedAppLibraryDir = | |
+ mActivityThread.mInstrumentedAppLibraryDir; | |
String[] instrumentationLibs = null; | |
if (mAppDir.equals(instrumentationAppDir) | |
|| mAppDir.equals(instrumentedAppDir)) { | |
zip = instrumentationAppDir + ":" + instrumentedAppDir; | |
+ libraryPath = instrumentationAppLibraryDir + ":" + instrumentedAppLibraryDir; | |
if (! instrumentedAppDir.equals(instrumentationAppDir)) { | |
instrumentationLibs = | |
getLibrariesFor(instrumentationAppPackage); | |
@@ -293,7 +308,7 @@ | |
*/ | |
if (ActivityThread.localLOGV) | |
- Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + mLibDir); | |
+ Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + libraryPath); | |
// Temporarily disable logging of disk reads on the Looper thread | |
// as this is early and necessary. | |
@@ -301,7 +316,7 @@ | |
mClassLoader = | |
ApplicationLoaders.getDefault().getClassLoader( | |
- zip, mLibDir, mBaseClassLoader); | |
+ zip, libraryPath, mBaseClassLoader); | |
initializeJavaContextClassLoader(); | |
StrictMode.setThreadPolicy(oldPolicy); | |
@@ -343,7 +358,7 @@ | |
IPackageManager pm = ActivityThread.getPackageManager(); | |
android.content.pm.PackageInfo pi; | |
try { | |
- pi = pm.getPackageInfo(mPackageName, 0); | |
+ pi = pm.getPackageInfo(mPackageName, 0, UserId.myUserId()); | |
} catch (RemoteException e) { | |
throw new AssertionError(e); | |
} | |
@@ -434,6 +449,10 @@ | |
return mAppDir; | |
} | |
+ public String getLibDir() { | |
+ return mLibDir; | |
+ } | |
+ | |
public String getResDir() { | |
return mResDir; | |
} | |
@@ -504,6 +523,7 @@ | |
public void removeContextRegistrations(Context context, | |
String who, String what) { | |
+ final boolean reportRegistrationLeaks = StrictMode.vmRegistrationLeaksEnabled(); | |
HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap = | |
mReceivers.remove(context); | |
if (rmap != null) { | |
@@ -517,6 +537,9 @@ | |
"call to unregisterReceiver()?"); | |
leak.setStackTrace(rd.getLocation().getStackTrace()); | |
Slog.e(ActivityThread.TAG, leak.getMessage(), leak); | |
+ if (reportRegistrationLeaks) { | |
+ StrictMode.onIntentReceiverLeaked(leak); | |
+ } | |
try { | |
ActivityManagerNative.getDefault().unregisterReceiver( | |
rd.getIIntentReceiver()); | |
@@ -538,6 +561,9 @@ | |
+ sd.getServiceConnection() + " that was originally bound here"); | |
leak.setStackTrace(sd.getLocation().getStackTrace()); | |
Slog.e(ActivityThread.TAG, leak.getMessage(), leak); | |
+ if (reportRegistrationLeaks) { | |
+ StrictMode.onServiceConnectionLeaked(leak); | |
+ } | |
try { | |
ActivityManagerNative.getDefault().unbindService( | |
sd.getIServiceConnection()); | |
@@ -720,6 +746,7 @@ | |
return; | |
} | |
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg"); | |
try { | |
ClassLoader cl = mReceiver.getClass().getClassLoader(); | |
intent.setExtrasClassLoader(cl); | |
@@ -734,6 +761,7 @@ | |
} | |
if (mInstrumentation == null || | |
!mInstrumentation.onException(mReceiver, e)) { | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
throw new RuntimeException( | |
"Error receiving broadcast " + intent | |
+ " in " + mReceiver, e); | |
@@ -743,6 +771,7 @@ | |
if (receiver.getPendingResult() != null) { | |
finish(); | |
} | |
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); | |
} | |
} | |
diff -Nur android-15/android/app/LoaderManager.java android-16/android/app/LoaderManager.java | |
--- android-15/android/app/LoaderManager.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/app/LoaderManager.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -17,6 +17,7 @@ | |
package android.app; | |
import android.content.Loader; | |
+import android.content.Loader.OnLoadCanceledListener; | |
import android.os.Bundle; | |
import android.util.DebugUtils; | |
import android.util.Log; | |
@@ -219,7 +220,8 @@ | |
boolean mCreatingLoader; | |
- final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> { | |
+ final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>, | |
+ Loader.OnLoadCanceledListener<Object> { | |
final int mId; | |
final Bundle mArgs; | |
LoaderManager.LoaderCallbacks<Object> mCallbacks; | |
@@ -271,6 +273,7 @@ | |
} | |
if (!mListenerRegistered) { | |
mLoader.registerListener(mId, this); | |
+ mLoader.registerOnLoadCanceledListener(this); | |
mListenerRegistered = true; | |
} | |
mLoader.startLoading(); | |
@@ -329,11 +332,21 @@ | |
// Let the loader know we're done with it | |
mListenerRegistered = false; | |
mLoader.unregisterListener(this); | |
+ mLoader.unregisterOnLoadCanceledListener(this); | |
mLoader.stopLoading(); | |
} | |
} | |
} | |
- | |
+ | |
+ void cancel() { | |
+ if (DEBUG) Log.v(TAG, " Canceling: " + this); | |
+ if (mStarted && mLoader != null && mListenerRegistered) { | |
+ if (!mLoader.cancelLoad()) { | |
+ onLoadCanceled(mLoader); | |
+ } | |
+ } | |
+ } | |
+ | |
void destroy() { | |
if (DEBUG) Log.v(TAG, " Destroying: " + this); | |
mDestroyed = true; | |
@@ -361,6 +374,7 @@ | |
if (mListenerRegistered) { | |
mListenerRegistered = false; | |
mLoader.unregisterListener(this); | |
+ mLoader.unregisterOnLoadCanceledListener(this); | |
} | |
mLoader.reset(); | |
} | |
@@ -368,8 +382,38 @@ | |
mPendingLoader.destroy(); | |
} | |
} | |
- | |
- @Override public void onLoadComplete(Loader<Object> loader, Object data) { | |
+ | |
+ @Override | |
+ public void onLoadCanceled(Loader<Object> loader) { | |
+ if (DEBUG) Log.v(TAG, "onLoadCanceled: " + this); | |
+ | |
+ if (mDestroyed) { | |
+ if (DEBUG) Log.v(TAG, " Ignoring load canceled -- destroyed"); | |
+ return; | |
+ } | |
+ | |
+ if (mLoaders.get(mId) != this) { | |
+ // This cancellation message is not coming from the current active loader. | |
+ // We don't care about it. | |
+ if (DEBUG) Log.v(TAG, " Ignoring load canceled -- not active"); | |
+ return; | |
+ } | |
+ | |
+ LoaderInfo pending = mPendingLoader; | |
+ if (pending != null) { | |
+ // There is a new request pending and we were just | |
+ // waiting for the old one to cancel or complete before starting | |
+ // it. So now it is time, switch over to the new loader. | |
+ if (DEBUG) Log.v(TAG, " Switching to pending loader: " + pending); | |
+ mPendingLoader = null; | |
+ mLoaders.put(mId, null); | |
+ destroy(); | |
+ installLoader(pending); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void onLoadComplete(Loader<Object> loader, Object data) { | |
if (DEBUG) Log.v(TAG, "onLoadComplete: " + this); | |
if (mDestroyed) { | |
@@ -632,7 +676,9 @@ | |
} else { | |
// Now we have three active loaders... we'll queue | |
// up this request to be processed once one of the other loaders | |
- // finishes. | |
+ // finishes or is canceled. | |
+ if (DEBUG) Log.v(TAG, " Current loader is running; attempting to cancel"); | |
+ info.cancel(); | |
if (info.mPendingLoader != null) { | |
if (DEBUG) Log.v(TAG, " Removing pending loader: " + info.mPendingLoader); | |
info.mPendingLoader.destroy(); | |
diff -Nur android-15/android/app/MediaRouteActionProvider.java android-16/android/app/MediaRouteActionProvider.java | |
--- android-15/android/app/MediaRouteActionProvider.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/app/MediaRouteActionProvider.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -0,0 +1,167 @@ | |
+/* | |
+ * 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 android.app; | |
+ | |
+import com.android.internal.app.MediaRouteChooserDialogFragment; | |
+ | |
+import android.content.Context; | |
+import android.content.ContextWrapper; | |
+import android.media.MediaRouter; | |
+import android.media.MediaRouter.RouteInfo; | |
+import android.util.Log; | |
+import android.view.ActionProvider; | |
+import android.view.MenuItem; | |
+import android.view.View; | |
+ | |
+import java.lang.ref.WeakReference; | |
+ | |
+public class MediaRouteActionProvider extends ActionProvider { | |
+ private static final String TAG = "MediaRouteActionProvider"; | |
+ | |
+ private Context mContext; | |
+ private MediaRouter mRouter; | |
+ private MenuItem mMenuItem; | |
+ private MediaRouteButton mView; | |
+ private int mRouteTypes; | |
+ private View.OnClickListener mExtendedSettingsListener; | |
+ private RouterCallback mCallback; | |
+ | |
+ public MediaRouteActionProvider(Context context) { | |
+ super(context); | |
+ mContext = context; | |
+ mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); | |
+ mCallback = new RouterCallback(this); | |
+ | |
+ // Start with live audio by default. | |
+ // TODO Update this when new route types are added; segment by API level | |
+ // when different route types were added. | |
+ setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_AUDIO); | |
+ } | |
+ | |
+ public void setRouteTypes(int types) { | |
+ if (mRouteTypes == types) return; | |
+ if (mRouteTypes != 0) { | |
+ mRouter.removeCallback(mCallback); | |
+ } | |
+ mRouteTypes = types; | |
+ if (types != 0) { | |
+ mRouter.addCallback(types, mCallback); | |
+ } | |
+ if (mView != null) { | |
+ mView.setRouteTypes(mRouteTypes); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public View onCreateActionView() { | |
+ throw new UnsupportedOperationException("Use onCreateActionView(MenuItem) instead."); | |
+ } | |
+ | |
+ @Override | |
+ public View onCreateActionView(MenuItem item) { | |
+ if (mMenuItem != null || mView != null) { | |
+ Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " + | |
+ "with a menu item. Don't reuse MediaRouteActionProvider instances! " + | |
+ "Abandoning the old one..."); | |
+ } | |
+ mMenuItem = item; | |
+ mView = new MediaRouteButton(mContext); | |
+ mView.setRouteTypes(mRouteTypes); | |
+ mView.setExtendedSettingsClickListener(mExtendedSettingsListener); | |
+ return mView; | |
+ } | |
+ | |
+ @Override | |
+ public boolean onPerformDefaultAction() { | |
+ final FragmentManager fm = getActivity().getFragmentManager(); | |
+ // See if one is already attached to this activity. | |
+ MediaRouteChooserDialogFragment dialogFragment = | |
+ (MediaRouteChooserDialogFragment) fm.findFragmentByTag( | |
+ MediaRouteChooserDialogFragment.FRAGMENT_TAG); | |
+ if (dialogFragment != null) { | |
+ Log.w(TAG, "onPerformDefaultAction(): Chooser dialog already showing!"); | |
+ return false; | |
+ } | |
+ | |
+ dialogFragment = new MediaRouteChooserDialogFragment(); | |
+ dialogFragment.setExtendedSettingsClickListener(mExtendedSettingsListener); | |
+ dialogFragment.setRouteTypes(mRouteTypes); | |
+ dialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); | |
+ return true; | |
+ } | |
+ | |
+ private Activity getActivity() { | |
+ // Gross way of unwrapping the Activity so we can get the FragmentManager | |
+ Context context = mContext; | |
+ while (context instanceof ContextWrapper && !(context instanceof Activity)) { | |
+ context = ((ContextWrapper) context).getBaseContext(); | |
+ } | |
+ if (!(context instanceof Activity)) { | |
+ throw new IllegalStateException("The MediaRouteActionProvider's Context " + | |
+ "is not an Activity."); | |
+ } | |
+ | |
+ return (Activity) context; | |
+ } | |
+ | |
+ public void setExtendedSettingsClickListener(View.OnClickListener listener) { | |
+ mExtendedSettingsListener = listener; | |
+ if (mView != null) { | |
+ mView.setExtendedSettingsClickListener(listener); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public boolean overridesItemVisibility() { | |
+ return true; | |
+ } | |
+ | |
+ @Override | |
+ public boolean isVisible() { | |
+ return mRouter.getRouteCount() > 1; | |
+ } | |
+ | |
+ private static class RouterCallback extends MediaRouter.SimpleCallback { | |
+ private WeakReference<MediaRouteActionProvider> mAp; | |
+ | |
+ RouterCallback(MediaRouteActionProvider ap) { | |
+ mAp = new WeakReference<MediaRouteActionProvider>(ap); | |
+ } | |
+ | |
+ @Override | |
+ public void onRouteAdded(MediaRouter router, RouteInfo info) { | |
+ final MediaRouteActionProvider ap = mAp.get(); | |
+ if (ap == null) { | |
+ router.removeCallback(this); | |
+ return; | |
+ } | |
+ | |
+ ap.refreshVisibility(); | |
+ } | |
+ | |
+ @Override | |
+ public void onRouteRemoved(MediaRouter router, RouteInfo info) { | |
+ final MediaRouteActionProvider ap = mAp.get(); | |
+ if (ap == null) { | |
+ router.removeCallback(this); | |
+ return; | |
+ } | |
+ | |
+ ap.refreshVisibility(); | |
+ } | |
+ } | |
+} | |
diff -Nur android-15/android/app/MediaRouteButton.java android-16/android/app/MediaRouteButton.java | |
--- android-15/android/app/MediaRouteButton.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/app/MediaRouteButton.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -0,0 +1,378 @@ | |
+/* | |
+ * 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 android.app; | |
+ | |
+import com.android.internal.R; | |
+import com.android.internal.app.MediaRouteChooserDialogFragment; | |
+ | |
+import android.content.Context; | |
+import android.content.ContextWrapper; | |
+import android.content.res.TypedArray; | |
+import android.graphics.Canvas; | |
+import android.graphics.drawable.Drawable; | |
+import android.media.MediaRouter; | |
+import android.media.MediaRouter.RouteGroup; | |
+import android.media.MediaRouter.RouteInfo; | |
+import android.util.AttributeSet; | |
+import android.util.Log; | |
+import android.view.SoundEffectConstants; | |
+import android.view.View; | |
+ | |
+public class MediaRouteButton extends View { | |
+ private static final String TAG = "MediaRouteButton"; | |
+ | |
+ private MediaRouter mRouter; | |
+ private final MediaRouteCallback mRouterCallback = new MediaRouteCallback(); | |
+ private int mRouteTypes; | |
+ | |
+ private boolean mAttachedToWindow; | |
+ | |
+ private Drawable mRemoteIndicator; | |
+ private boolean mRemoteActive; | |
+ private boolean mToggleMode; | |
+ | |
+ private int mMinWidth; | |
+ private int mMinHeight; | |
+ | |
+ private OnClickListener mExtendedSettingsClickListener; | |
+ private MediaRouteChooserDialogFragment mDialogFragment; | |
+ | |
+ private static final int[] ACTIVATED_STATE_SET = { | |
+ R.attr.state_activated | |
+ }; | |
+ | |
+ public MediaRouteButton(Context context) { | |
+ this(context, null); | |
+ } | |
+ | |
+ public MediaRouteButton(Context context, AttributeSet attrs) { | |
+ this(context, null, com.android.internal.R.attr.mediaRouteButtonStyle); | |
+ } | |
+ | |
+ public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) { | |
+ super(context, attrs, defStyleAttr); | |
+ | |
+ mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE); | |
+ | |
+ TypedArray a = context.obtainStyledAttributes(attrs, | |
+ com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0); | |
+ setRemoteIndicatorDrawable(a.getDrawable( | |
+ com.android.internal.R.styleable.MediaRouteButton_externalRouteEnabledDrawable)); | |
+ mMinWidth = a.getDimensionPixelSize( | |
+ com.android.internal.R.styleable.MediaRouteButton_minWidth, 0); | |
+ mMinHeight = a.getDimensionPixelSize( | |
+ com.android.internal.R.styleable.MediaRouteButton_minHeight, 0); | |
+ final int routeTypes = a.getInteger( | |
+ com.android.internal.R.styleable.MediaRouteButton_mediaRouteTypes, | |
+ MediaRouter.ROUTE_TYPE_LIVE_AUDIO); | |
+ a.recycle(); | |
+ | |
+ setClickable(true); | |
+ | |
+ setRouteTypes(routeTypes); | |
+ } | |
+ | |
+ private void setRemoteIndicatorDrawable(Drawable d) { | |
+ if (mRemoteIndicator != null) { | |
+ mRemoteIndicator.setCallback(null); | |
+ unscheduleDrawable(mRemoteIndicator); | |
+ } | |
+ mRemoteIndicator = d; | |
+ if (d != null) { | |
+ d.setCallback(this); | |
+ d.setState(getDrawableState()); | |
+ d.setVisible(getVisibility() == VISIBLE, false); | |
+ } | |
+ | |
+ refreshDrawableState(); | |
+ } | |
+ | |
+ @Override | |
+ public boolean performClick() { | |
+ // Send the appropriate accessibility events and call listeners | |
+ boolean handled = super.performClick(); | |
+ if (!handled) { | |
+ playSoundEffect(SoundEffectConstants.CLICK); | |
+ } | |
+ | |
+ if (mToggleMode) { | |
+ if (mRemoteActive) { | |
+ mRouter.selectRouteInt(mRouteTypes, mRouter.getSystemAudioRoute()); | |
+ } else { | |
+ final int N = mRouter.getRouteCount(); | |
+ for (int i = 0; i < N; i++) { | |
+ final RouteInfo route = mRouter.getRouteAt(i); | |
+ if ((route.getSupportedTypes() & mRouteTypes) != 0 && | |
+ route != mRouter.getSystemAudioRoute()) { | |
+ mRouter.selectRouteInt(mRouteTypes, route); | |
+ } | |
+ } | |
+ } | |
+ } else { | |
+ showDialog(); | |
+ } | |
+ | |
+ return handled; | |
+ } | |
+ | |
+ public void setRouteTypes(int types) { | |
+ if (types == mRouteTypes) { | |
+ // Already registered; nothing to do. | |
+ return; | |
+ } | |
+ | |
+ if (mAttachedToWindow && mRouteTypes != 0) { | |
+ mRouter.removeCallback(mRouterCallback); | |
+ } | |
+ | |
+ mRouteTypes = types; | |
+ | |
+ if (mAttachedToWindow) { | |
+ updateRouteInfo(); | |
+ mRouter.addCallback(types, mRouterCallback); | |
+ } | |
+ } | |
+ | |
+ private void updateRouteInfo() { | |
+ updateRemoteIndicator(); | |
+ updateRouteCount(); | |
+ } | |
+ | |
+ public int getRouteTypes() { | |
+ return mRouteTypes; | |
+ } | |
+ | |
+ void updateRemoteIndicator() { | |
+ final boolean isRemote = | |
+ mRouter.getSelectedRoute(mRouteTypes) != mRouter.getSystemAudioRoute(); | |
+ if (mRemoteActive != isRemote) { | |
+ mRemoteActive = isRemote; | |
+ refreshDrawableState(); | |
+ } | |
+ } | |
+ | |
+ void updateRouteCount() { | |
+ final int N = mRouter.getRouteCount(); | |
+ int count = 0; | |
+ for (int i = 0; i < N; i++) { | |
+ final RouteInfo route = mRouter.getRouteAt(i); | |
+ if ((route.getSupportedTypes() & mRouteTypes) != 0) { | |
+ if (route instanceof RouteGroup) { | |
+ count += ((RouteGroup) route).getRouteCount(); | |
+ } else { | |
+ count++; | |
+ } | |
+ } | |
+ } | |
+ | |
+ setEnabled(count != 0); | |
+ | |
+ // Only allow toggling if we have more than just user routes | |
+ mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0; | |
+ } | |
+ | |
+ @Override | |
+ protected int[] onCreateDrawableState(int extraSpace) { | |
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); | |
+ if (mRemoteActive) { | |
+ mergeDrawableStates(drawableState, ACTIVATED_STATE_SET); | |
+ } | |
+ return drawableState; | |
+ } | |
+ | |
+ @Override | |
+ protected void drawableStateChanged() { | |
+ super.drawableStateChanged(); | |
+ | |
+ if (mRemoteIndicator != null) { | |
+ int[] myDrawableState = getDrawableState(); | |
+ mRemoteIndicator.setState(myDrawableState); | |
+ invalidate(); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ protected boolean verifyDrawable(Drawable who) { | |
+ return super.verifyDrawable(who) || who == mRemoteIndicator; | |
+ } | |
+ | |
+ @Override | |
+ public void jumpDrawablesToCurrentState() { | |
+ super.jumpDrawablesToCurrentState(); | |
+ if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState(); | |
+ } | |
+ | |
+ @Override | |
+ public void setVisibility(int visibility) { | |
+ super.setVisibility(visibility); | |
+ if (mRemoteIndicator != null) { | |
+ mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void onAttachedToWindow() { | |
+ super.onAttachedToWindow(); | |
+ mAttachedToWindow = true; | |
+ if (mRouteTypes != 0) { | |
+ mRouter.addCallback(mRouteTypes, mRouterCallback); | |
+ updateRouteInfo(); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void onDetachedFromWindow() { | |
+ if (mRouteTypes != 0) { | |
+ mRouter.removeCallback(mRouterCallback); | |
+ } | |
+ mAttachedToWindow = false; | |
+ super.onDetachedFromWindow(); | |
+ } | |
+ | |
+ @Override | |
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec); | |
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec); | |
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec); | |
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec); | |
+ | |
+ final int minWidth = Math.max(mMinWidth, | |
+ mRemoteIndicator != null ? mRemoteIndicator.getIntrinsicWidth() : 0); | |
+ final int minHeight = Math.max(mMinHeight, | |
+ mRemoteIndicator != null ? mRemoteIndicator.getIntrinsicHeight() : 0); | |
+ | |
+ int width; | |
+ switch (widthMode) { | |
+ case MeasureSpec.EXACTLY: | |
+ width = widthSize; | |
+ break; | |
+ case MeasureSpec.AT_MOST: | |
+ width = Math.min(widthSize, minWidth + getPaddingLeft() + getPaddingRight()); | |
+ break; | |
+ default: | |
+ case MeasureSpec.UNSPECIFIED: | |
+ width = minWidth + getPaddingLeft() + getPaddingRight(); | |
+ break; | |
+ } | |
+ | |
+ int height; | |
+ switch (heightMode) { | |
+ case MeasureSpec.EXACTLY: | |
+ height = heightSize; | |
+ break; | |
+ case MeasureSpec.AT_MOST: | |
+ height = Math.min(heightSize, minHeight + getPaddingTop() + getPaddingBottom()); | |
+ break; | |
+ default: | |
+ case MeasureSpec.UNSPECIFIED: | |
+ height = minHeight + getPaddingTop() + getPaddingBottom(); | |
+ break; | |
+ } | |
+ | |
+ setMeasuredDimension(width, height); | |
+ } | |
+ | |
+ @Override | |
+ protected void onDraw(Canvas canvas) { | |
+ super.onDraw(canvas); | |
+ | |
+ if (mRemoteIndicator == null) return; | |
+ | |
+ final int left = getPaddingLeft(); | |
+ final int right = getWidth() - getPaddingRight(); | |
+ final int top = getPaddingTop(); | |
+ final int bottom = getHeight() - getPaddingBottom(); | |
+ | |
+ final int drawWidth = mRemoteIndicator.getIntrinsicWidth(); | |
+ final int drawHeight = mRemoteIndicator.getIntrinsicHeight(); | |
+ final int drawLeft = left + (right - left - drawWidth) / 2; | |
+ final int drawTop = top + (bottom - top - drawHeight) / 2; | |
+ | |
+ mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight); | |
+ mRemoteIndicator.draw(canvas); | |
+ } | |
+ | |
+ public void setExtendedSettingsClickListener(OnClickListener listener) { | |
+ mExtendedSettingsClickListener = listener; | |
+ if (mDialogFragment != null) { | |
+ mDialogFragment.setExtendedSettingsClickListener(listener); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Asynchronously show the route chooser dialog. | |
+ * This will attach a {@link DialogFragment} to the containing Activity. | |
+ */ | |
+ public void showDialog() { | |
+ final FragmentManager fm = getActivity().getFragmentManager(); | |
+ if (mDialogFragment == null) { | |
+ // See if one is already attached to this activity. | |
+ mDialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag( | |
+ MediaRouteChooserDialogFragment.FRAGMENT_TAG); | |
+ } | |
+ if (mDialogFragment != null) { | |
+ Log.w(TAG, "showDialog(): Already showing!"); | |
+ return; | |
+ } | |
+ | |
+ mDialogFragment = new MediaRouteChooserDialogFragment(); | |
+ mDialogFragment.setExtendedSettingsClickListener(mExtendedSettingsClickListener); | |
+ mDialogFragment.setLauncherListener(new MediaRouteChooserDialogFragment.LauncherListener() { | |
+ @Override | |
+ public void onDetached(MediaRouteChooserDialogFragment detachedFragment) { | |
+ mDialogFragment = null; | |
+ } | |
+ }); | |
+ mDialogFragment.setRouteTypes(mRouteTypes); | |
+ mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); | |
+ } | |
+ | |
+ private Activity getActivity() { | |
+ // Gross way of unwrapping the Activity so we can get the FragmentManager | |
+ Context context = getContext(); | |
+ while (context instanceof ContextWrapper && !(context instanceof Activity)) { | |
+ context = ((ContextWrapper) context).getBaseContext(); | |
+ } | |
+ if (!(context instanceof Activity)) { | |
+ throw new IllegalStateException("The MediaRouteButton's Context is not an Activity."); | |
+ } | |
+ | |
+ return (Activity) context; | |
+ } | |
+ | |
+ private class MediaRouteCallback extends MediaRouter.SimpleCallback { | |
+ @Override | |
+ public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { | |
+ updateRemoteIndicator(); | |
+ } | |
+ | |
+ @Override | |
+ public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { | |
+ updateRemoteIndicator(); | |
+ } | |
+ | |
+ @Override | |
+ public void onRouteAdded(MediaRouter router, RouteInfo info) { | |
+ updateRouteCount(); | |
+ } | |
+ | |
+ @Override | |
+ public void onRouteRemoved(MediaRouter router, RouteInfo info) { | |
+ updateRouteCount(); | |
+ } | |
+ } | |
+} | |
diff -Nur android-15/android/app/Notification.java android-16/android/app/Notification.java | |
--- android-15/android/app/Notification.java 2012-06-18 20:00:45.000000000 +0900 | |
+++ android-16/android/app/Notification.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -20,16 +20,26 @@ | |
import android.content.Context; | |
import android.content.Intent; | |
+import android.content.res.Resources; | |
import android.graphics.Bitmap; | |
+import android.media.AudioManager; | |
import android.net.Uri; | |
+import android.os.Bundle; | |
+import android.os.IBinder; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
+import android.os.SystemClock; | |
import android.text.TextUtils; | |
+import android.util.IntProperty; | |
+import android.util.Log; | |
+import android.util.Slog; | |
+import android.util.TypedValue; | |
import android.view.View; | |
import android.widget.ProgressBar; | |
import android.widget.RemoteViews; | |
import java.text.NumberFormat; | |
+import java.util.ArrayList; | |
/** | |
* A class that represents how a persistent notification is to be presented to | |
@@ -51,36 +61,58 @@ | |
* Use all default values (where applicable). | |
*/ | |
public static final int DEFAULT_ALL = ~0; | |
- | |
+ | |
/** | |
* Use the default notification sound. This will ignore any given | |
* {@link #sound}. | |
- * | |
+ * | |
+ | |
* @see #defaults | |
- */ | |
+ */ | |
+ | |
public static final int DEFAULT_SOUND = 1; | |
/** | |
* Use the default notification vibrate. This will ignore any given | |
- * {@link #vibrate}. Using phone vibration requires the | |
+ * {@link #vibrate}. Using phone vibration requires the | |
* {@link android.Manifest.permission#VIBRATE VIBRATE} permission. | |
- * | |
+ * | |
* @see #defaults | |
- */ | |
+ */ | |
+ | |
public static final int DEFAULT_VIBRATE = 2; | |
- | |
+ | |
/** | |
* Use the default notification lights. This will ignore the | |
* {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or | |
* {@link #ledOnMS}. | |
- * | |
+ * | |
* @see #defaults | |
- */ | |
+ */ | |
+ | |
public static final int DEFAULT_LIGHTS = 4; | |
- | |
+ | |
/** | |
- * The timestamp for the notification. The icons and expanded views | |
- * are sorted by this key. | |
+ * A timestamp related to this notification, in milliseconds since the epoch. | |
+ * | |
+ * Default value: {@link System#currentTimeMillis() Now}. | |
+ * | |
+ * Choose a timestamp that will be most relevant to the user. For most finite events, this | |
+ * corresponds to the time the event happened (or will happen, in the case of events that have | |
+ * yet to occur but about which the user is being informed). Indefinite events should be | |
+ * timestamped according to when the activity began. | |
+ * | |
+ * Some examples: | |
+ * | |
+ * <ul> | |
+ * <li>Notification of a new chat message should be stamped when the message was received.</li> | |
+ * <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li> | |
+ * <li>Notification of a completed file download should be stamped when the download finished.</li> | |
+ * <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li> | |
+ * <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time. | |
+ * <li>Notification of an ongoing countdown timer should be stamped with the timer's end time. | |
+ * </ul> | |
+ * | |
*/ | |
public long when; | |
@@ -100,10 +132,16 @@ | |
public int iconLevel; | |
/** | |
- * The number of events that this notification represents. For example, in a new mail | |
- * notification, this could be the number of unread messages. This number is superimposed over | |
- * the icon in the status bar. If the number is 0 or negative, it is not shown in the status | |
- * bar. | |
+ * The number of events that this notification represents. For example, in a new mail | |
+ * notification, this could be the number of unread messages. | |
+ * | |
+ * The system may or may not use this field to modify the appearance of the notification. For | |
+ * example, before {@link android.os.Build.VERSION_CODES#HONEYCOMB}, this number was | |
+ * superimposed over the icon in the status bar. Starting with | |
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, the template used by | |
+ * {@link Notification.Builder} has displayed the number in the expanded notification view. | |
+ * | |
+ * If the number is 0 or negative, it is never shown. | |
*/ | |
public int number; | |
@@ -121,10 +159,11 @@ | |
public PendingIntent contentIntent; | |
/** | |
- * The intent to execute when the status entry is deleted by the user | |
- * with the "Clear All Notifications" button. This probably shouldn't | |
- * be launching an activity since several of those will be sent at the | |
- * same time. | |
+ * The intent to execute when the notification is explicitly dismissed by the user, either with | |
+ * the "Clear All" button or by swiping it away individually. | |
+ * | |
+ * This probably shouldn't be launching an activity since several of those will be sent | |
+ * at the same time. | |
*/ | |
public PendingIntent deleteIntent; | |
@@ -139,11 +178,6 @@ | |
* Text to scroll across the screen when this item is added to | |
* the status bar on large and smaller devices. | |
* | |
- * <p>This field is provided separately from the other ticker fields | |
- * both for compatibility and to allow an application to choose different | |
- * text for when the text scrolls in and when it is displayed all at once | |
- * in conjunction with one or more icons. | |
- * | |
* @see #tickerView | |
*/ | |
public CharSequence tickerText; | |
@@ -160,15 +194,22 @@ | |
public RemoteViews contentView; | |
/** | |
+ * A large-format version of {@link #contentView}, giving the Notification an | |
+ * opportunity to show more detail. The system UI may choose to show this | |
+ * instead of the normal content view at its discretion. | |
+ */ | |
+ public RemoteViews bigContentView; | |
+ | |
+ /** | |
* The bitmap that may escape the bounds of the panel and bar. | |
*/ | |
public Bitmap largeIcon; | |
/** | |
* The sound to play. | |
- * | |
+ * | |
* <p> | |
- * To play the default notification sound, see {@link #defaults}. | |
+ * To play the default notification sound, see {@link #defaults}. | |
* </p> | |
*/ | |
public Uri sound; | |
@@ -176,7 +217,7 @@ | |
/** | |
* Use this constant as the value for audioStreamType to request that | |
* the default stream type for notifications be used. Currently the | |
- * default stream type is STREAM_RING. | |
+ * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. | |
*/ | |
public static final int STREAM_DEFAULT = -1; | |
@@ -187,14 +228,13 @@ | |
*/ | |
public int audioStreamType = STREAM_DEFAULT; | |
- | |
/** | |
- * The pattern with which to vibrate. | |
- * | |
+ * The pattern with which to vibrate. | |
+ * | |
* <p> | |
* To vibrate the default pattern, see {@link #defaults}. | |
* </p> | |
- * | |
+ * | |
* @see android.os.Vibrator#vibrate(long[],int) | |
*/ | |
public long[] vibrate; | |
@@ -235,7 +275,6 @@ | |
*/ | |
public int defaults; | |
- | |
/** | |
* Bit to be bitwise-ored into the {@link #flags} field that should be | |
* set if you want the LED on for this notification. | |
@@ -252,7 +291,7 @@ | |
* because they will be set to values that work on any given hardware. | |
* <p> | |
* The alpha channel must be set for forward compatibility. | |
- * | |
+ * | |
*/ | |
public static final int FLAG_SHOW_LIGHTS = 0x00000001; | |
@@ -282,7 +321,8 @@ | |
/** | |
* Bit to be bitwise-ored into the {@link #flags} field that should be | |
* set if the notification should be canceled when it is clicked by the | |
- * user. On tablets, the | |
+ * user. On tablets, the | |
+ | |
*/ | |
public static final int FLAG_AUTO_CANCEL = 0x00000010; | |
@@ -301,22 +341,169 @@ | |
public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; | |
/** | |
- * Bit to be bitwise-ored into the {@link #flags} field that should be set if this notification | |
- * represents a high-priority event that may be shown to the user even if notifications are | |
- * otherwise unavailable (that is, when the status bar is hidden). This flag is ideally used | |
- * in conjunction with {@link #fullScreenIntent}. | |
+ * Obsolete flag indicating high-priority notifications; use the priority field instead. | |
+ * | |
+ * @deprecated Use {@link #priority} with a positive value. | |
*/ | |
- public static final int FLAG_HIGH_PRIORITY = 0x00000080; | |
+ public static final int FLAG_HIGH_PRIORITY = 0x00000080; | |
public int flags; | |
/** | |
- * Constructs a Notification object with everything set to 0. | |
+ * Default notification {@link #priority}. If your application does not prioritize its own | |
+ * notifications, use this value for all notifications. | |
+ */ | |
+ public static final int PRIORITY_DEFAULT = 0; | |
+ | |
+ /** | |
+ * Lower {@link #priority}, for items that are less important. The UI may choose to show these | |
+ * items smaller, or at a different position in the list, compared with your app's | |
+ * {@link #PRIORITY_DEFAULT} items. | |
+ */ | |
+ public static final int PRIORITY_LOW = -1; | |
+ | |
+ /** | |
+ * Lowest {@link #priority}; these items might not be shown to the user except under special | |
+ * circumstances, such as detailed notification logs. | |
+ */ | |
+ public static final int PRIORITY_MIN = -2; | |
+ | |
+ /** | |
+ * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to | |
+ * show these items larger, or at a different position in notification lists, compared with | |
+ * your app's {@link #PRIORITY_DEFAULT} items. | |
+ */ | |
+ public static final int PRIORITY_HIGH = 1; | |
+ | |
+ /** | |
+ * Highest {@link #priority}, for your application's most important items that require the | |
+ * user's prompt attention or input. | |
+ */ | |
+ public static final int PRIORITY_MAX = 2; | |
+ | |
+ /** | |
+ * Relative priority for this notification. | |
+ * | |
+ * Priority is an indication of how much of the user's valuable attention should be consumed by | |
+ * this notification. Low-priority notifications may be hidden from the user in certain | |
+ * situations, while the user might be interrupted for a higher-priority notification. The | |
+ * system will make a determination about how to interpret notification priority as described in | |
+ * MUMBLE MUMBLE. | |
+ */ | |
+ public int priority; | |
+ | |
+ /** | |
+ * @hide | |
+ * Notification type: incoming call (voice or video) or similar synchronous communication request. | |
+ */ | |
+ public static final String KIND_CALL = "android.call"; | |
+ | |
+ /** | |
+ * @hide | |
+ * Notification type: incoming direct message (SMS, instant message, etc.). | |
+ */ | |
+ public static final String KIND_MESSAGE = "android.message"; | |
+ | |
+ /** | |
+ * @hide | |
+ * Notification type: asynchronous bulk message (email). | |
+ */ | |
+ public static final String KIND_EMAIL = "android.email"; | |
+ | |
+ /** | |
+ * @hide | |
+ * Notification type: calendar event. | |
+ */ | |
+ public static final String KIND_EVENT = "android.event"; | |
+ | |
+ /** | |
+ * @hide | |
+ * Notification type: promotion or advertisement. | |
+ */ | |
+ public static final String KIND_PROMO = "android.promo"; | |
+ | |
+ /** | |
+ * @hide | |
+ * If this notification matches of one or more special types (see the <code>KIND_*</code> | |
+ * constants), add them here, best match first. | |
+ */ | |
+ public String[] kind; | |
+ | |
+ /** | |
+ * Extra key for people values (type TBD). | |
+ * | |
+ * @hide | |
+ */ | |
+ public static final String EXTRA_PEOPLE = "android.people"; | |
+ | |
+ private Bundle extras; | |
+ | |
+ /** | |
+ * Structure to encapsulate an "action", including title and icon, that can be attached to a Notification. | |
+ * @hide | |
+ */ | |
+ private static class Action implements Parcelable { | |
+ public int icon; | |
+ public CharSequence title; | |
+ public PendingIntent actionIntent; | |
+ @SuppressWarnings("unused") | |
+ public Action() { } | |
+ private Action(Parcel in) { | |
+ icon = in.readInt(); | |
+ title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); | |
+ if (in.readInt() == 1) { | |
+ actionIntent = PendingIntent.CREATOR.createFromParcel(in); | |
+ } | |
+ } | |
+ public Action(int icon_, CharSequence title_, PendingIntent intent_) { | |
+ this.icon = icon_; | |
+ this.title = title_; | |
+ this.actionIntent = intent_; | |
+ } | |
+ @Override | |
+ public Action clone() { | |
+ return new Action( | |
+ this.icon, | |
+ this.title.toString(), | |
+ this.actionIntent // safe to alias | |
+ ); | |
+ } | |
+ @Override | |
+ public int describeContents() { | |
+ return 0; | |
+ } | |
+ @Override | |
+ public void writeToParcel(Parcel out, int flags) { | |
+ out.writeInt(icon); | |
+ TextUtils.writeToParcel(title, out, flags); | |
+ if (actionIntent != null) { | |
+ out.writeInt(1); | |
+ actionIntent.writeToParcel(out, flags); | |
+ } else { | |
+ out.writeInt(0); | |
+ } | |
+ } | |
+ public static final Parcelable.Creator<Action> CREATOR | |
+ = new Parcelable.Creator<Action>() { | |
+ public Action createFromParcel(Parcel in) { | |
+ return new Action(in); | |
+ } | |
+ public Action[] newArray(int size) { | |
+ return new Action[size]; | |
+ } | |
+ }; | |
+ } | |
+ | |
+ private Action[] actions; | |
+ | |
+ /** | |
+ * Constructs a Notification object with default values. | |
* You might want to consider using {@link Builder} instead. | |
*/ | |
public Notification() | |
{ | |
this.when = System.currentTimeMillis(); | |
+ this.priority = PRIORITY_DEFAULT; | |
} | |
/** | |
@@ -396,6 +583,19 @@ | |
if (parcel.readInt() != 0) { | |
fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); | |
} | |
+ | |
+ priority = parcel.readInt(); | |
+ | |
+ kind = parcel.createStringArray(); // may set kind to null | |
+ | |
+ if (parcel.readInt() != 0) { | |
+ extras = parcel.readBundle(); | |
+ } | |
+ | |
+ actions = parcel.createTypedArray(Action.CREATOR); | |
+ if (parcel.readInt() != 0) { | |
+ bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); | |
+ } | |
} | |
@Override | |
@@ -438,9 +638,31 @@ | |
that.ledOnMS = this.ledOnMS; | |
that.ledOffMS = this.ledOffMS; | |
that.defaults = this.defaults; | |
- | |
+ | |
that.flags = this.flags; | |
+ that.priority = this.priority; | |
+ | |
+ final String[] thiskind = this.kind; | |
+ if (thiskind != null) { | |
+ final int N = thiskind.length; | |
+ final String[] thatkind = that.kind = new String[N]; | |
+ System.arraycopy(thiskind, 0, thatkind, 0, N); | |
+ } | |
+ | |
+ if (this.extras != null) { | |
+ that.extras = new Bundle(this.extras); | |
+ | |
+ } | |
+ | |
+ that.actions = new Action[this.actions.length]; | |
+ for(int i=0; i<this.actions.length; i++) { | |
+ that.actions[i] = this.actions[i].clone(); | |
+ } | |
+ if (this.bigContentView != null) { | |
+ that.bigContentView = this.bigContentView.clone(); | |
+ } | |
+ | |
return that; | |
} | |
@@ -517,6 +739,26 @@ | |
} else { | |
parcel.writeInt(0); | |
} | |
+ | |
+ parcel.writeInt(priority); | |
+ | |
+ parcel.writeStringArray(kind); // ok for null | |
+ | |
+ if (extras != null) { | |
+ parcel.writeInt(1); | |
+ extras.writeToParcel(parcel, 0); | |
+ } else { | |
+ parcel.writeInt(0); | |
+ } | |
+ | |
+ parcel.writeTypedArray(actions, 0); | |
+ | |
+ if (bigContentView != null) { | |
+ parcel.writeInt(1); | |
+ bigContentView.writeToParcel(parcel, 0); | |
+ } else { | |
+ parcel.writeInt(0); | |
+ } | |
} | |
/** | |
@@ -551,17 +793,24 @@ | |
* that you take care of task management as described in the | |
* <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back | |
* Stack</a> document. | |
- * | |
+ * | |
* @deprecated Use {@link Builder} instead. | |
*/ | |
@Deprecated | |
public void setLatestEventInfo(Context context, | |
CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { | |
+ // TODO: rewrite this to use Builder | |
RemoteViews contentView = new RemoteViews(context.getPackageName(), | |
- R.layout.status_bar_latest_event_content); | |
+ R.layout.notification_template_base); | |
if (this.icon != 0) { | |
contentView.setImageViewResource(R.id.icon, this.icon); | |
} | |
+ if (priority < PRIORITY_LOW) { | |
+ contentView.setInt(R.id.icon, | |
+ "setBackgroundResource", R.drawable.notification_template_icon_low_bg); | |
+ contentView.setInt(R.id.status_bar_latest_event_content, | |
+ "setBackgroundResource", R.drawable.notification_bg_low); | |
+ } | |
if (contentTitle != null) { | |
contentView.setTextViewText(R.id.title, contentTitle); | |
} | |
@@ -569,8 +818,13 @@ | |
contentView.setTextViewText(R.id.text, contentText); | |
} | |
if (this.when != 0) { | |
+ contentView.setViewVisibility(R.id.time, View.VISIBLE); | |
contentView.setLong(R.id.time, "setTime", when); | |
} | |
+ if (this.number != 0) { | |
+ NumberFormat f = NumberFormat.getIntegerInstance(); | |
+ contentView.setTextViewText(R.id.info, f.format(this.number)); | |
+ } | |
this.contentView = contentView; | |
this.contentIntent = contentIntent; | |
@@ -579,7 +833,9 @@ | |
@Override | |
public String toString() { | |
StringBuilder sb = new StringBuilder(); | |
- sb.append("Notification(contentView="); | |
+ sb.append("Notification(pri="); | |
+ sb.append(priority); | |
+ sb.append(" contentView="); | |
if (contentView != null) { | |
sb.append(contentView.getPackage()); | |
sb.append("/0x"); | |
@@ -587,6 +843,7 @@ | |
} else { | |
sb.append("null"); | |
} | |
+ // TODO(dsandler): defaults take precedence over local values, so reorder the branches below | |
sb.append(" vibrate="); | |
if (this.vibrate != null) { | |
int N = this.vibrate.length-1; | |
@@ -604,7 +861,7 @@ | |
} else { | |
sb.append("null"); | |
} | |
- sb.append(",sound="); | |
+ sb.append(" sound="); | |
if (this.sound != null) { | |
sb.append(this.sound.toString()); | |
} else if ((this.defaults & DEFAULT_SOUND) != 0) { | |
@@ -612,22 +869,50 @@ | |
} else { | |
sb.append("null"); | |
} | |
- sb.append(",defaults=0x"); | |
+ sb.append(" defaults=0x"); | |
sb.append(Integer.toHexString(this.defaults)); | |
- sb.append(",flags=0x"); | |
+ sb.append(" flags=0x"); | |
sb.append(Integer.toHexString(this.flags)); | |
- if ((this.flags & FLAG_HIGH_PRIORITY) != 0) { | |
- sb.append("!!!1!one!"); | |
+ sb.append(" kind=["); | |
+ if (this.kind == null) { | |
+ sb.append("null"); | |
+ } else { | |
+ for (int i=0; i<this.kind.length; i++) { | |
+ if (i>0) sb.append(","); | |
+ sb.append(this.kind[i]); | |
+ } | |
+ } | |
+ sb.append("]"); | |
+ if (actions != null) { | |
+ sb.append(" "); | |
+ sb.append(actions.length); | |
+ sb.append(" action"); | |
+ if (actions.length > 1) sb.append("s"); | |
} | |
sb.append(")"); | |
return sb.toString(); | |
} | |
/** | |
- * Builder class for {@link Notification} objects. Allows easier control over | |
- * all the flags, as well as help constructing the typical notification layouts. | |
+ * Builder class for {@link Notification} objects. | |
+ * | |
+ * Provides a convenient way to set the various fields of a {@link Notification} and generate | |
+ * content views using the platform's notification layout template. | |
+ * | |
+ * Example: | |
+ * | |
+ * <pre class="prettyprint"> | |
+ * Notification noti = new Notification.Builder() | |
+ * .setContentTitle("New mail from " + sender.toString()) | |
+ * .setContentText(subject) | |
+ * .setSmallIcon(R.drawable.new_mail) | |
+ * .setLargeIcon(aBitmap) | |
+ * .build(); | |
+ * </pre> | |
*/ | |
public static class Builder { | |
+ private static final int MAX_ACTION_BUTTONS = 3; | |
+ | |
private Context mContext; | |
private long mWhen; | |
@@ -637,6 +922,7 @@ | |
private CharSequence mContentTitle; | |
private CharSequence mContentText; | |
private CharSequence mContentInfo; | |
+ private CharSequence mSubText; | |
private PendingIntent mContentIntent; | |
private RemoteViews mContentView; | |
private PendingIntent mDeleteIntent; | |
@@ -655,16 +941,31 @@ | |
private int mProgressMax; | |
private int mProgress; | |
private boolean mProgressIndeterminate; | |
+ private ArrayList<String> mKindList = new ArrayList<String>(1); | |
+ private Bundle mExtras; | |
+ private int mPriority; | |
+ private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); | |
+ private boolean mUseChronometer; | |
+ private Style mStyle; | |
/** | |
- * Constructor. | |
+ * Constructs a new Builder with the defaults: | |
* | |
- * Automatically sets the when field to {@link System#currentTimeMillis() | |
- * System.currentTimeMllis()} and the audio stream to the {@link #STREAM_DEFAULT}. | |
+ | |
+ * <table> | |
+ * <tr><th align=right>priority</th> | |
+ * <td>{@link #PRIORITY_DEFAULT}</td></tr> | |
+ * <tr><th align=right>when</th> | |
+ * <td>now ({@link System#currentTimeMillis()})</td></tr> | |
+ * <tr><th align=right>audio stream</th> | |
+ * <td>{@link #STREAM_DEFAULT}</td></tr> | |
+ * </table> | |
* | |
- * @param context A {@link Context} that will be used to construct the | |
- * RemoteViews. The Context will not be held past the lifetime of this | |
- * Builder object. | |
+ | |
+ * @param context | |
+ * A {@link Context} that will be used by the Builder to construct the | |
+ * RemoteViews. The Context will not be held past the lifetime of this Builder | |
+ * object. | |
*/ | |
public Builder(Context context) { | |
mContext = context; | |
@@ -672,11 +973,14 @@ | |
// Set defaults to match the defaults of a Notification | |
mWhen = System.currentTimeMillis(); | |
mAudioStreamType = STREAM_DEFAULT; | |
+ mPriority = PRIORITY_DEFAULT; | |
} | |
/** | |
- * Set the time that the event occurred. Notifications in the panel are | |
- * sorted by this time. | |
+ * Add a timestamp pertaining to the notification (usually the time the event occurred). | |
+ * | |
+ | |
+ * @see Notification#when | |
*/ | |
public Builder setWhen(long when) { | |
mWhen = when; | |
@@ -684,11 +988,34 @@ | |
} | |
/** | |
- * Set the small icon to use in the notification layouts. Different classes of devices | |
- * may return different sizes. See the UX guidelines for more information on how to | |
- * design these icons. | |
+ * Show the {@link Notification#when} field as a stopwatch. | |
+ * | |
+ * Instead of presenting <code>when</code> as a timestamp, the notification will show an | |
+ * automatically updating display of the minutes and seconds since <code>when</code>. | |
+ * | |
+ * Useful when showing an elapsed time (like an ongoing phone call). | |
* | |
- * @param icon A resource ID in the application's package of the drawble to use. | |
+ * @see android.widget.Chronometer | |
+ * @see Notification#when | |
+ */ | |
+ public Builder setUsesChronometer(boolean b) { | |
+ mUseChronometer = b; | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Set the small icon resource, which will be used to represent the notification in the | |
+ * status bar. | |
+ * | |
+ | |
+ * The platform template for the expanded view will draw this icon in the left, unless a | |
+ * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small | |
+ * icon will be moved to the right-hand side. | |
+ * | |
+ | |
+ * @param icon | |
+ * A resource ID in the application's package of the drawable to use. | |
+ * @see Notification#icon | |
*/ | |
public Builder setSmallIcon(int icon) { | |
mSmallIcon = icon; | |
@@ -700,10 +1027,11 @@ | |
* level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable | |
* LevelListDrawable}. | |
* | |
- * @param icon A resource ID in the application's package of the drawble to use. | |
+ * @param icon A resource ID in the application's package of the drawable to use. | |
* @param level The level to use for the icon. | |
* | |
- * @see android.graphics.drawable.LevelListDrawable | |
+ * @see Notification#icon | |
+ * @see Notification#iconLevel | |
*/ | |
public Builder setSmallIcon(int icon, int level) { | |
mSmallIcon = icon; | |
@@ -712,7 +1040,7 @@ | |
} | |
/** | |
- * Set the title (first row) of the notification, in a standard notification. | |
+ * Set the first line of text in the platform notification template. | |
*/ | |
public Builder setContentTitle(CharSequence title) { | |
mContentTitle = title; | |
@@ -720,7 +1048,7 @@ | |
} | |
/** | |
- * Set the text (second row) of the notification, in a standard notification. | |
+ * Set the second line of text in the platform notification template. | |
*/ | |
public Builder setContentText(CharSequence text) { | |
mContentText = text; | |
@@ -728,6 +1056,15 @@ | |
} | |
/** | |
+ * Set the third line of text in the platform notification template. | |
+ * Don't use if you're also using {@link #setProgress(int, int, boolean)}; they occupy the same location in the standard template. | |
+ */ | |
+ public Builder setSubText(CharSequence text) { | |
+ mSubText = text; | |
+ return this; | |
+ } | |
+ | |
+ /** | |
* Set the large number at the right-hand side of the notification. This is | |
* equivalent to setContentInfo, although it might show the number in a different | |
* font size for readability. | |
@@ -738,7 +1075,10 @@ | |
} | |
/** | |
- * Set the large text at the right-hand side of the notification. | |
+ * A small piece of additional information pertaining to this notification. | |
+ * | |
+ * The platform template will draw this on the last line of the notification, at the far | |
+ * right (to the right of a smallIcon if it has been placed there). | |
*/ | |
public Builder setContentInfo(CharSequence info) { | |
mContentInfo = info; | |
@@ -746,8 +1086,9 @@ | |
} | |
/** | |
- * Set the progress this notification represents, which may be | |
- * represented as a {@link ProgressBar}. | |
+ * Set the progress this notification represents. | |
+ * | |
+ * The platform template will represent this using a {@link ProgressBar}. | |
*/ | |
public Builder setProgress(int max, int progress, boolean indeterminate) { | |
mProgressMax = max; | |
@@ -757,7 +1098,9 @@ | |
} | |
/** | |
- * Supply a custom RemoteViews to use instead of the standard one. | |
+ * Supply a custom RemoteViews to use instead of the platform template. | |
+ * | |
+ * @see Notification#contentView | |
*/ | |
public Builder setContent(RemoteViews views) { | |
mContentView = views; | |
@@ -765,12 +1108,15 @@ | |
} | |
/** | |
- * Supply a {@link PendingIntent} to send when the notification is clicked. | |
- * If you do not supply an intent, you can now add PendingIntents to individual | |
- * views to be launched when clicked by calling {@link RemoteViews#setOnClickPendingIntent | |
- * RemoteViews.setOnClickPendingIntent(int,PendingIntent)}. Be sure to | |
- * read {@link Notification#contentIntent Notification.contentIntent} for | |
- * how to correctly use this. | |
+ * Supply a {@link PendingIntent} to be sent when the notification is clicked. | |
+ * | |
+ * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you | |
+ * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use | |
+ * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} | |
+ * to assign PendingIntents to individual views in that custom layout (i.e., to create | |
+ * clickable buttons inside the notification view). | |
+ * | |
+ * @see Notification#contentIntent Notification.contentIntent | |
*/ | |
public Builder setContentIntent(PendingIntent intent) { | |
mContentIntent = intent; | |
@@ -778,11 +1124,9 @@ | |
} | |
/** | |
- * Supply a {@link PendingIntent} to send when the notification is cleared by the user | |
- * directly from the notification panel. For example, this intent is sent when the user | |
- * clicks the "Clear all" button, or the individual "X" buttons on notifications. This | |
- * intent is not sent when the application calls {@link NotificationManager#cancel | |
- * NotificationManager.cancel(int)}. | |
+ * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. | |
+ * | |
+ * @see Notification#deleteIntent | |
*/ | |
public Builder setDeleteIntent(PendingIntent intent) { | |
mDeleteIntent = intent; | |
@@ -801,6 +1145,8 @@ | |
* @param intent The pending intent to launch. | |
* @param highPriority Passing true will cause this notification to be sent | |
* even if other notifications are suppressed. | |
+ * | |
+ * @see Notification#fullScreenIntent | |
*/ | |
public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { | |
mFullScreenIntent = intent; | |
@@ -809,8 +1155,10 @@ | |
} | |
/** | |
- * Set the text that is displayed in the status bar when the notification first | |
+ * Set the "ticker" text which is displayed in the status bar when the notification first | |
* arrives. | |
+ * | |
+ * @see Notification#tickerText | |
*/ | |
public Builder setTicker(CharSequence tickerText) { | |
mTickerText = tickerText; | |
@@ -821,6 +1169,9 @@ | |
* Set the text that is displayed in the status bar when the notification first | |
* arrives, and also a RemoteViews object that may be displayed instead on some | |
* devices. | |
+ * | |
+ * @see Notification#tickerText | |
+ * @see Notification#tickerView | |
*/ | |
public Builder setTicker(CharSequence tickerText, RemoteViews views) { | |
mTickerText = tickerText; | |
@@ -829,7 +1180,12 @@ | |
} | |
/** | |
- * Set the large icon that is shown in the ticker and notification. | |
+ * Add a large icon to the notification (and the ticker on some devices). | |
+ * | |
+ * In the platform template, this image will be shown on the left of the notification view | |
+ * in place of the {@link #setSmallIcon(int) small icon} (which will move to the right side). | |
+ * | |
+ * @see Notification#largeIcon | |
*/ | |
public Builder setLargeIcon(Bitmap icon) { | |
mLargeIcon = icon; | |
@@ -837,7 +1193,11 @@ | |
} | |
/** | |
- * Set the sound to play. It will play on the default stream. | |
+ * Set the sound to play. | |
+ * | |
+ * It will be played on the {@link #STREAM_DEFAULT default stream} for notifications. | |
+ * | |
+ * @see Notification#sound | |
*/ | |
public Builder setSound(Uri sound) { | |
mSound = sound; | |
@@ -846,10 +1206,11 @@ | |
} | |
/** | |
- * Set the sound to play. It will play on the stream you supply. | |
+ * Set the sound to play, along with a specific stream on which to play it. | |
+ * | |
+ * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. | |
* | |
- * @see #STREAM_DEFAULT | |
- * @see AudioManager for the <code>STREAM_</code> constants. | |
+ * @see Notification#sound | |
*/ | |
public Builder setSound(Uri sound, int streamType) { | |
mSound = sound; | |
@@ -860,8 +1221,12 @@ | |
/** | |
* Set the vibration pattern to use. | |
* | |
- * @see android.os.Vibrator for a discussion of the <code>pattern</code> | |
- * parameter. | |
+ | |
+ * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the | |
+ * <code>pattern</code> parameter. | |
+ * | |
+ | |
+ * @see Notification#vibrate | |
*/ | |
public Builder setVibrate(long[] pattern) { | |
mVibrate = pattern; | |
@@ -869,9 +1234,16 @@ | |
} | |
/** | |
- * Set the argb value that you would like the LED on the device to blnk, as well as the | |
- * rate. The rate is specified in terms of the number of milliseconds to be on | |
- * and then the number of milliseconds to be off. | |
+ * Set the desired color for the indicator LED on the device, as well as the | |
+ * blink duty cycle (specified in milliseconds). | |
+ * | |
+ | |
+ * Not all devices will honor all (or even any) of these values. | |
+ * | |
+ | |
+ * @see Notification#ledARGB | |
+ * @see Notification#ledOnMS | |
+ * @see Notification#ledOffMS | |
*/ | |
public Builder setLights(int argb, int onMs, int offMs) { | |
mLedArgb = argb; | |
@@ -881,15 +1253,20 @@ | |
} | |
/** | |
- * Set whether this is an ongoing notification. | |
+ * Set whether this is an "ongoing" notification. | |
+ * | |
+ | |
+ * Ongoing notifications cannot be dismissed by the user, so your application or service | |
+ * must take care of canceling them. | |
+ * | |
+ | |
+ * They are typically used to indicate a background task that the user is actively engaged | |
+ * with (e.g., playing music) or is pending in some way and therefore occupying the device | |
+ * (e.g., a file download, sync operation, active network connection). | |
* | |
- * <p>Ongoing notifications differ from regular notifications in the following ways: | |
- * <ul> | |
- * <li>Ongoing notifications are sorted above the regular notifications in the | |
- * notification panel.</li> | |
- * <li>Ongoing notifications do not have an 'X' close button, and are not affected | |
- * by the "Clear all" button. | |
- * </ul> | |
+ | |
+ * @see Notification#FLAG_ONGOING_EVENT | |
+ * @see Service#setForeground(boolean) | |
*/ | |
public Builder setOngoing(boolean ongoing) { | |
setFlag(FLAG_ONGOING_EVENT, ongoing); | |
@@ -899,6 +1276,8 @@ | |
/** | |
* Set this flag if you would only like the sound, vibrate | |
* and ticker to be played if the notification is not already showing. | |
+ * | |
+ * @see Notification#FLAG_ONLY_ALERT_ONCE | |
*/ | |
public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { | |
setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); | |
@@ -906,10 +1285,10 @@ | |
} | |
/** | |
- * Setting this flag will make it so the notification is automatically | |
- * canceled when the user clicks it in the panel. The PendingIntent | |
- * set with {@link #setDeleteIntent} will be broadcast when the notification | |
- * is canceled. | |
+ * Make this notification automatically dismissed when the user touches it. The | |
+ * PendingIntent set with {@link #setDeleteIntent} will be sent when this happens. | |
+ * | |
+ * @see Notification#FLAG_AUTO_CANCEL | |
*/ | |
public Builder setAutoCancel(boolean autoCancel) { | |
setFlag(FLAG_AUTO_CANCEL, autoCancel); | |
@@ -917,7 +1296,7 @@ | |
} | |
/** | |
- * Set the default notification options that will be used. | |
+ * Set which notification properties will be inherited from system defaults. | |
* <p> | |
* The value should be one or more of the following fields combined with | |
* bitwise-or: | |
@@ -930,6 +1309,69 @@ | |
return this; | |
} | |
+ /** | |
+ * Set the priority of this notification. | |
+ * | |
+ * @see Notification#priority | |
+ */ | |
+ public Builder setPriority(int pri) { | |
+ mPriority = pri; | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * @hide | |
+ * | |
+ * Add a kind (category) to this notification. Optional. | |
+ * | |
+ * @see Notification#kind | |
+ */ | |
+ public Builder addKind(String k) { | |
+ mKindList.add(k); | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Add metadata to this notification. | |
+ * | |
+ * A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's | |
+ * current contents are copied into the Notification each time {@link #build()} is | |
+ * called. | |
+ * | |
+ * @see Notification#extras | |
+ * @hide | |
+ */ | |
+ public Builder setExtras(Bundle bag) { | |
+ mExtras = bag; | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Add an action to this notification. Actions are typically displayed by | |
+ * the system as a button adjacent to the notification content. | |
+ * | |
+ * @param icon Resource ID of a drawable that represents the action. | |
+ * @param title Text describing the action. | |
+ * @param intent PendingIntent to be fired when the action is invoked. | |
+ */ | |
+ public Builder addAction(int icon, CharSequence title, PendingIntent intent) { | |
+ mActions.add(new Action(icon, title, intent)); | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Add a rich notification style to be applied at build time. | |
+ * | |
+ * @param style Object responsible for modifying the notification style. | |
+ */ | |
+ public Builder setStyle(Style style) { | |
+ if (mStyle != style) { | |
+ mStyle = style; | |
+ mStyle.setBuilder(this); | |
+ } | |
+ return this; | |
+ } | |
+ | |
private void setFlag(int mask, boolean value) { | |
if (value) { | |
mFlags |= mask; | |
@@ -938,26 +1380,38 @@ | |
} | |
} | |
- private RemoteViews makeRemoteViews(int resId) { | |
+ private RemoteViews applyStandardTemplate(int resId, boolean fitIn1U) { | |
RemoteViews contentView = new RemoteViews(mContext.getPackageName(), resId); | |
- boolean hasLine3 = false; | |
+ boolean showLine3 = false; | |
+ boolean showLine2 = false; | |
+ int smallIconImageViewId = R.id.icon; | |
+ if (mLargeIcon != null) { | |
+ contentView.setImageViewBitmap(R.id.icon, mLargeIcon); | |
+ smallIconImageViewId = R.id.right_icon; | |
+ } | |
+ if (mPriority < PRIORITY_LOW) { | |
+ contentView.setInt(R.id.icon, | |
+ "setBackgroundResource", R.drawable.notification_template_icon_low_bg); | |
+ contentView.setInt(R.id.status_bar_latest_event_content, | |
+ "setBackgroundResource", R.drawable.notification_bg_low); | |
+ } | |
if (mSmallIcon != 0) { | |
- contentView.setImageViewResource(R.id.icon, mSmallIcon); | |
- contentView.setViewVisibility(R.id.icon, View.VISIBLE); | |
+ contentView.setImageViewResource(smallIconImageViewId, mSmallIcon); | |
+ contentView.setViewVisibility(smallIconImageViewId, View.VISIBLE); | |
} else { | |
- contentView.setViewVisibility(R.id.icon, View.GONE); | |
+ contentView.setViewVisibility(smallIconImageViewId, View.GONE); | |
} | |
if (mContentTitle != null) { | |
contentView.setTextViewText(R.id.title, mContentTitle); | |
} | |
if (mContentText != null) { | |
contentView.setTextViewText(R.id.text, mContentText); | |
- hasLine3 = true; | |
+ showLine3 = true; | |
} | |
if (mContentInfo != null) { | |
contentView.setTextViewText(R.id.info, mContentInfo); | |
contentView.setViewVisibility(R.id.info, View.VISIBLE); | |
- hasLine3 = true; | |
+ showLine3 = true; | |
} else if (mNumber > 0) { | |
final int tooBig = mContext.getResources().getInteger( | |
R.integer.status_bar_notification_info_maxnum); | |
@@ -969,31 +1423,84 @@ | |
contentView.setTextViewText(R.id.info, f.format(mNumber)); | |
} | |
contentView.setViewVisibility(R.id.info, View.VISIBLE); | |
- hasLine3 = true; | |
+ showLine3 = true; | |
} else { | |
contentView.setViewVisibility(R.id.info, View.GONE); | |
} | |
- if (mProgressMax != 0 || mProgressIndeterminate) { | |
- contentView.setProgressBar( | |
- R.id.progress, mProgressMax, mProgress, mProgressIndeterminate); | |
- contentView.setViewVisibility(R.id.progress, View.VISIBLE); | |
+ | |
+ // Need to show three lines? | |
+ if (mSubText != null) { | |
+ contentView.setTextViewText(R.id.text, mSubText); | |
+ if (mContentText != null) { | |
+ contentView.setTextViewText(R.id.text2, mContentText); | |
+ contentView.setViewVisibility(R.id.text2, View.VISIBLE); | |
+ showLine2 = true; | |
+ } else { | |
+ contentView.setViewVisibility(R.id.text2, View.GONE); | |
+ } | |
} else { | |
- contentView.setViewVisibility(R.id.progress, View.GONE); | |
+ contentView.setViewVisibility(R.id.text2, View.GONE); | |
+ if (mProgressMax != 0 || mProgressIndeterminate) { | |
+ contentView.setProgressBar( | |
+ R.id.progress, mProgressMax, mProgress, mProgressIndeterminate); | |
+ contentView.setViewVisibility(R.id.progress, View.VISIBLE); | |
+ showLine2 = true; | |
+ } else { | |
+ contentView.setViewVisibility(R.id.progress, View.GONE); | |
+ } | |
} | |
+ if (showLine2) { | |
+ if (fitIn1U) { | |
+ // need to shrink all the type to make sure everything fits | |
+ final Resources res = mContext.getResources(); | |
+ final float subTextSize = res.getDimensionPixelSize( | |
+ R.dimen.notification_subtext_size); | |
+ contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize); | |
+ } | |
+ // vertical centering | |
+ contentView.setViewPadding(R.id.line1, 0, 0, 0, 0); | |
+ } | |
+ | |
if (mWhen != 0) { | |
- contentView.setLong(R.id.time, "setTime", mWhen); | |
+ if (mUseChronometer) { | |
+ contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); | |
+ contentView.setLong(R.id.chronometer, "setBase", | |
+ mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); | |
+ contentView.setBoolean(R.id.chronometer, "setStarted", true); | |
+ } else { | |
+ contentView.setViewVisibility(R.id.time, View.VISIBLE); | |
+ contentView.setLong(R.id.time, "setTime", mWhen); | |
+ } | |
} | |
- contentView.setViewVisibility(R.id.line3, hasLine3 ? View.VISIBLE : View.GONE); | |
+ contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE); | |
+ contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE); | |
return contentView; | |
} | |
+ private RemoteViews applyStandardTemplateWithActions(int layoutId) { | |
+ RemoteViews big = applyStandardTemplate(layoutId, false); | |
+ | |
+ int N = mActions.size(); | |
+ if (N > 0) { | |
+ // Log.d("Notification", "has actions: " + mContentText); | |
+ big.setViewVisibility(R.id.actions, View.VISIBLE); | |
+ big.setViewVisibility(R.id.action_divider, View.VISIBLE); | |
+ if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; | |
+ big.removeAllViews(R.id.actions); | |
+ for (int i=0; i<N; i++) { | |
+ final RemoteViews button = generateActionButton(mActions.get(i)); | |
+ //Log.d("Notification", "adding action " + i + ": " + mActions.get(i).title); | |
+ big.addView(R.id.actions, button); | |
+ } | |
+ } | |
+ return big; | |
+ } | |
+ | |
private RemoteViews makeContentView() { | |
if (mContentView != null) { | |
return mContentView; | |
} else { | |
- return makeRemoteViews(mLargeIcon == null | |
- ? R.layout.status_bar_latest_event_content | |
- : R.layout.status_bar_latest_event_content_large_icon); | |
+ return applyStandardTemplate(R.layout.notification_template_base, true); // no more special large_icon flavor | |
} | |
} | |
@@ -1002,20 +1509,39 @@ | |
return mTickerView; | |
} else { | |
if (mContentView == null) { | |
- return makeRemoteViews(mLargeIcon == null | |
+ return applyStandardTemplate(mLargeIcon == null | |
? R.layout.status_bar_latest_event_ticker | |
- : R.layout.status_bar_latest_event_ticker_large_icon); | |
+ : R.layout.status_bar_latest_event_ticker_large_icon, true); | |
} else { | |
return null; | |
} | |
} | |
} | |
+ private RemoteViews makeBigContentView() { | |
+ if (mActions.size() == 0) return null; | |
+ | |
+ return applyStandardTemplateWithActions(R.layout.notification_template_big_base); | |
+ } | |
+ | |
+ private RemoteViews generateActionButton(Action action) { | |
+ final boolean tombstone = (action.actionIntent == null); | |
+ RemoteViews button = new RemoteViews(mContext.getPackageName(), | |
+ tombstone ? R.layout.notification_action_tombstone | |
+ : R.layout.notification_action); | |
+ button.setTextViewCompoundDrawables(R.id.action0, action.icon, 0, 0, 0); | |
+ button.setTextViewText(R.id.action0, action.title); | |
+ if (!tombstone) { | |
+ button.setOnClickPendingIntent(R.id.action0, action.actionIntent); | |
+ } | |
+ button.setContentDescription(R.id.action0, action.title); | |
+ return button; | |
+ } | |
+ | |
/** | |
- * Combine all of the options that have been set and return a new {@link Notification} | |
- * object. | |
+ * Apply the unstyled operations and return a new {@link Notification} object. | |
*/ | |
- public Notification getNotification() { | |
+ private Notification buildUnstyled() { | |
Notification n = new Notification(); | |
n.when = mWhen; | |
n.icon = mSmallIcon; | |
@@ -1036,13 +1562,374 @@ | |
n.ledOffMS = mLedOffMs; | |
n.defaults = mDefaults; | |
n.flags = mFlags; | |
+ n.bigContentView = makeBigContentView(); | |
if (mLedOnMs != 0 && mLedOffMs != 0) { | |
n.flags |= FLAG_SHOW_LIGHTS; | |
} | |
if ((mDefaults & DEFAULT_LIGHTS) != 0) { | |
n.flags |= FLAG_SHOW_LIGHTS; | |
} | |
+ if (mKindList.size() > 0) { | |
+ n.kind = new String[mKindList.size()]; | |
+ mKindList.toArray(n.kind); | |
+ } else { | |
+ n.kind = null; | |
+ } | |
+ n.priority = mPriority; | |
+ n.extras = mExtras != null ? new Bundle(mExtras) : null; | |
+ if (mActions.size() > 0) { | |
+ n.actions = new Action[mActions.size()]; | |
+ mActions.toArray(n.actions); | |
+ } | |
return n; | |
} | |
+ | |
+ /** | |
+ * @deprecated Use {@link #build()} instead. | |
+ */ | |
+ @Deprecated | |
+ public Notification getNotification() { | |
+ return build(); | |
+ } | |
+ | |
+ /** | |
+ * Combine all of the options that have been set and return a new {@link Notification} | |
+ * object. | |
+ */ | |
+ public Notification build() { | |
+ if (mStyle != null) { | |
+ return mStyle.build(); | |
+ } else { | |
+ return buildUnstyled(); | |
+ } | |
+ } | |
+ } | |
+ | |
+ | |
+ /** | |
+ * An object that can apply a rich notification style to a {@link Notification.Builder} | |
+ * object. | |
+ */ | |
+ public static abstract class Style | |
+ { | |
+ private CharSequence mBigContentTitle; | |
+ private CharSequence mSummaryText = null; | |
+ private boolean mSummaryTextSet = false; | |
+ | |
+ protected Builder mBuilder; | |
+ | |
+ /** | |
+ * Overrides ContentTitle in the big form of the template. | |
+ * This defaults to the value passed to setContentTitle(). | |
+ */ | |
+ protected void internalSetBigContentTitle(CharSequence title) { | |
+ mBigContentTitle = title; | |
+ } | |
+ | |
+ /** | |
+ * Set the first line of text after the detail section in the big form of the template. | |
+ */ | |
+ protected void internalSetSummaryText(CharSequence cs) { | |
+ mSummaryText = cs; | |
+ mSummaryTextSet = true; | |
+ } | |
+ | |
+ public void setBuilder(Builder builder) { | |
+ if (mBuilder != builder) { | |
+ mBuilder = builder; | |
+ mBuilder.setStyle(this); | |
+ } | |
+ } | |
+ | |
+ protected void checkBuilder() { | |
+ if (mBuilder == null) { | |
+ throw new IllegalArgumentException("Style requires a valid Builder object"); | |
+ } | |
+ } | |
+ | |
+ protected RemoteViews getStandardView(int layoutId) { | |
+ checkBuilder(); | |
+ | |
+ if (mBigContentTitle != null) { | |
+ mBuilder.setContentTitle(mBigContentTitle); | |
+ } | |
+ | |
+ RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId); | |
+ | |
+ if (mBigContentTitle != null && mBigContentTitle.equals("")) { | |
+ contentView.setViewVisibility(R.id.line1, View.GONE); | |
+ } else { | |
+ contentView.setViewVisibility(R.id.line1, View.VISIBLE); | |
+ } | |
+ | |
+ // The last line defaults to the subtext, but can be replaced by mSummaryText | |
+ final CharSequence overflowText = | |
+ mSummaryTextSet ? mSummaryText | |
+ : mBuilder.mSubText; | |
+ if (overflowText != null) { | |
+ contentView.setTextViewText(R.id.text, overflowText); | |
+ contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE); | |
+ contentView.setViewVisibility(R.id.line3, View.VISIBLE); | |
+ } else { | |
+ contentView.setViewVisibility(R.id.overflow_divider, View.GONE); | |
+ contentView.setViewVisibility(R.id.line3, View.GONE); | |
+ } | |
+ | |
+ return contentView; | |
+ } | |
+ | |
+ public abstract Notification build(); | |
+ } | |
+ | |
+ /** | |
+ * Helper class for generating large-format notifications that include a large image attachment. | |
+ * | |
+ * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: | |
+ * <pre class="prettyprint"> | |
+ * Notification noti = new Notification.BigPictureStyle( | |
+ * new Notification.Builder() | |
+ * .setContentTitle("New photo from " + sender.toString()) | |
+ * .setContentText(subject) | |
+ * .setSmallIcon(R.drawable.new_post) | |
+ * .setLargeIcon(aBitmap)) | |
+ * .bigPicture(aBigBitmap) | |
+ * .build(); | |
+ * </pre> | |
+ * | |
+ * @see Notification#bigContentView | |
+ */ | |
+ public static class BigPictureStyle extends Style { | |
+ private Bitmap mPicture; | |
+ private Bitmap mBigLargeIcon; | |
+ private boolean mBigLargeIconSet = false; | |
+ | |
+ public BigPictureStyle() { | |
+ } | |
+ | |
+ public BigPictureStyle(Builder builder) { | |
+ setBuilder(builder); | |
+ } | |
+ | |
+ /** | |
+ * Overrides ContentTitle in the big form of the template. | |
+ * This defaults to the value passed to setContentTitle(). | |
+ */ | |
+ public BigPictureStyle setBigContentTitle(CharSequence title) { | |
+ internalSetBigContentTitle(title); | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Set the first line of text after the detail section in the big form of the template. | |
+ */ | |
+ public BigPictureStyle setSummaryText(CharSequence cs) { | |
+ internalSetSummaryText(cs); | |
+ return this; | |
+ } | |
+ | |
+ public BigPictureStyle bigPicture(Bitmap b) { | |
+ mPicture = b; | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Override the large icon when the big notification is shown. | |
+ */ | |
+ public BigPictureStyle bigLargeIcon(Bitmap b) { | |
+ mBigLargeIconSet = true; | |
+ mBigLargeIcon = b; | |
+ return this; | |
+ } | |
+ | |
+ private RemoteViews makeBigContentView() { | |
+ RemoteViews contentView = getStandardView(R.layout.notification_template_big_picture); | |
+ | |
+ contentView.setImageViewBitmap(R.id.big_picture, mPicture); | |
+ | |
+ return contentView; | |
+ } | |
+ | |
+ @Override | |
+ public Notification build() { | |
+ checkBuilder(); | |
+ Notification wip = mBuilder.buildUnstyled(); | |
+ if (mBigLargeIconSet ) { | |
+ mBuilder.mLargeIcon = mBigLargeIcon; | |
+ } | |
+ wip.bigContentView = makeBigContentView(); | |
+ return wip; | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Helper class for generating large-format notifications that include a lot of text. | |
+ * | |
+ * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: | |
+ * <pre class="prettyprint"> | |
+ * Notification noti = new Notification.BigPictureStyle( | |
+ * new Notification.Builder() | |
+ * .setContentTitle("New mail from " + sender.toString()) | |
+ * .setContentText(subject) | |
+ * .setSmallIcon(R.drawable.new_mail) | |
+ * .setLargeIcon(aBitmap)) | |
+ * .bigText(aVeryLongString) | |
+ * .build(); | |
+ * </pre> | |
+ * | |
+ * @see Notification#bigContentView | |
+ */ | |
+ public static class BigTextStyle extends Style { | |
+ private CharSequence mBigText; | |
+ | |
+ public BigTextStyle() { | |
+ } | |
+ | |
+ public BigTextStyle(Builder builder) { | |
+ setBuilder(builder); | |
+ } | |
+ | |
+ /** | |
+ * Overrides ContentTitle in the big form of the template. | |
+ * This defaults to the value passed to setContentTitle(). | |
+ */ | |
+ public BigTextStyle setBigContentTitle(CharSequence title) { | |
+ internalSetBigContentTitle(title); | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Set the first line of text after the detail section in the big form of the template. | |
+ */ | |
+ public BigTextStyle setSummaryText(CharSequence cs) { | |
+ internalSetSummaryText(cs); | |
+ return this; | |
+ } | |
+ | |
+ public BigTextStyle bigText(CharSequence cs) { | |
+ mBigText = cs; | |
+ return this; | |
+ } | |
+ | |
+ private RemoteViews makeBigContentView() { | |
+ // Remove the content text so line3 only shows if you have a summary | |
+ final boolean hadThreeLines = (mBuilder.mContentText != null && mBuilder.mSubText != null); | |
+ mBuilder.mContentText = null; | |
+ | |
+ RemoteViews contentView = getStandardView(R.layout.notification_template_big_text); | |
+ | |
+ if (hadThreeLines) { | |
+ // vertical centering | |
+ contentView.setViewPadding(R.id.line1, 0, 0, 0, 0); | |
+ } | |
+ | |
+ contentView.setTextViewText(R.id.big_text, mBigText); | |
+ contentView.setViewVisibility(R.id.big_text, View.VISIBLE); | |
+ contentView.setViewVisibility(R.id.text2, View.GONE); | |
+ | |
+ return contentView; | |
+ } | |
+ | |
+ @Override | |
+ public Notification build() { | |
+ checkBuilder(); | |
+ Notification wip = mBuilder.buildUnstyled(); | |
+ wip.bigContentView = makeBigContentView(); | |
+ return wip; | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Helper class for generating large-format notifications that include a list of (up to 5) strings. | |
+ * | |
+ * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: | |
+ * <pre class="prettyprint"> | |
+ * Notification noti = new Notification.InboxStyle( | |
+ * new Notification.Builder() | |
+ * .setContentTitle("5 New mails from " + sender.toString()) | |
+ * .setContentText(subject) | |
+ * .setSmallIcon(R.drawable.new_mail) | |
+ * .setLargeIcon(aBitmap)) | |
+ * .addLine(str1) | |
+ * .addLine(str2) | |
+ * .setContentTitle("") | |
+ * .setSummaryText("+3 more") | |
+ * .build(); | |
+ * </pre> | |
+ * | |
+ * @see Notification#bigContentView | |
+ */ | |
+ public static class InboxStyle extends Style { | |
+ private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); | |
+ | |
+ public InboxStyle() { | |
+ } | |
+ | |
+ public InboxStyle(Builder builder) { | |
+ setBuilder(builder); | |
+ } | |
+ | |
+ /** | |
+ * Overrides ContentTitle in the big form of the template. | |
+ * This defaults to the value passed to setContentTitle(). | |
+ */ | |
+ public InboxStyle setBigContentTitle(CharSequence title) { | |
+ internalSetBigContentTitle(title); | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Set the first line of text after the detail section in the big form of the template. | |
+ */ | |
+ public InboxStyle setSummaryText(CharSequence cs) { | |
+ internalSetSummaryText(cs); | |
+ return this; | |
+ } | |
+ | |
+ public InboxStyle addLine(CharSequence cs) { | |
+ mTexts.add(cs); | |
+ return this; | |
+ } | |
+ | |
+ private RemoteViews makeBigContentView() { | |
+ // Remove the content text so line3 disappears unless you have a summary | |
+ mBuilder.mContentText = null; | |
+ RemoteViews contentView = getStandardView(R.layout.notification_template_inbox); | |
+ | |
+ contentView.setViewVisibility(R.id.text2, View.GONE); | |
+ | |
+ int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, | |
+ R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; | |
+ | |
+ // Make sure all rows are gone in case we reuse a view. | |
+ for (int rowId : rowIds) { | |
+ contentView.setViewVisibility(rowId, View.GONE); | |
+ } | |
+ | |
+ int i=0; | |
+ while (i < mTexts.size() && i < rowIds.length) { | |
+ CharSequence str = mTexts.get(i); | |
+ if (str != null && !str.equals("")) { | |
+ contentView.setViewVisibility(rowIds[i], View.VISIBLE); | |
+ contentView.setTextViewText(rowIds[i], str); | |
+ } | |
+ i++; | |
+ } | |
+ | |
+ if (mTexts.size() > rowIds.length) { | |
+ contentView.setViewVisibility(R.id.inbox_more, View.VISIBLE); | |
+ } else { | |
+ contentView.setViewVisibility(R.id.inbox_more, View.GONE); | |
+ } | |
+ | |
+ return contentView; | |
+ } | |
+ | |
+ @Override | |
+ public Notification build() { | |
+ checkBuilder(); | |
+ Notification wip = mBuilder.buildUnstyled(); | |
+ wip.bigContentView = makeBigContentView(); | |
+ return wip; | |
+ } | |
} | |
} | |
diff -Nur android-15/android/app/PendingIntent.java android-16/android/app/PendingIntent.java | |
--- android-15/android/app/PendingIntent.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/PendingIntent.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -188,6 +188,35 @@ | |
*/ | |
public static PendingIntent getActivity(Context context, int requestCode, | |
Intent intent, int flags) { | |
+ return getActivity(context, requestCode, intent, flags, null); | |
+ } | |
+ | |
+ /** | |
+ * Retrieve a PendingIntent that will start a new activity, like calling | |
+ * {@link Context#startActivity(Intent) Context.startActivity(Intent)}. | |
+ * Note that the activity will be started outside of the context of an | |
+ * existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK | |
+ * Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent. | |
+ * | |
+ * @param context The Context in which this PendingIntent should start | |
+ * the activity. | |
+ * @param requestCode Private request code for the sender (currently | |
+ * not used). | |
+ * @param intent Intent of the activity to be launched. | |
+ * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE}, | |
+ * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT}, | |
+ * or any of the flags as supported by | |
+ * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts | |
+ * of the intent that can be supplied when the actual send happens. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * May be null if there are no options. | |
+ * | |
+ * @return Returns an existing or new PendingIntent matching the given | |
+ * parameters. May return null only if {@link #FLAG_NO_CREATE} has been | |
+ * supplied. | |
+ */ | |
+ public static PendingIntent getActivity(Context context, int requestCode, | |
+ Intent intent, int flags, Bundle options) { | |
String packageName = context.getPackageName(); | |
String resolvedType = intent != null ? intent.resolveTypeIfNeeded( | |
context.getContentResolver()) : null; | |
@@ -195,9 +224,10 @@ | |
intent.setAllowFds(false); | |
IIntentSender target = | |
ActivityManagerNative.getDefault().getIntentSender( | |
- IActivityManager.INTENT_SENDER_ACTIVITY, packageName, | |
+ ActivityManager.INTENT_SENDER_ACTIVITY, packageName, | |
null, null, requestCode, new Intent[] { intent }, | |
- resolvedType != null ? new String[] { resolvedType } : null, flags); | |
+ resolvedType != null ? new String[] { resolvedType } : null, | |
+ flags, options); | |
return target != null ? new PendingIntent(target) : null; | |
} catch (RemoteException e) { | |
} | |
@@ -247,6 +277,52 @@ | |
*/ | |
public static PendingIntent getActivities(Context context, int requestCode, | |
Intent[] intents, int flags) { | |
+ return getActivities(context, requestCode, intents, flags, null); | |
+ } | |
+ | |
+ /** | |
+ * Like {@link #getActivity(Context, int, Intent, int)}, but allows an | |
+ * array of Intents to be supplied. The first Intent in the array is | |
+ * taken as the primary key for the PendingIntent, like the single Intent | |
+ * given to {@link #getActivity(Context, int, Intent, int)}. Upon sending | |
+ * the resulting PendingIntent, all of the Intents are started in the same | |
+ * way as they would be by passing them to {@link Context#startActivities(Intent[])}. | |
+ * | |
+ * <p class="note"> | |
+ * The <em>first</em> intent in the array will be started outside of the context of an | |
+ * existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK | |
+ * Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent. (Activities after | |
+ * the first in the array are started in the context of the previous activity | |
+ * in the array, so FLAG_ACTIVITY_NEW_TASK is not needed nor desired for them.) | |
+ * </p> | |
+ * | |
+ * <p class="note"> | |
+ * The <em>last</em> intent in the array represents the key for the | |
+ * PendingIntent. In other words, it is the significant element for matching | |
+ * (as done with the single intent given to {@link #getActivity(Context, int, Intent, int)}, | |
+ * its content will be the subject of replacement by | |
+ * {@link #send(Context, int, Intent)} and {@link #FLAG_UPDATE_CURRENT}, etc. | |
+ * This is because it is the most specific of the supplied intents, and the | |
+ * UI the user actually sees when the intents are started. | |
+ * </p> | |
+ * | |
+ * @param context The Context in which this PendingIntent should start | |
+ * the activity. | |
+ * @param requestCode Private request code for the sender (currently | |
+ * not used). | |
+ * @param intents Array of Intents of the activities to be launched. | |
+ * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE}, | |
+ * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT}, | |
+ * or any of the flags as supported by | |
+ * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts | |
+ * of the intent that can be supplied when the actual send happens. | |
+ * | |
+ * @return Returns an existing or new PendingIntent matching the given | |
+ * parameters. May return null only if {@link #FLAG_NO_CREATE} has been | |
+ * supplied. | |
+ */ | |
+ public static PendingIntent getActivities(Context context, int requestCode, | |
+ Intent[] intents, int flags, Bundle options) { | |
String packageName = context.getPackageName(); | |
String[] resolvedTypes = new String[intents.length]; | |
for (int i=0; i<intents.length; i++) { | |
@@ -256,8 +332,8 @@ | |
try { | |
IIntentSender target = | |
ActivityManagerNative.getDefault().getIntentSender( | |
- IActivityManager.INTENT_SENDER_ACTIVITY, packageName, | |
- null, null, requestCode, intents, resolvedTypes, flags); | |
+ ActivityManager.INTENT_SENDER_ACTIVITY, packageName, | |
+ null, null, requestCode, intents, resolvedTypes, flags, options); | |
return target != null ? new PendingIntent(target) : null; | |
} catch (RemoteException e) { | |
} | |
@@ -292,9 +368,10 @@ | |
intent.setAllowFds(false); | |
IIntentSender target = | |
ActivityManagerNative.getDefault().getIntentSender( | |
- IActivityManager.INTENT_SENDER_BROADCAST, packageName, | |
+ ActivityManager.INTENT_SENDER_BROADCAST, packageName, | |
null, null, requestCode, new Intent[] { intent }, | |
- resolvedType != null ? new String[] { resolvedType } : null, flags); | |
+ resolvedType != null ? new String[] { resolvedType } : null, | |
+ flags, null); | |
return target != null ? new PendingIntent(target) : null; | |
} catch (RemoteException e) { | |
} | |
@@ -330,9 +407,10 @@ | |
intent.setAllowFds(false); | |
IIntentSender target = | |
ActivityManagerNative.getDefault().getIntentSender( | |
- IActivityManager.INTENT_SENDER_SERVICE, packageName, | |
+ ActivityManager.INTENT_SENDER_SERVICE, packageName, | |
null, null, requestCode, new Intent[] { intent }, | |
- resolvedType != null ? new String[] { resolvedType } : null, flags); | |
+ resolvedType != null ? new String[] { resolvedType } : null, | |
+ flags, null); | |
return target != null ? new PendingIntent(target) : null; | |
} catch (RemoteException e) { | |
} | |
@@ -351,7 +429,7 @@ | |
/** | |
* Cancel a currently active PendingIntent. Only the original application | |
- * owning an PendingIntent can cancel it. | |
+ * owning a PendingIntent can cancel it. | |
*/ | |
public void cancel() { | |
try { | |
diff -Nur android-15/android/app/SearchDialog.java android-16/android/app/SearchDialog.java | |
--- android-15/android/app/SearchDialog.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/SearchDialog.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -551,7 +551,6 @@ | |
try { | |
// If the intent was created from a suggestion, it will always have an explicit | |
// component here. | |
- Log.i(LOG_TAG, "Starting (as ourselves) " + intent.toUri(0)); | |
getContext().startActivity(intent); | |
// If the search switches to a different activity, | |
// SearchDialogWrapper#performActivityResuming | |
diff -Nur android-15/android/app/SearchManager.java android-16/android/app/SearchManager.java | |
--- android-15/android/app/SearchManager.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/SearchManager.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -22,6 +22,7 @@ | |
import android.content.Context; | |
import android.content.DialogInterface; | |
import android.content.Intent; | |
+import android.content.pm.PackageManager; | |
import android.content.pm.ResolveInfo; | |
import android.database.Cursor; | |
import android.graphics.Rect; | |
@@ -32,6 +33,7 @@ | |
import android.os.ServiceManager; | |
import android.text.TextUtils; | |
import android.util.Log; | |
+import android.util.Slog; | |
import android.view.KeyEvent; | |
import java.util.List; | |
@@ -395,11 +397,7 @@ | |
/** | |
* Intent action to be broadcast to inform that the global search provider | |
- * has changed. Normal components will have no need to handle this intent since | |
- * they should be using API methods from this class to access the global search | |
- * activity | |
- * | |
- * @hide | |
+ * has changed. | |
*/ | |
public final static String INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED | |
= "android.search.action.GLOBAL_SEARCH_ACTIVITY_CHANGED"; | |
@@ -590,8 +588,6 @@ | |
/** | |
* Gets the name of the global search activity. | |
- * | |
- * @hide | |
*/ | |
public ComponentName getGlobalSearchActivity() { | |
try { | |
@@ -843,4 +839,20 @@ | |
} | |
} | |
+ /** | |
+ * Gets an intent for launching installed assistant activity, or null if not available. | |
+ * @return The assist intent. | |
+ * | |
+ * @hide | |
+ */ | |
+ public static final Intent getAssistIntent(Context context) { | |
+ PackageManager pm = context.getPackageManager(); | |
+ Intent intent = new Intent(Intent.ACTION_ASSIST); | |
+ ComponentName component = intent.resolveActivity(pm); | |
+ if (component != null) { | |
+ intent.setComponent(component); | |
+ return intent; | |
+ } | |
+ return null; | |
+ } | |
} | |
diff -Nur android-15/android/app/Service.java android-16/android/app/Service.java | |
--- android-15/android/app/Service.java 2012-06-18 20:00:44.000000000 +0900 | |
+++ android-16/android/app/Service.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -163,7 +163,19 @@ | |
* {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>} | |
* element in their own manifest to be able to start, stop, or bind to | |
* the service. | |
- * | |
+ * | |
+ * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, when using | |
+ * {@link Context#startService(Intent) Context.startService(Intent)}, you can | |
+ * also set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION | |
+ * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION | |
+ * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} on the Intent. This will grant the | |
+ * Service temporary access to the specific URIs in the Intent. Access will | |
+ * remain until the Service has called {@link #stopSelf(int)} for that start | |
+ * command or a later one, or until the Service has been completely stopped. | |
+ * This works for granting access to the other apps that have not requested | |
+ * the permission protecting the Service, or even when the Service is not | |
+ * exported at all. | |
+ * | |
* <p>In addition, a service can protect individual IPC calls into it with | |
* permissions, by calling the | |
* {@link #checkCallingPermission} | |
@@ -441,7 +453,7 @@ | |
/** | |
* Called by the system to notify a Service that it is no longer used and is being removed. The | |
- * service should clean up an resources it holds (threads, registered | |
+ * service should clean up any resources it holds (threads, registered | |
* receivers, etc) at this point. Upon return, there will be no more calls | |
* in to this Service object and it is effectively dead. Do not call this method directly. | |
*/ | |
@@ -654,8 +666,8 @@ | |
/** | |
* Print the Service's state into the given stream. This gets invoked if | |
- * you run "adb shell dumpsys activity service <yourservicename>". | |
- * This is distinct from "dumpsys <servicename>", which only works for | |
+ * you run "adb shell dumpsys activity service <yourservicename>". | |
+ * This is distinct from "dumpsys <servicename>", which only works for | |
* named system services and which invokes the {@link IBinder#dump} method | |
* on the {@link IBinder} interface registered with ServiceManager. | |
* | |
diff -Nur android-15/android/app/StatusBarManager.java android-16/android/app/StatusBarManager.java | |
--- android-15/android/app/StatusBarManager.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/app/StatusBarManager.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -56,6 +56,11 @@ | |
| DISABLE_NOTIFICATION_ALERTS | DISABLE_NOTIFICATION_TICKER | |
| DISABLE_SYSTEM_INFO | DISABLE_RECENT | DISABLE_HOME | DISABLE_BACK | DISABLE_CLOCK; | |
+ public static final int NAVIGATION_HINT_BACK_NOP = 1 << 0; | |
+ public static final int NAVIGATION_HINT_HOME_NOP = 1 << 1; | |
+ public static final int NAVIGATION_HINT_RECENT_NOP = 1 << 2; | |
+ public static final int NAVIGATION_HINT_BACK_ALT = 1 << 3; | |
+ | |
private Context mContext; | |
private IStatusBarService mService; | |
private IBinder mToken = new Binder(); | |
diff -Nur android-15/android/app/TaskStackBuilder.java android-16/android/app/TaskStackBuilder.java | |
--- android-15/android/app/TaskStackBuilder.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/app/TaskStackBuilder.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -0,0 +1,307 @@ | |
+/* | |
+ * 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 android.app; | |
+ | |
+import android.content.ComponentName; | |
+import android.content.Context; | |
+import android.content.Intent; | |
+import android.content.pm.ActivityInfo; | |
+import android.content.pm.PackageManager; | |
+import android.content.pm.PackageManager.NameNotFoundException; | |
+import android.os.Bundle; | |
+import android.util.Log; | |
+ | |
+import java.util.ArrayList; | |
+ | |
+/** | |
+ * Utility class for constructing synthetic back stacks for cross-task navigation | |
+ * on Android 3.0 and newer. | |
+ * | |
+ * <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for | |
+ * app navigation using the back key changed. The back key's behavior is local | |
+ * to the current task and does not capture navigation across different tasks. | |
+ * Navigating across tasks and easily reaching the previous task is accomplished | |
+ * through the "recents" UI, accessible through the software-provided Recents key | |
+ * on the navigation or system bar. On devices with the older hardware button configuration | |
+ * the recents UI can be accessed with a long press on the Home key.</p> | |
+ * | |
+ * <p>When crossing from one task stack to another post-Android 3.0, | |
+ * the application should synthesize a back stack/history for the new task so that | |
+ * the user may navigate out of the new task and back to the Launcher by repeated | |
+ * presses of the back key. Back key presses should not navigate across task stacks.</p> | |
+ * | |
+ * <p>TaskStackBuilder provides a way to obey the correct conventions | |
+ * around cross-task navigation.</p> | |
+ * | |
+ * <div class="special reference"> | |
+ * <h3>About Navigation</h3> | |
+ * For more detailed information about tasks, the back stack, and navigation design guidelines, | |
+ * please read | |
+ * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a> | |
+ * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> | |
+ * from the design guide. | |
+ * </div> | |
+ */ | |
+public class TaskStackBuilder { | |
+ private static final String TAG = "TaskStackBuilder"; | |
+ | |
+ private final ArrayList<Intent> mIntents = new ArrayList<Intent>(); | |
+ private final Context mSourceContext; | |
+ | |
+ private TaskStackBuilder(Context a) { | |
+ mSourceContext = a; | |
+ } | |
+ | |
+ /** | |
+ * Return a new TaskStackBuilder for launching a fresh task stack consisting | |
+ * of a series of activities. | |
+ * | |
+ * @param context The context that will launch the new task stack or generate a PendingIntent | |
+ * @return A new TaskStackBuilder | |
+ */ | |
+ public static TaskStackBuilder create(Context context) { | |
+ return new TaskStackBuilder(context); | |
+ } | |
+ | |
+ /** | |
+ * Add a new Intent to the task stack. The most recently added Intent will invoke | |
+ * the Activity at the top of the final task stack. | |
+ * | |
+ * @param nextIntent Intent for the next Activity in the synthesized task stack | |
+ * @return This TaskStackBuilder for method chaining | |
+ */ | |
+ public TaskStackBuilder addNextIntent(Intent nextIntent) { | |
+ mIntents.add(nextIntent); | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Add a new Intent with the resolved chain of parents for the target activity to | |
+ * the task stack. | |
+ * | |
+ * <p>This is equivalent to calling {@link #addParentStack(ComponentName) addParentStack} | |
+ * with the resolved ComponentName of nextIntent (if it can be resolved), followed by | |
+ * {@link #addNextIntent(Intent) addNextIntent} with nextIntent.</p> | |
+ * | |
+ * @param nextIntent Intent for the topmost Activity in the synthesized task stack. | |
+ * Its chain of parents as specified in the manifest will be added. | |
+ * @return This TaskStackBuilder for method chaining. | |
+ */ | |
+ public TaskStackBuilder addNextIntentWithParentStack(Intent nextIntent) { | |
+ ComponentName target = nextIntent.getComponent(); | |
+ if (target == null) { | |
+ target = nextIntent.resolveActivity(mSourceContext.getPackageManager()); | |
+ } | |
+ if (target != null) { | |
+ addParentStack(target); | |
+ } | |
+ addNextIntent(nextIntent); | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Add the activity parent chain as specified by the | |
+ * {@link Activity#getParentActivityIntent() getParentActivityIntent()} method of the activity | |
+ * specified and the {@link android.R.attr#parentActivityName parentActivityName} attributes | |
+ * of each successive activity (or activity-alias) element in the application's manifest | |
+ * to the task stack builder. | |
+ * | |
+ * @param sourceActivity All parents of this activity will be added | |
+ * @return This TaskStackBuilder for method chaining | |
+ */ | |
+ public TaskStackBuilder addParentStack(Activity sourceActivity) { | |
+ final int insertAt = mIntents.size(); | |
+ Intent parent = sourceActivity.getParentActivityIntent(); | |
+ PackageManager pm = sourceActivity.getPackageManager(); | |
+ while (parent != null) { | |
+ mIntents.add(insertAt, parent); | |
+ try { | |
+ ActivityInfo info = pm.getActivityInfo(parent.getComponent(), 0); | |
+ String parentActivity = info.parentActivityName; | |
+ if (parentActivity != null) { | |
+ parent = new Intent().setComponent( | |
+ new ComponentName(mSourceContext, parentActivity)); | |
+ } else { | |
+ parent = null; | |
+ } | |
+ } catch (NameNotFoundException e) { | |
+ Log.e(TAG, "Bad ComponentName while traversing activity parent metadata"); | |
+ throw new IllegalArgumentException(e); | |
+ } | |
+ } | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Add the activity parent chain as specified by the | |
+ * {@link android.R.attr#parentActivityName parentActivityName} attribute of the activity | |
+ * (or activity-alias) element in the application's manifest to the task stack builder. | |
+ * | |
+ * @param sourceActivityClass All parents of this activity will be added | |
+ * @return This TaskStackBuilder for method chaining | |
+ */ | |
+ public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) { | |
+ final int insertAt = mIntents.size(); | |
+ PackageManager pm = mSourceContext.getPackageManager(); | |
+ try { | |
+ ActivityInfo info = pm.getActivityInfo( | |
+ new ComponentName(mSourceContext, sourceActivityClass), 0); | |
+ String parentActivity = info.parentActivityName; | |
+ while (parentActivity != null) { | |
+ Intent parent = new Intent().setComponent( | |
+ new ComponentName(mSourceContext, parentActivity)); | |
+ mIntents.add(insertAt, parent); | |
+ info = pm.getActivityInfo(parent.getComponent(), 0); | |
+ parentActivity = info.parentActivityName; | |
+ } | |
+ } catch (NameNotFoundException e) { | |
+ Log.e(TAG, "Bad ComponentName while traversing activity parent metadata"); | |
+ throw new IllegalArgumentException(e); | |
+ } | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * Add the activity parent chain as specified by the | |
+ * {@link android.R.attr#parentActivityName parentActivityName} attribute of the activity | |
+ * (or activity-alias) element in the application's manifest to the task stack builder. | |
+ * | |
+ * @param sourceActivityName Must specify an Activity component. All parents of | |
+ * this activity will be added | |
+ * @return This TaskStackBuilder for method chaining | |
+ */ | |
+ public TaskStackBuilder addParentStack(ComponentName sourceActivityName) { | |
+ final int insertAt = mIntents.size(); | |
+ PackageManager pm = mSourceContext.getPackageManager(); | |
+ try { | |
+ ActivityInfo info = pm.getActivityInfo(sourceActivityName, 0); | |
+ String parentActivity = info.parentActivityName; | |
+ while (parentActivity != null) { | |
+ Intent parent = new Intent().setComponent( | |
+ new ComponentName(info.packageName, parentActivity)); | |
+ mIntents.add(insertAt, parent); | |
+ info = pm.getActivityInfo(parent.getComponent(), 0); | |
+ parentActivity = info.parentActivityName; | |
+ } | |
+ } catch (NameNotFoundException e) { | |
+ Log.e(TAG, "Bad ComponentName while traversing activity parent metadata"); | |
+ throw new IllegalArgumentException(e); | |
+ } | |
+ return this; | |
+ } | |
+ | |
+ /** | |
+ * @return the number of intents added so far. | |
+ */ | |
+ public int getIntentCount() { | |
+ return mIntents.size(); | |
+ } | |
+ | |
+ /** | |
+ * Return the intent at the specified index for modification. | |
+ * Useful if you need to modify the flags or extras of an intent that was previously added, | |
+ * for example with {@link #addParentStack(Activity)}. | |
+ * | |
+ * @param index Index from 0-getIntentCount() | |
+ * @return the intent at position index | |
+ */ | |
+ public Intent editIntentAt(int index) { | |
+ return mIntents.get(index); | |
+ } | |
+ | |
+ /** | |
+ * Start the task stack constructed by this builder. | |
+ */ | |
+ public void startActivities() { | |
+ startActivities(null); | |
+ } | |
+ | |
+ /** | |
+ * Start the task stack constructed by this builder. | |
+ * | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. | |
+ */ | |
+ public void startActivities(Bundle options) { | |
+ if (mIntents.isEmpty()) { | |
+ throw new IllegalStateException( | |
+ "No intents added to TaskStackBuilder; cannot startActivities"); | |
+ } | |
+ | |
+ Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]); | |
+ intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | | |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK | | |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME); | |
+ mSourceContext.startActivities(intents, options); | |
+ } | |
+ | |
+ /** | |
+ * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far. | |
+ * | |
+ * @param requestCode Private request code for the sender | |
+ * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT}, | |
+ * {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT}, | |
+ * {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by | |
+ * {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the | |
+ * intent that can be supplied when the actual send happens. | |
+ * | |
+ * @return The obtained PendingIntent | |
+ */ | |
+ public PendingIntent getPendingIntent(int requestCode, int flags) { | |
+ return getPendingIntent(requestCode, flags, null); | |
+ } | |
+ | |
+ /** | |
+ * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far. | |
+ * | |
+ * @param requestCode Private request code for the sender | |
+ * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT}, | |
+ * {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT}, | |
+ * {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by | |
+ * {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the | |
+ * intent that can be supplied when the actual send happens. | |
+ * @param options Additional options for how the Activity should be started. | |
+ * See {@link android.content.Context#startActivity(Intent, Bundle) | |
+ * Context.startActivity(Intent, Bundle)} for more details. | |
+ * | |
+ * @return The obtained PendingIntent | |
+ */ | |
+ public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) { | |
+ if (mIntents.isEmpty()) { | |
+ throw new IllegalStateException( | |
+ "No intents added to TaskStackBuilder; cannot getPendingIntent"); | |
+ } | |
+ | |
+ Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]); | |
+ intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | | |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK | | |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME); | |
+ return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags, options); | |
+ } | |
+ | |
+ /** | |
+ * Return an array containing the intents added to this builder. The intent at the | |
+ * root of the task stack will appear as the first item in the array and the | |
+ * intent at the top of the stack will appear as the last item. | |
+ * | |
+ * @return An array containing the intents added to this builder. | |
+ */ | |
+ public Intent[] getIntents() { | |
+ return mIntents.toArray(new Intent[mIntents.size()]); | |
+ } | |
+} | |
diff -Nur android-15/android/app/TimePickerDialog.java android-16/android/app/TimePickerDialog.java | |
--- android-15/android/app/TimePickerDialog.java 2012-06-18 20:00:45.000000000 +0900 | |
+++ android-16/android/app/TimePickerDialog.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -96,9 +96,7 @@ | |
setTitle(R.string.time_picker_dialog_title); | |
Context themeContext = getContext(); | |
- setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_set), this); | |
- setButton(BUTTON_NEGATIVE, themeContext.getText(R.string.cancel), | |
- (OnClickListener) null); | |
+ setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this); | |
LayoutInflater inflater = | |
(LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); | |
@@ -114,11 +112,7 @@ | |
} | |
public void onClick(DialogInterface dialog, int which) { | |
- if (mCallback != null) { | |
- mTimePicker.clearFocus(); | |
- mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), | |
- mTimePicker.getCurrentMinute()); | |
- } | |
+ tryNotifyTimeSet(); | |
} | |
public void updateTime(int hourOfDay, int minutOfHour) { | |
@@ -130,6 +124,20 @@ | |
/* do nothing */ | |
} | |
+ private void tryNotifyTimeSet() { | |
+ if (mCallback != null) { | |
+ mTimePicker.clearFocus(); | |
+ mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), | |
+ mTimePicker.getCurrentMinute()); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ protected void onStop() { | |
+ tryNotifyTimeSet(); | |
+ super.onStop(); | |
+ } | |
+ | |
@Override | |
public Bundle onSaveInstanceState() { | |
Bundle state = super.onSaveInstanceState(); | |
diff -Nur android-15/android/app/UiModeManager.java android-16/android/app/UiModeManager.java | |
--- android-15/android/app/UiModeManager.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/app/UiModeManager.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -168,7 +168,7 @@ | |
* {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL}, | |
* {@link Configuration#UI_MODE_TYPE_DESK Configuration.UI_MODE_TYPE_DESK}, or | |
* {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR}, or | |
- * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_TV}. | |
+ * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_APPLIANCE}. | |
*/ | |
public int getCurrentModeType() { | |
if (mService != null) { | |
diff -Nur android-15/android/app/WallpaperManager.java android-16/android/app/WallpaperManager.java | |
--- android-15/android/app/WallpaperManager.java 2012-06-18 20:00:46.000000000 +0900 | |
+++ android-16/android/app/WallpaperManager.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -30,6 +30,7 @@ | |
import android.graphics.Rect; | |
import android.graphics.drawable.BitmapDrawable; | |
import android.graphics.drawable.Drawable; | |
+import android.os.Binder; | |
import android.os.Bundle; | |
import android.os.Handler; | |
import android.os.IBinder; | |
@@ -66,6 +67,23 @@ | |
= "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"; | |
/** | |
+ * Directly launch live wallpaper preview, allowing the user to immediately | |
+ * confirm to switch to a specific live wallpaper. You must specify | |
+ * {@link #EXTRA_LIVE_WALLPAPER_COMPONENT} with the ComponentName of | |
+ * a live wallpaper component that is to be shown. | |
+ */ | |
+ public static final String ACTION_CHANGE_LIVE_WALLPAPER | |
+ = "android.service.wallpaper.CHANGE_LIVE_WALLPAPER"; | |
+ | |
+ /** | |
+ * Extra in {@link #ACTION_CHANGE_LIVE_WALLPAPER} that specifies the | |
+ * ComponentName of a live wallpaper that should be shown as a preview, | |
+ * for the user to confirm. | |
+ */ | |
+ public static final String EXTRA_LIVE_WALLPAPER_COMPONENT | |
+ = "android.service.wallpaper.extra.LIVE_WALLPAPER_COMPONENT"; | |
+ | |
+ /** | |
* Manifest entry for activities that respond to {@link Intent#ACTION_SET_WALLPAPER} | |
* which allows them to provide a custom large icon associated with this action. | |
*/ | |
@@ -213,10 +231,6 @@ | |
mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER); | |
} | |
- public Handler getHandler() { | |
- return mHandler; | |
- } | |
- | |
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) { | |
synchronized (this) { | |
if (mWallpaper != null) { | |
@@ -437,7 +451,12 @@ | |
*/ | |
public WallpaperInfo getWallpaperInfo() { | |
try { | |
- return sGlobals.mService.getWallpaperInfo(); | |
+ if (sGlobals.mService == null) { | |
+ Log.w(TAG, "WallpaperService not running"); | |
+ return null; | |
+ } else { | |
+ return sGlobals.mService.getWallpaperInfo(); | |
+ } | |
} catch (RemoteException e) { | |
return null; | |
} | |
@@ -449,12 +468,19 @@ | |
* wallpaper; it must be a valid PNG or JPEG image. On success, the intent | |
* {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. | |
* | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#SET_WALLPAPER}. | |
+ * | |
* @param resid The bitmap to save. | |
* | |
* @throws IOException If an error occurs reverting to the default | |
* wallpaper. | |
*/ | |
public void setResource(int resid) throws IOException { | |
+ if (sGlobals.mService == null) { | |
+ Log.w(TAG, "WallpaperService not running"); | |
+ return; | |
+ } | |
try { | |
Resources resources = mContext.getResources(); | |
/* Set the wallpaper to the default values */ | |
@@ -481,12 +507,19 @@ | |
* converted to a PNG and stored as the wallpaper. On success, the intent | |
* {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. | |
* | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#SET_WALLPAPER}. | |
+ * | |
* @param bitmap The bitmap to save. | |
* | |
* @throws IOException If an error occurs reverting to the default | |
* wallpaper. | |
*/ | |
public void setBitmap(Bitmap bitmap) throws IOException { | |
+ if (sGlobals.mService == null) { | |
+ Log.w(TAG, "WallpaperService not running"); | |
+ return; | |
+ } | |
try { | |
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); | |
if (fd == null) { | |
@@ -513,12 +546,19 @@ | |
* image. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} | |
* is broadcast. | |
* | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#SET_WALLPAPER}. | |
+ * | |
* @param data A stream containing the raw data to install as a wallpaper. | |
* | |
* @throws IOException If an error occurs reverting to the default | |
* wallpaper. | |
*/ | |
public void setStream(InputStream data) throws IOException { | |
+ if (sGlobals.mService == null) { | |
+ Log.w(TAG, "WallpaperService not running"); | |
+ return; | |
+ } | |
try { | |
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); | |
if (fd == null) { | |
@@ -562,6 +602,10 @@ | |
* mandatory. | |
*/ | |
public int getDesiredMinimumWidth() { | |
+ if (sGlobals.mService == null) { | |
+ Log.w(TAG, "WallpaperService not running"); | |
+ return 0; | |
+ } | |
try { | |
return sGlobals.mService.getWidthHint(); | |
} catch (RemoteException e) { | |
@@ -585,6 +629,10 @@ | |
* mandatory. | |
*/ | |
public int getDesiredMinimumHeight() { | |
+ if (sGlobals.mService == null) { | |
+ Log.w(TAG, "WallpaperService not running"); | |
+ return 0; | |
+ } | |
try { | |
return sGlobals.mService.getHeightHint(); | |
} catch (RemoteException e) { | |
@@ -598,12 +646,27 @@ | |
* wallpaper it would like to use. This allows such applications to have | |
* a virtual wallpaper that is larger than the physical screen, matching | |
* the size of their workspace. | |
+ * | |
+ * <p>Note developers, who don't seem to be reading this. This is | |
+ * for <em>home screens</em> to tell what size wallpaper they would like. | |
+ * Nobody else should be calling this! Certainly not other non-home-screen | |
+ * apps that change the wallpaper. Those apps are supposed to | |
+ * <b>retrieve</b> the suggested size so they can construct a wallpaper | |
+ * that matches it. | |
+ * | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#SET_WALLPAPER_HINTS}. | |
+ * | |
* @param minimumWidth Desired minimum width | |
* @param minimumHeight Desired minimum height | |
*/ | |
public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) { | |
try { | |
- sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight); | |
+ if (sGlobals.mService == null) { | |
+ Log.w(TAG, "WallpaperService not running"); | |
+ } else { | |
+ sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight); | |
+ } | |
} catch (RemoteException e) { | |
// Ignore | |
} | |
@@ -623,24 +686,14 @@ | |
* @param yOffset The offset along the Y dimension, from 0 to 1. | |
*/ | |
public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) { | |
- final IBinder fWindowToken = windowToken; | |
- final float fXOffset = xOffset; | |
- final float fYOffset = yOffset; | |
- sGlobals.getHandler().post(new Runnable() { | |
- public void run() { | |
- try { | |
- //Log.v(TAG, "Sending new wallpaper offsets from app..."); | |
- ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( | |
- fWindowToken, fXOffset, fYOffset, mWallpaperXStep, mWallpaperYStep); | |
- //Log.v(TAG, "...app returning after sending offsets!"); | |
- } catch (RemoteException e) { | |
- // Ignore. | |
- } catch (IllegalArgumentException e) { | |
- // Since this is being posted, it's possible that this windowToken is no longer | |
- // valid, for example, if setWallpaperOffsets is called just before rotation. | |
- } | |
- } | |
- }); | |
+ try { | |
+ //Log.v(TAG, "Sending new wallpaper offsets from app..."); | |
+ ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( | |
+ windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep); | |
+ //Log.v(TAG, "...app returning after sending offsets!"); | |
+ } catch (RemoteException e) { | |
+ // Ignore. | |
+ } | |
} | |
/** | |
@@ -705,6 +758,9 @@ | |
* wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} | |
* is broadcast. | |
* | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#SET_WALLPAPER}. | |
+ * | |
* @throws IOException If an error occurs reverting to the default | |
* wallpaper. | |
*/ | |
diff -Nur android-15/android/app/activity/BroadcastTest.java android-16/android/app/activity/BroadcastTest.java | |
--- android-15/android/app/activity/BroadcastTest.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/app/activity/BroadcastTest.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -303,7 +303,8 @@ | |
public void testSetSticky() throws Exception { | |
Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null); | |
intent.putExtra("test", LaunchpadActivity.DATA_1); | |
- ActivityManagerNative.getDefault().unbroadcastIntent(null, intent); | |
+ ActivityManagerNative.getDefault().unbroadcastIntent(null, intent, | |
+ Binder.getOrigCallingUser()); | |
ActivityManagerNative.broadcastStickyIntent(intent, null); | |
addIntermediate("finished-broadcast"); | |
@@ -320,7 +321,8 @@ | |
ActivityManagerNative.broadcastStickyIntent(intent, null); | |
ActivityManagerNative.getDefault().unbroadcastIntent( | |
- null, new Intent(LaunchpadActivity.BROADCAST_STICKY1, null)); | |
+ null, new Intent(LaunchpadActivity.BROADCAST_STICKY1, null), | |
+ Binder.getOrigCallingUser()); | |
addIntermediate("finished-unbroadcast"); | |
IntentFilter filter = new IntentFilter(LaunchpadActivity.BROADCAST_STICKY1); | |
diff -Nur android-15/android/app/admin/DeviceAdminReceiver.java android-16/android/app/admin/DeviceAdminReceiver.java | |
--- android-15/android/app/admin/DeviceAdminReceiver.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/admin/DeviceAdminReceiver.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -165,7 +165,7 @@ | |
= "android.app.action.ACTION_PASSWORD_EXPIRING"; | |
/** | |
- * Name under which an DevicePolicy component publishes information | |
+ * Name under which a DevicePolicy component publishes information | |
* about itself. This meta-data must reference an XML resource containing | |
* a device-admin tag. XXX TO DO: describe syntax. | |
*/ | |
diff -Nur android-15/android/app/backup/BackupManager.java android-16/android/app/backup/BackupManager.java | |
--- android-15/android/app/backup/BackupManager.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/app/backup/BackupManager.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -145,8 +145,10 @@ | |
try { | |
IRestoreSession binder = sService.beginRestoreSession(mContext.getPackageName(), | |
null); | |
- session = new RestoreSession(mContext, binder); | |
- result = session.restorePackage(mContext.getPackageName(), observer); | |
+ if (binder != null) { | |
+ session = new RestoreSession(mContext, binder); | |
+ result = session.restorePackage(mContext.getPackageName(), observer); | |
+ } | |
} catch (RemoteException e) { | |
Log.w(TAG, "restoreSelf() unable to contact service"); | |
} finally { | |
@@ -170,7 +172,9 @@ | |
try { | |
// All packages, current transport | |
IRestoreSession binder = sService.beginRestoreSession(null, null); | |
- session = new RestoreSession(mContext, binder); | |
+ if (binder != null) { | |
+ session = new RestoreSession(mContext, binder); | |
+ } | |
} catch (RemoteException e) { | |
Log.w(TAG, "beginRestoreSession() couldn't connect"); | |
} | |
diff -Nur android-15/android/app/backup/WallpaperBackupHelper.java android-16/android/app/backup/WallpaperBackupHelper.java | |
--- android-15/android/app/backup/WallpaperBackupHelper.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/app/backup/WallpaperBackupHelper.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -38,15 +38,23 @@ | |
private static final boolean DEBUG = false; | |
// This path must match what the WallpaperManagerService uses | |
- private static final String WALLPAPER_IMAGE = "/data/data/com.android.settings/files/wallpaper"; | |
+ // TODO: Will need to change if backing up non-primary user's wallpaper | |
+ public static final String WALLPAPER_IMAGE = "/data/system/users/0/wallpaper"; | |
+ public static final String WALLPAPER_INFO = "/data/system/users/0/wallpaper_info.xml"; | |
+ // Use old keys to keep legacy data compatibility and avoid writing two wallpapers | |
+ public static final String WALLPAPER_IMAGE_KEY = | |
+ "/data/data/com.android.settings/files/wallpaper"; | |
+ public static final String WALLPAPER_INFO_KEY = "/data/system/wallpaper_info.xml"; | |
// Stage file - should be adjacent to the WALLPAPER_IMAGE location. The wallpapers | |
// will be saved to this file from the restore stream, then renamed to the proper | |
// location if it's deemed suitable. | |
- private static final String STAGE_FILE = "/data/data/com.android.settings/files/wallpaper-tmp"; | |
+ // TODO: Will need to change if backing up non-primary user's wallpaper | |
+ private static final String STAGE_FILE = "/data/system/users/0/wallpaper-tmp"; | |
Context mContext; | |
String[] mFiles; | |
+ String[] mKeys; | |
double mDesiredMinWidth; | |
double mDesiredMinHeight; | |
@@ -57,11 +65,12 @@ | |
* @param context | |
* @param files | |
*/ | |
- public WallpaperBackupHelper(Context context, String... files) { | |
+ public WallpaperBackupHelper(Context context, String[] files, String[] keys) { | |
super(context); | |
mContext = context; | |
mFiles = files; | |
+ mKeys = keys; | |
WallpaperManager wpm; | |
wpm = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE); | |
@@ -89,7 +98,7 @@ | |
*/ | |
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, | |
ParcelFileDescriptor newState) { | |
- performBackup_checked(oldState, data, newState, mFiles, mFiles); | |
+ performBackup_checked(oldState, data, newState, mFiles, mKeys); | |
} | |
/** | |
@@ -99,8 +108,8 @@ | |
*/ | |
public void restoreEntity(BackupDataInputStream data) { | |
final String key = data.getKey(); | |
- if (isKeyInList(key, mFiles)) { | |
- if (key.equals(WALLPAPER_IMAGE)) { | |
+ if (isKeyInList(key, mKeys)) { | |
+ if (key.equals(WALLPAPER_IMAGE_KEY)) { | |
// restore the file to the stage for inspection | |
File f = new File(STAGE_FILE); | |
if (writeFile(f, data)) { | |
@@ -135,9 +144,9 @@ | |
f.delete(); | |
} | |
} | |
- } else { | |
- // Some other normal file; just decode it to its destination | |
- File f = new File(key); | |
+ } else if (key.equals(WALLPAPER_INFO_KEY)) { | |
+ // XML file containing wallpaper info | |
+ File f = new File(WALLPAPER_INFO); | |
writeFile(f, data); | |
} | |
} | |
diff -Nur android-15/android/appwidget/AppWidgetHostView.java android-16/android/appwidget/AppWidgetHostView.java | |
--- android-15/android/appwidget/AppWidgetHostView.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/appwidget/AppWidgetHostView.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -28,6 +28,7 @@ | |
import android.graphics.Paint; | |
import android.graphics.Rect; | |
import android.os.Build; | |
+import android.os.Bundle; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
import android.os.SystemClock; | |
@@ -37,6 +38,7 @@ | |
import android.view.Gravity; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
+import android.view.accessibility.AccessibilityNodeInfo; | |
import android.widget.Adapter; | |
import android.widget.AdapterView; | |
import android.widget.BaseAdapter; | |
@@ -206,6 +208,54 @@ | |
super.dispatchRestoreInstanceState(jail); | |
} | |
+ /** | |
+ * Provide guidance about the size of this widget to the AppWidgetManager. The widths and | |
+ * heights should correspond to the full area the AppWidgetHostView is given. Padding added by | |
+ * the framework will be accounted for automatically. This information gets embedded into the | |
+ * AppWidget options and causes a callback to the AppWidgetProvider. | |
+ * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) | |
+ * | |
+ * @param options The bundle of options, in addition to the size information, | |
+ * can be null. | |
+ * @param minWidth The minimum width that the widget will be displayed at. | |
+ * @param minHeight The maximum height that the widget will be displayed at. | |
+ * @param maxWidth The maximum width that the widget will be displayed at. | |
+ * @param maxHeight The maximum height that the widget will be displayed at. | |
+ * | |
+ */ | |
+ public void updateAppWidgetSize(Bundle options, int minWidth, int minHeight, int maxWidth, | |
+ int maxHeight) { | |
+ if (options == null) { | |
+ options = new Bundle(); | |
+ } | |
+ | |
+ Rect padding = new Rect(); | |
+ if (mInfo != null) { | |
+ padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding); | |
+ } | |
+ float density = getResources().getDisplayMetrics().density; | |
+ | |
+ int xPaddingDips = (int) ((padding.left + padding.right) / density); | |
+ int yPaddingDips = (int) ((padding.top + padding.bottom) / density); | |
+ | |
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, minWidth - xPaddingDips); | |
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, minHeight - yPaddingDips); | |
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, maxWidth - xPaddingDips); | |
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, maxHeight - yPaddingDips); | |
+ updateAppWidgetOptions(options); | |
+ } | |
+ | |
+ /** | |
+ * Specify some extra information for the widget provider. Causes a callback to the | |
+ * AppWidgetProvider. | |
+ * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) | |
+ * | |
+ * @param options The bundle of options information. | |
+ */ | |
+ public void updateAppWidgetOptions(Bundle options) { | |
+ AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options); | |
+ } | |
+ | |
/** {@inheritDoc} */ | |
@Override | |
public LayoutParams generateLayoutParams(AttributeSet attrs) { | |
@@ -474,6 +524,12 @@ | |
return tv; | |
} | |
+ @Override | |
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { | |
+ super.onInitializeAccessibilityNodeInfo(info); | |
+ info.setClassName(AppWidgetHostView.class.getName()); | |
+ } | |
+ | |
private static class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable { | |
public int describeContents() { | |
return 0; | |
diff -Nur android-15/android/appwidget/AppWidgetManager.java android-16/android/appwidget/AppWidgetManager.java | |
--- android-15/android/appwidget/AppWidgetManager.java 2012-06-18 20:00:45.000000000 +0900 | |
+++ android-16/android/appwidget/AppWidgetManager.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -19,6 +19,7 @@ | |
import android.content.ComponentName; | |
import android.content.Context; | |
import android.content.Intent; | |
+import android.os.Bundle; | |
import android.os.IBinder; | |
import android.os.RemoteException; | |
import android.os.ServiceManager; | |
@@ -79,6 +80,46 @@ | |
public static final String ACTION_APPWIDGET_PICK = "android.appwidget.action.APPWIDGET_PICK"; | |
/** | |
+ * Send this from your {@link AppWidgetHost} activity when you want to bind an AppWidget to | |
+ * display and bindAppWidgetIdIfAllowed returns false. | |
+ * <p> | |
+ * You must supply the following extras: | |
+ * <table> | |
+ * <tr> | |
+ * <td>{@link #EXTRA_APPWIDGET_ID}</td> | |
+ * <td>A newly allocated appWidgetId, which will be bound to the AppWidget provider | |
+ * you provide.</td> | |
+ * </tr> | |
+ * <tr> | |
+ * <td>{@link #EXTRA_APPWIDGET_PROVIDER}</td> | |
+ * <td>The BroadcastReceiver that will be the AppWidget provider for this AppWidget. | |
+ * </td> | |
+ * </tr> | |
+ * </table> | |
+ * | |
+ * <p> | |
+ * The system will respond with an onActivityResult call with the following extras in | |
+ * the intent: | |
+ * <table> | |
+ * <tr> | |
+ * <td>{@link #EXTRA_APPWIDGET_ID}</td> | |
+ * <td>The appWidgetId that you supplied in the original intent.</td> | |
+ * </tr> | |
+ * </table> | |
+ * <p> | |
+ * When you receive the result from the AppWidget bind activity, if the resultCode is | |
+ * {@link android.app.Activity#RESULT_OK}, the AppWidget has been bound. You should then | |
+ * check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its | |
+ * configuration activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you | |
+ * should delete | |
+ * the appWidgetId. | |
+ * | |
+ * @see #ACTION_APPWIDGET_CONFIGURE | |
+ * | |
+ */ | |
+ public static final String ACTION_APPWIDGET_BIND = "android.appwidget.action.APPWIDGET_BIND"; | |
+ | |
+ /** | |
* Sent when it is time to configure your AppWidget while it is being added to a host. | |
* This action is not sent as a broadcast to the AppWidget provider, but as a startActivity | |
* to the activity specified in the {@link AppWidgetProviderInfo AppWidgetProviderInfo meta-data}. | |
@@ -109,6 +150,32 @@ | |
public static final String EXTRA_APPWIDGET_ID = "appWidgetId"; | |
/** | |
+ * An bundle extra that contains the lower bound on the current width, in dips, of a widget instance. | |
+ */ | |
+ public static final String OPTION_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth"; | |
+ | |
+ /** | |
+ * An bundle extra that contains the lower bound on the current height, in dips, of a widget instance. | |
+ */ | |
+ public static final String OPTION_APPWIDGET_MIN_HEIGHT = "appWidgetMinHeight"; | |
+ | |
+ /** | |
+ * An bundle extra that contains the upper bound on the current width, in dips, of a widget instance. | |
+ */ | |
+ public static final String OPTION_APPWIDGET_MAX_WIDTH = "appWidgetMaxWidth"; | |
+ | |
+ /** | |
+ * An bundle extra that contains the upper bound on the current width, in dips, of a widget instance. | |
+ */ | |
+ public static final String OPTION_APPWIDGET_MAX_HEIGHT = "appWidgetMaxHeight"; | |
+ | |
+ /** | |
+ * An intent extra which points to a bundle of extra information for a particular widget id. | |
+ * In particular this bundle can contain EXTRA_APPWIDGET_WIDTH and EXTRA_APPWIDGET_HEIGHT. | |
+ */ | |
+ public static final String EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions"; | |
+ | |
+ /** | |
* An intent extra that contains multiple appWidgetIds. | |
* <p> | |
* The value will be an int array that can be retrieved like this: | |
@@ -117,6 +184,13 @@ | |
public static final String EXTRA_APPWIDGET_IDS = "appWidgetIds"; | |
/** | |
+ * An intent extra that contains the component name of a AppWidget provider. | |
+ * <p> | |
+ * The value will be an ComponentName. | |
+ */ | |
+ public static final String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider"; | |
+ | |
+ /** | |
* An intent extra to pass to the AppWidget picker containing a {@link java.util.List} of | |
* {@link AppWidgetProviderInfo} objects to mix in to the list of AppWidgets that are | |
* installed. (This is how the launcher shows the search widget). | |
@@ -161,6 +235,14 @@ | |
public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE"; | |
/** | |
+ * Sent when the custom extras for an AppWidget change. | |
+ * | |
+ * @see AppWidgetProvider#onAppWidgetExtrasChanged AppWidgetProvider#onAppWidgetExtrasChanged( | |
+ * Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newExtras) | |
+ */ | |
+ public static final String ACTION_APPWIDGET_OPTIONS_CHANGED = "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS"; | |
+ | |
+ /** | |
* Sent when an instance of an AppWidget is deleted from its host. | |
* | |
* @see AppWidgetProvider#onDeleted AppWidgetProvider.onDeleted(Context context, int[] appWidgetIds) | |
@@ -238,6 +320,10 @@ | |
* It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast, | |
* and outside of the handler. | |
* This method will only work when called from the uid that owns the AppWidget provider. | |
+ * | |
+ * <p> | |
+ * The total Bitmap memory used by the RemoteViews object cannot exceed that required to | |
+ * fill the screen once, ie. (screen width x screen height x 4) bytes. | |
* | |
* @param appWidgetIds The AppWidget instances for which to set the RemoteViews. | |
* @param views The RemoteViews object to show. | |
@@ -252,6 +338,46 @@ | |
} | |
/** | |
+ * Update the extras for a given widget instance. | |
+ * | |
+ * The extras can be used to embed additional information about this widget to be accessed | |
+ * by the associated widget's AppWidgetProvider. | |
+ * | |
+ * @see #getAppWidgetOptions(int) | |
+ * | |
+ * @param appWidgetId The AppWidget instances for which to set the RemoteViews. | |
+ * @param options The options to associate with this widget | |
+ */ | |
+ public void updateAppWidgetOptions(int appWidgetId, Bundle options) { | |
+ try { | |
+ sService.updateAppWidgetOptions(appWidgetId, options); | |
+ } | |
+ catch (RemoteException e) { | |
+ throw new RuntimeException("system server dead?", e); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Get the extras associated with a given widget instance. | |
+ * | |
+ * The extras can be used to embed additional information about this widget to be accessed | |
+ * by the associated widget's AppWidgetProvider. | |
+ * | |
+ * @see #updateAppWidgetOptions(int, Bundle) | |
+ * | |
+ * @param appWidgetId The AppWidget instances for which to set the RemoteViews. | |
+ * @return The options associated with the given widget instance. | |
+ */ | |
+ public Bundle getAppWidgetOptions(int appWidgetId) { | |
+ try { | |
+ return sService.getAppWidgetOptions(appWidgetId); | |
+ } | |
+ catch (RemoteException e) { | |
+ throw new RuntimeException("system server dead?", e); | |
+ } | |
+ } | |
+ | |
+ /** | |
* Set the RemoteViews to use for the specified appWidgetId. | |
* | |
* Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should | |
@@ -263,6 +389,10 @@ | |
* and outside of the handler. | |
* This method will only work when called from the uid that owns the AppWidget provider. | |
* | |
+ * <p> | |
+ * The total Bitmap memory used by the RemoteViews object cannot exceed that required to | |
+ * fill the screen once, ie. (screen width x screen height x 4) bytes. | |
+ * | |
* @param appWidgetId The AppWidget instance for which to set the RemoteViews. | |
* @param views The RemoteViews object to show. | |
*/ | |
@@ -426,12 +556,14 @@ | |
/** | |
* Set the component for a given appWidgetId. | |
* | |
- * <p class="note">You need the APPWIDGET_LIST permission. This method is to be used by the | |
- * AppWidget picker. | |
+ * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding | |
+ * widgets always for your component. This method is used by the AppWidget picker and | |
+ * should not be used by other apps. | |
* | |
* @param appWidgetId The AppWidget instance for which to set the RemoteViews. | |
* @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget | |
* provider for this AppWidget. | |
+ * @hide | |
*/ | |
public void bindAppWidgetId(int appWidgetId, ComponentName provider) { | |
try { | |
@@ -443,6 +575,68 @@ | |
} | |
/** | |
+ * Set the component for a given appWidgetId. | |
+ * | |
+ * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding | |
+ * widgets always for your component. Should be used by apps that host widgets; if this | |
+ * method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to | |
+ * bind | |
+ * | |
+ * @param appWidgetId The AppWidget instance for which to set the RemoteViews. | |
+ * @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget | |
+ * provider for this AppWidget. | |
+ * @return true if this component has permission to bind the AppWidget | |
+ */ | |
+ public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider) { | |
+ if (mContext == null) { | |
+ return false; | |
+ } | |
+ try { | |
+ return sService.bindAppWidgetIdIfAllowed( | |
+ mContext.getPackageName(), appWidgetId, provider); | |
+ } | |
+ catch (RemoteException e) { | |
+ throw new RuntimeException("system server dead?", e); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Query if a given package was granted permission by the user to bind app widgets | |
+ * | |
+ * <p class="note">You need the MODIFY_APPWIDGET_BIND_PERMISSIONS permission | |
+ * | |
+ * @param packageName The package for which the permission is being queried | |
+ * @return true if the package was granted permission by the user to bind app widgets | |
+ * @hide | |
+ */ | |
+ public boolean hasBindAppWidgetPermission(String packageName) { | |
+ try { | |
+ return sService.hasBindAppWidgetPermission(packageName); | |
+ } | |
+ catch (RemoteException e) { | |
+ throw new RuntimeException("system server dead?", e); | |
+ } | |
+ } | |
+ | |
+ /** | |
+ * Changes any user-granted permission for the given package to bind app widgets | |
+ * | |
+ * <p class="note">You need the MODIFY_APPWIDGET_BIND_PERMISSIONS permission | |
+ * | |
+ * @param provider The package whose permission is being changed | |
+ * @param permission Whether to give the package permission to bind widgets | |
+ * @hide | |
+ */ | |
+ public void setBindAppWidgetPermission(String packageName, boolean permission) { | |
+ try { | |
+ sService.setBindAppWidgetPermission(packageName, permission); | |
+ } | |
+ catch (RemoteException e) { | |
+ throw new RuntimeException("system server dead?", e); | |
+ } | |
+ } | |
+ | |
+ /** | |
* Binds the RemoteViewsService for a given appWidgetId and intent. | |
* | |
* The appWidgetId specified must already be bound to the calling AppWidgetHost via | |
diff -Nur android-15/android/appwidget/AppWidgetProvider.java android-16/android/appwidget/AppWidgetProvider.java | |
--- android-15/android/appwidget/AppWidgetProvider.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/appwidget/AppWidgetProvider.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -74,6 +74,16 @@ | |
this.onDeleted(context, new int[] { appWidgetId }); | |
} | |
} | |
+ else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) { | |
+ Bundle extras = intent.getExtras(); | |
+ if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID) | |
+ && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) { | |
+ int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID); | |
+ Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS); | |
+ this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context), | |
+ appWidgetId, widgetExtras); | |
+ } | |
+ } | |
else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) { | |
this.onEnabled(context); | |
} | |
@@ -82,7 +92,7 @@ | |
} | |
} | |
// END_INCLUDE(onReceive) | |
- | |
+ | |
/** | |
* Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcast when | |
* this AppWidget provider is being asked to provide {@link android.widget.RemoteViews RemoteViews} | |
@@ -102,7 +112,26 @@ | |
*/ | |
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | |
} | |
- | |
+ | |
+ /** | |
+ * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED} | |
+ * broadcast when this widget has been layed out at a new size. | |
+ * | |
+ * {@more} | |
+ * | |
+ * @param context The {@link android.content.Context Context} in which this receiver is | |
+ * running. | |
+ * @param appWidgetManager A {@link AppWidgetManager} object you can call {@link | |
+ * AppWidgetManager#updateAppWidget} on. | |
+ * @param appWidgetId The appWidgetId of the widget who's size changed. | |
+ * @param newOptions The appWidgetId of the widget who's size changed. | |
+ * | |
+ * @see AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED | |
+ */ | |
+ public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, | |
+ int appWidgetId, Bundle newOptions) { | |
+ } | |
+ | |
/** | |
* Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_DELETED} broadcast when | |
* one or more AppWidget instances have been deleted. Override this method to implement | |
diff -Nur android-15/android/bluetooth/AtCommandResult.java android-16/android/bluetooth/AtCommandResult.java | |
--- android-15/android/bluetooth/AtCommandResult.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/bluetooth/AtCommandResult.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -17,7 +17,7 @@ | |
package android.bluetooth; | |
/** | |
- * The result of execution of an single AT command.<p> | |
+ * The result of execution of a single AT command.<p> | |
* | |
* | |
* This class can represent the final response to an AT command line, and also | |
diff -Nur android-15/android/bluetooth/BluetoothAdapter.java android-16/android/bluetooth/BluetoothAdapter.java | |
--- android-15/android/bluetooth/BluetoothAdapter.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/bluetooth/BluetoothAdapter.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -405,6 +405,25 @@ | |
} | |
/** | |
+ * Get a {@link BluetoothDevice} object for the given Bluetooth hardware | |
+ * address. | |
+ * <p>Valid Bluetooth hardware addresses must be 6 bytes. This method | |
+ * expects the address in network byte order (MSB first). | |
+ * <p>A {@link BluetoothDevice} will always be returned for a valid | |
+ * hardware address, even if this adapter has never seen that device. | |
+ * | |
+ * @param address Bluetooth MAC address (6 bytes) | |
+ * @throws IllegalArgumentException if address is invalid | |
+ */ | |
+ public BluetoothDevice getRemoteDevice(byte[] address) { | |
+ if (address == null || address.length != 6) { | |
+ throw new IllegalArgumentException("Bluetooth address must have 6 bytes"); | |
+ } | |
+ return new BluetoothDevice(String.format("%02X:%02X:%02X:%02X:%02X:%02X", | |
+ address[0], address[1], address[2], address[3], address[4], address[5])); | |
+ } | |
+ | |
+ /** | |
* Return true if Bluetooth is currently enabled and ready for use. | |
* <p>Equivalent to: | |
* <code>getBluetoothState() == STATE_ON</code> | |
@@ -1213,6 +1232,18 @@ | |
} | |
/** | |
+ * Enable the Bluetooth Adapter, but don't auto-connect devices | |
+ * and don't persist state. Only for use by system applications. | |
+ * @hide | |
+ */ | |
+ public boolean enableNoAutoConnect() { | |
+ try { | |
+ return mService.enableNoAutoConnect(); | |
+ } catch (RemoteException e) {Log.e(TAG, "", e);} | |
+ return false; | |
+ } | |
+ | |
+ /** | |
* Enable control of the Bluetooth Adapter for a single application. | |
* | |
* <p>Some applications need to use Bluetooth for short periods of time to | |
@@ -1287,7 +1318,7 @@ | |
} | |
/** | |
- * Validate a Bluetooth address, such as "00:43:A8:23:10:F0" | |
+ * Validate a String Bluetooth address, such as "00:43:A8:23:10:F0" | |
* <p>Alphabetic characters must be uppercase to be valid. | |
* | |
* @param address Bluetooth address as string | |
diff -Nur android-15/android/bluetooth/BluetoothDevice.java android-16/android/bluetooth/BluetoothDevice.java | |
--- android-15/android/bluetooth/BluetoothDevice.java 2012-06-18 20:00:45.000000000 +0900 | |
+++ android-16/android/bluetooth/BluetoothDevice.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -160,6 +160,18 @@ | |
"android.bluetooth.device.action.NAME_CHANGED"; | |
/** | |
+ * Broadcast Action: Indicates the alias of a remote device has been | |
+ * changed. | |
+ * <p>Always contains the extra field {@link #EXTRA_DEVICE}. | |
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. | |
+ * | |
+ * @hide | |
+ */ | |
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) | |
+ public static final String ACTION_ALIAS_CHANGED = | |
+ "android.bluetooth.device.action.ALIAS_CHANGED"; | |
+ | |
+ /** | |
* Broadcast Action: Indicates a change in the bond state of a remote | |
* device. For example, if a device is bonded (paired). | |
* <p>Always contains the extra fields {@link #EXTRA_DEVICE}, {@link | |
@@ -193,7 +205,7 @@ | |
public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI"; | |
/** | |
- * Used as an Parcelable {@link BluetoothClass} extra field in {@link | |
+ * Used as a Parcelable {@link BluetoothClass} extra field in {@link | |
* #ACTION_FOUND} and {@link #ACTION_CLASS_CHANGED} intents. | |
*/ | |
public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS"; | |
diff -Nur android-15/android/bluetooth/BluetoothPbap.java android-16/android/bluetooth/BluetoothPbap.java | |
--- android-15/android/bluetooth/BluetoothPbap.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/bluetooth/BluetoothPbap.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -60,7 +60,7 @@ | |
public static final String PBAP_PREVIOUS_STATE = | |
"android.bluetooth.pbap.intent.PBAP_PREVIOUS_STATE"; | |
- /** Indicates the state of an pbap connection state has changed. | |
+ /** Indicates the state of a pbap connection state has changed. | |
* This intent will always contain PBAP_STATE, PBAP_PREVIOUS_STATE and | |
* BluetoothIntent.ADDRESS extras. | |
*/ | |
diff -Nur android-15/android/bordeaux/learning/MulticlassPA.java android-16/android/bordeaux/learning/MulticlassPA.java | |
--- android-15/android/bordeaux/learning/MulticlassPA.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/learning/MulticlassPA.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -0,0 +1,68 @@ | |
+/* | |
+ * Copyright (C) 2011 The Android Open Source Project | |
+ * | |
+ * Licensed under the Apache License, Version 2.0 (the "License"); | |
+ * you may not use this file except in compliance with the License. | |
+ * You may obtain a copy of the License at | |
+ * | |
+ * http://www.apache.org/licenses/LICENSE-2.0 | |
+ * | |
+ * Unless required by applicable law or agreed to in writing, software | |
+ * distributed under the License is distributed on an "AS IS" BASIS, | |
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ * See the License for the specific language governing permissions and | |
+ * limitations under the License. | |
+ */ | |
+ | |
+ | |
+package android.bordeaux.learning; | |
+ | |
+/** | |
+ * Wrapper for multiclass passive aggressive classifier. | |
+ * version 1 supports indexed sparse feature only. | |
+ */ | |
+public class MulticlassPA { | |
+ | |
+ public MulticlassPA(int numClasses, int numDimensions, float aggressiveness) { | |
+ nativeClassifier = initNativeClassifier(numClasses, numDimensions, | |
+ aggressiveness); | |
+ } | |
+ | |
+ /** | |
+ * Train on one example | |
+ */ | |
+ public boolean sparseTrainOneExample(int[] index_array, | |
+ float[] float_array, | |
+ int target) { | |
+ return nativeSparseTrainOneExample( | |
+ index_array, float_array, target, nativeClassifier); | |
+ } | |
+ | |
+ /** | |
+ * Train on one example | |
+ */ | |
+ public int sparseGetClass(int[] index_array, float[] float_array) { | |
+ return nativeSparseGetClass(index_array, float_array, nativeClassifier); | |
+ } | |
+ | |
+ static { | |
+ System.loadLibrary("bordeaux"); | |
+ } | |
+ | |
+ private int nativeClassifier; | |
+ | |
+ /* | |
+ * Initialize native classifier | |
+ */ | |
+ private native int initNativeClassifier(int num_classes, int num_dims, float aggressiveness); | |
+ | |
+ private native void deleteNativeClassifier(int classPtr); | |
+ | |
+ private native boolean nativeSparseTrainOneExample(int[] index_array, | |
+ float[] float_array, | |
+ int target, int classPtr); | |
+ | |
+ private native int nativeSparseGetClass(int[] index_array, | |
+ float[] float_array, | |
+ int classPtr); | |
+} | |
diff -Nur android-15/android/bordeaux/learning/StochasticLinearRanker.java android-16/android/bordeaux/learning/StochasticLinearRanker.java | |
--- android-15/android/bordeaux/learning/StochasticLinearRanker.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/learning/StochasticLinearRanker.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -0,0 +1,193 @@ | |
+/* | |
+ * Copyright (C) 2011 The Android Open Source Project | |
+ * | |
+ * Licensed under the Apache License, Version 2.0 (the "License"); | |
+ * you may not use this file except in compliance with the License. | |
+ * You may obtain a copy of the License at | |
+ * | |
+ * http://www.apache.org/licenses/LICENSE-2.0 | |
+ * | |
+ * Unless required by applicable law or agreed to in writing, software | |
+ * distributed under the License is distributed on an "AS IS" BASIS, | |
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ * See the License for the specific language governing permissions and | |
+ * limitations under the License. | |
+ */ | |
+ | |
+ | |
+package android.bordeaux.learning; | |
+ | |
+import android.util.Log; | |
+ | |
+import java.io.Serializable; | |
+import java.util.List; | |
+import java.util.Arrays; | |
+import java.util.ArrayList; | |
+import java.util.HashMap; | |
+import java.util.Map; | |
+ | |
+/** | |
+ * Stochastic Linear Ranker, learns how to rank a sample. The learned rank score | |
+ * can be used to compare samples. | |
+ * This java class wraps the native StochasticLinearRanker class. | |
+ * To update the ranker, call updateClassifier with two samples, with the first | |
+ * one having higher rank than the second one. | |
+ * To get the rank score of the sample call scoreSample. | |
+ * TODO: adding more interfaces for changing the learning parameters | |
+ */ | |
+public class StochasticLinearRanker { | |
+ String TAG = "StochasticLinearRanker"; | |
+ public static int VAR_NUM = 14; | |
+ static public class Model implements Serializable { | |
+ public HashMap<String, Float> weights = new HashMap<String, Float>(); | |
+ public float weightNormalizer = 1; | |
+ public HashMap<String, String> parameters = new HashMap<String, String>(); | |
+ } | |
+ | |
+ /** | |
+ * Initializing a ranker | |
+ */ | |
+ public StochasticLinearRanker() { | |
+ mNativeClassifier = initNativeClassifier(); | |
+ } | |
+ | |
+ /** | |
+ * Reset the ranker | |
+ */ | |
+ public void resetRanker(){ | |
+ deleteNativeClassifier(mNativeClassifier); | |
+ mNativeClassifier = initNativeClassifier(); | |
+ } | |
+ | |
+ /** | |
+ * Train the ranker with a pair of samples. A sample, a pair of arrays of | |
+ * keys and values. The first sample should have higher rank than the second | |
+ * one. | |
+ */ | |
+ public boolean updateClassifier(String[] keys_positive, | |
+ float[] values_positive, | |
+ String[] keys_negative, | |
+ float[] values_negative) { | |
+ return nativeUpdateClassifier(keys_positive, values_positive, | |
+ keys_negative, values_negative, | |
+ mNativeClassifier); | |
+ } | |
+ | |
+ /** | |
+ * Get the rank score of the sample, a sample is a list of key, value pairs. | |
+ */ | |
+ public float scoreSample(String[] keys, float[] values) { | |
+ return nativeScoreSample(keys, values, mNativeClassifier); | |
+ } | |
+ | |
+ /** | |
+ * Get the current model and parameters of ranker | |
+ */ | |
+ public Model getUModel(){ | |
+ Model slrModel = new Model(); | |
+ int len = nativeGetLengthClassifier(mNativeClassifier); | |
+ String[] wKeys = new String[len]; | |
+ float[] wValues = new float[len]; | |
+ float wNormalizer = 1; | |
+ nativeGetWeightClassifier(wKeys, wValues, wNormalizer, mNativeClassifier); | |
+ slrModel.weightNormalizer = wNormalizer; | |
+ for (int i=0; i< wKeys.length ; i++) | |
+ slrModel.weights.put(wKeys[i], wValues[i]); | |
+ | |
+ String[] paramKeys = new String[VAR_NUM]; | |
+ String[] paramValues = new String[VAR_NUM]; | |
+ nativeGetParameterClassifier(paramKeys, paramValues, mNativeClassifier); | |
+ for (int i=0; i< paramKeys.length ; i++) | |
+ slrModel.parameters.put(paramKeys[i], paramValues[i]); | |
+ return slrModel; | |
+ } | |
+ | |
+ /** | |
+ * load the given model and parameters to the ranker | |
+ */ | |
+ public boolean loadModel(Model model) { | |
+ String[] wKeys = new String[model.weights.size()]; | |
+ float[] wValues = new float[model.weights.size()]; | |
+ int i = 0 ; | |
+ for (Map.Entry<String, Float> e : model.weights.entrySet()){ | |
+ wKeys[i] = e.getKey(); | |
+ wValues[i] = e.getValue(); | |
+ i++; | |
+ } | |
+ boolean res = setModelWeights(wKeys, wValues, model.weightNormalizer); | |
+ if (!res) | |
+ return false; | |
+ | |
+ for (Map.Entry<String, String> e : model.parameters.entrySet()){ | |
+ res = setModelParameter(e.getKey(), e.getValue()); | |
+ if (!res) | |
+ return false; | |
+ } | |
+ return res; | |
+ } | |
+ | |
+ public boolean setModelWeights(String[] keys, float [] values, float normalizer){ | |
+ return nativeSetWeightClassifier(keys, values, normalizer, mNativeClassifier); | |
+ } | |
+ | |
+ public boolean setModelParameter(String key, String value){ | |
+ boolean res = nativeSetParameterClassifier(key, value, mNativeClassifier); | |
+ return res; | |
+ } | |
+ | |
+ /** | |
+ * Print a model for debugging | |
+ */ | |
+ public void print(Model model){ | |
+ String Sw = ""; | |
+ String Sp = ""; | |
+ for (Map.Entry<String, Float> e : model.weights.entrySet()) | |
+ Sw = Sw + "<" + e.getKey() + "," + e.getValue() + "> "; | |
+ for (Map.Entry<String, String> e : model.parameters.entrySet()) | |
+ Sp = Sp + "<" + e.getKey() + "," + e.getValue() + "> "; | |
+ Log.i(TAG, "Weights are " + Sw); | |
+ Log.i(TAG, "Normalizer is " + model.weightNormalizer); | |
+ Log.i(TAG, "Parameters are " + Sp); | |
+ } | |
+ | |
+ @Override | |
+ protected void finalize() throws Throwable { | |
+ deleteNativeClassifier(mNativeClassifier); | |
+ } | |
+ | |
+ static { | |
+ System.loadLibrary("bordeaux"); | |
+ } | |
+ | |
+ private int mNativeClassifier; | |
+ | |
+ /* | |
+ * The following methods are the java stubs for the jni implementations. | |
+ */ | |
+ private native int initNativeClassifier(); | |
+ | |
+ private native void deleteNativeClassifier(int classifierPtr); | |
+ | |
+ private native boolean nativeUpdateClassifier( | |
+ String[] keys_positive, | |
+ float[] values_positive, | |
+ String[] keys_negative, | |
+ float[] values_negative, | |
+ int classifierPtr); | |
+ | |
+ private native float nativeScoreSample(String[] keys, float[] values, int classifierPtr); | |
+ | |
+ private native void nativeGetWeightClassifier(String [] keys, float[] values, float normalizer, | |
+ int classifierPtr); | |
+ | |
+ private native void nativeGetParameterClassifier(String [] keys, String[] values, | |
+ int classifierPtr); | |
+ | |
+ private native int nativeGetLengthClassifier(int classifierPtr); | |
+ | |
+ private native boolean nativeSetWeightClassifier(String [] keys, float[] values, | |
+ float normalizer, int classifierPtr); | |
+ | |
+ private native boolean nativeSetParameterClassifier(String key, String value, | |
+ int classifierPtr); | |
+} | |
diff -Nur android-15/android/bordeaux/services/BordeauxClassifier.java android-16/android/bordeaux/services/BordeauxClassifier.java | |
--- android-15/android/bordeaux/services/BordeauxClassifier.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/BordeauxClassifier.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -0,0 +1,103 @@ | |
+/* | |
+ * 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 android.bordeaux.services; | |
+ | |
+import android.bordeaux.services.ILearning_MulticlassPA; | |
+import android.bordeaux.services.IntFloat; | |
+import android.content.Context; | |
+import android.os.RemoteException; | |
+import android.util.Log; | |
+ | |
+import java.util.ArrayList; | |
+import java.util.List; | |
+import java.util.HashMap; | |
+import java.util.Map; | |
+ | |
+/** Classifier for the Learning framework. | |
+ * For training: call trainOneSample | |
+ * For classifying: call classify | |
+ * Data is represented as sparse key, value pair. And key is an integer, value | |
+ * is a float. Class label(target) for the training data is an integer. | |
+ * Note: since the actual classifier is running in a remote the service. | |
+ * Sometimes the connection may be lost or not established. | |
+ * | |
+ */ | |
+public class BordeauxClassifier { | |
+ static final String TAG = "BordeauxClassifier"; | |
+ private Context mContext; | |
+ private String mName; | |
+ private ILearning_MulticlassPA mClassifier; | |
+ private ArrayList<IntFloat> getArrayList(final HashMap<Integer, Float> sample) { | |
+ ArrayList<IntFloat> intfloat_sample = new ArrayList<IntFloat>(); | |
+ for (Map.Entry<Integer, Float> x : sample.entrySet()) { | |
+ IntFloat v = new IntFloat(); | |
+ v.index = x.getKey(); | |
+ v.value = x.getValue(); | |
+ intfloat_sample.add(v); | |
+ } | |
+ return intfloat_sample; | |
+ } | |
+ | |
+ private boolean retrieveClassifier() { | |
+ if (mClassifier == null) | |
+ mClassifier = BordeauxManagerService.getClassifier(mContext, mName); | |
+ // if classifier is not available, return false | |
+ if (mClassifier == null) { | |
+ Log.i(TAG,"Classifier not available."); | |
+ return false; | |
+ } | |
+ return true; | |
+ } | |
+ | |
+ public BordeauxClassifier(Context context) { | |
+ mContext = context; | |
+ mName = "defaultClassifier"; | |
+ mClassifier = BordeauxManagerService.getClassifier(context, mName); | |
+ } | |
+ | |
+ public BordeauxClassifier(Context context, String name) { | |
+ mContext = context; | |
+ mName = name; | |
+ mClassifier = BordeauxManagerService.getClassifier(context, mName); | |
+ } | |
+ | |
+ public boolean update(final HashMap<Integer, Float> sample, int target) { | |
+ if (!retrieveClassifier()) | |
+ return false; | |
+ try { | |
+ mClassifier.TrainOneSample(getArrayList(sample), target); | |
+ } catch (RemoteException e) { | |
+ Log.e(TAG,"Exception: training one sample."); | |
+ return false; | |
+ } | |
+ return true; | |
+ } | |
+ | |
+ public int classify(final HashMap<Integer, Float> sample) { | |
+ // if classifier is not available return -1 as an indication of fail. | |
+ if (!retrieveClassifier()) | |
+ return -1; | |
+ try { | |
+ return mClassifier.Classify(getArrayList(sample)); | |
+ } catch (RemoteException e) { | |
+ Log.e(TAG,"Exception: classify the sample."); | |
+ // return an invalid number. | |
+ // TODO: throw exception. | |
+ return -1; | |
+ } | |
+ } | |
+} | |
diff -Nur android-15/android/bordeaux/services/BordeauxManagerService.java android-16/android/bordeaux/services/BordeauxManagerService.java | |
--- android-15/android/bordeaux/services/BordeauxManagerService.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/BordeauxManagerService.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -0,0 +1,124 @@ | |
+/* | |
+ * 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 android.bordeaux.services; | |
+ | |
+import android.content.ComponentName; | |
+import android.content.Context; | |
+import android.content.Intent; | |
+import android.content.ServiceConnection; | |
+import android.graphics.Canvas; | |
+import android.graphics.Paint; | |
+import android.graphics.PointF; | |
+import android.os.Bundle; | |
+import android.os.Handler; | |
+import android.os.IBinder; | |
+import android.os.Message; | |
+import android.os.Process; | |
+import android.os.RemoteException; | |
+import android.util.Log; | |
+ | |
+/** | |
+ * {@hide} | |
+ * This is used to provide a convenience to access the actual remote running | |
+ * service. | |
+ * TODO: Eventally the remote service will be running in the system server, and | |
+ * this will need to be served as a stub for the remote running service. And | |
+ * extends from IBordeauxManager.stub | |
+ */ | |
+public class BordeauxManagerService { | |
+ | |
+ static private final String TAG = "BordeauxMangerService"; | |
+ static private IBordeauxService mService = null; | |
+ static private ILearning_StochasticLinearRanker mRanker = null; | |
+ static private ILearning_MulticlassPA mClassifier = null; | |
+ static private boolean mStarted = false; | |
+ | |
+ public BordeauxManagerService() { | |
+ } | |
+ | |
+ static private synchronized void bindServices(Context context) { | |
+ if (mStarted) return; | |
+ context.bindService(new Intent(IBordeauxService.class.getName()), | |
+ mConnection, Context.BIND_AUTO_CREATE); | |
+ mStarted = true; | |
+ | |
+ } | |
+ | |
+ // Call the release, before the Context gets destroyed. | |
+ static public synchronized void release(Context context) { | |
+ if (mStarted && mConnection != null) { | |
+ context.unbindService(mConnection); | |
+ mService = null; | |
+ mStarted = false; | |
+ } | |
+ } | |
+ | |
+ static public synchronized IBordeauxService getService(Context context) { | |
+ if (mService == null) bindServices(context); | |
+ return mService; | |
+ } | |
+ | |
+ static public synchronized ILearning_StochasticLinearRanker | |
+ getRanker(Context context, String name) { | |
+ if (mService == null) { | |
+ bindServices(context); | |
+ return null; | |
+ } | |
+ try { | |
+ mRanker = | |
+ ILearning_StochasticLinearRanker.Stub.asInterface( | |
+ mService.getRanker(name)); | |
+ } catch (RemoteException e) { | |
+ mRanker = null; | |
+ } | |
+ return mRanker; | |
+ } | |
+ | |
+ static public synchronized ILearning_MulticlassPA | |
+ getClassifier(Context context, String name) { | |
+ if (mService == null) { | |
+ bindServices(context); | |
+ return null; | |
+ } | |
+ try { | |
+ mClassifier = | |
+ ILearning_MulticlassPA.Stub.asInterface(mService.getClassifier(name)); | |
+ } catch (RemoteException e) { | |
+ mClassifier = null; | |
+ } | |
+ return mClassifier; | |
+ } | |
+ | |
+ /** | |
+ * Class for interacting with the main interface of the service. | |
+ */ | |
+ static private ServiceConnection mConnection = new ServiceConnection() { | |
+ public void onServiceConnected(ComponentName className, | |
+ IBinder service) { | |
+ // This is called when the connection with the service has been | |
+ // established. | |
+ mService = IBordeauxService.Stub.asInterface(service); | |
+ } | |
+ | |
+ public void onServiceDisconnected(ComponentName className) { | |
+ // This is called when the connection with the service has been | |
+ // unexpectedly disconnected -- that is, its process crashed. | |
+ mService = null; | |
+ mStarted = false; // needs to bind again | |
+ } | |
+ }; | |
+} | |
diff -Nur android-15/android/bordeaux/services/BordeauxRanker.java android-16/android/bordeaux/services/BordeauxRanker.java | |
--- android-15/android/bordeaux/services/BordeauxRanker.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/BordeauxRanker.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -0,0 +1,139 @@ | |
+/* | |
+ * 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 android.bordeaux.services; | |
+ | |
+import android.bordeaux.services.ILearning_StochasticLinearRanker; | |
+import android.bordeaux.services.StringFloat; | |
+import android.content.Context; | |
+import android.os.RemoteException; | |
+import android.util.Log; | |
+ | |
+import java.util.ArrayList; | |
+import java.util.List; | |
+import java.util.HashMap; | |
+import java.util.Map; | |
+ | |
+/** Ranker for the Learning framework. | |
+ * For training: call updateClassifier with a pair of samples. | |
+ * For ranking: call scoreSample to the score of the rank | |
+ * Data is represented as sparse key, value pair. And key is a String, value | |
+ * is a float. | |
+ * Note: since the actual ranker is running in a remote the service. | |
+ * Sometimes the connection may be lost or not established. | |
+ * | |
+ */ | |
+public class BordeauxRanker { | |
+ static final String TAG = "BordeauxRanker"; | |
+ static final String RANKER_NOTAVAILABLE = "Ranker not Available"; | |
+ private Context mContext; | |
+ private String mName; | |
+ private ILearning_StochasticLinearRanker mRanker; | |
+ private ArrayList<StringFloat> getArrayList(final HashMap<String, Float> sample) { | |
+ ArrayList<StringFloat> stringfloat_sample = new ArrayList<StringFloat>(); | |
+ for (Map.Entry<String, Float> x : sample.entrySet()) { | |
+ StringFloat v = new StringFloat(); | |
+ v.key = x.getKey(); | |
+ v.value = x.getValue(); | |
+ stringfloat_sample.add(v); | |
+ } | |
+ return stringfloat_sample; | |
+ } | |
+ | |
+ public boolean retrieveRanker() { | |
+ if (mRanker == null) | |
+ mRanker = BordeauxManagerService.getRanker(mContext, mName); | |
+ // if classifier is not available, return false | |
+ if (mRanker == null) { | |
+ Log.e(TAG,"Ranker not available."); | |
+ return false; | |
+ } | |
+ return true; | |
+ } | |
+ | |
+ public BordeauxRanker(Context context) { | |
+ mContext = context; | |
+ mName = "defaultRanker"; | |
+ mRanker = BordeauxManagerService.getRanker(context, mName); | |
+ } | |
+ | |
+ public BordeauxRanker(Context context, String name) { | |
+ mContext = context; | |
+ mName = name; | |
+ mRanker = BordeauxManagerService.getRanker(context, mName); | |
+ } | |
+ | |
+ // Update the ranker with two samples, sample1 has higher rank than | |
+ // sample2. | |
+ public boolean update(final HashMap<String, Float> sample1, | |
+ final HashMap<String, Float> sample2) { | |
+ if (!retrieveRanker()) | |
+ return false; | |
+ try { | |
+ mRanker.UpdateClassifier(getArrayList(sample1), getArrayList(sample2)); | |
+ } catch (RemoteException e) { | |
+ Log.e(TAG,"Exception: updateClassifier."); | |
+ return false; | |
+ } | |
+ return true; | |
+ } | |
+ | |
+ public boolean reset() { | |
+ if (!retrieveRanker()){ | |
+ Log.e(TAG,"Exception: Ranker is not availible"); | |
+ return false; | |
+ } | |
+ try { | |
+ mRanker.ResetRanker(); | |
+ return true; | |
+ } catch (RemoteException e) { | |
+ } | |
+ return false; | |
+ } | |
+ | |
+ public float scoreSample(final HashMap<String, Float> sample) { | |
+ if (!retrieveRanker()) | |
+ throw new RuntimeException(RANKER_NOTAVAILABLE); | |
+ try { | |
+ return mRanker.ScoreSample(getArrayList(sample)); | |
+ } catch (RemoteException e) { | |
+ Log.e(TAG,"Exception: scoring the sample."); | |
+ throw new RuntimeException(RANKER_NOTAVAILABLE); | |
+ } | |
+ } | |
+ | |
+ public boolean setPriorWeight(final HashMap<String, Float> sample) { | |
+ if (!retrieveRanker()) | |
+ throw new RuntimeException(RANKER_NOTAVAILABLE); | |
+ try { | |
+ return mRanker.SetModelPriorWeight(getArrayList(sample)); | |
+ } catch (RemoteException e) { | |
+ Log.e(TAG,"Exception: set prior Weights"); | |
+ throw new RuntimeException(RANKER_NOTAVAILABLE); | |
+ } | |
+ } | |
+ | |
+ public boolean setParameter(String key, String value) { | |
+ if (!retrieveRanker()) | |
+ throw new RuntimeException(RANKER_NOTAVAILABLE); | |
+ try { | |
+ return mRanker.SetModelParameter(key, value); | |
+ } catch (RemoteException e) { | |
+ Log.e(TAG,"Exception: scoring the sample with prior."); | |
+ throw new RuntimeException(RANKER_NOTAVAILABLE); | |
+ } | |
+ } | |
+} | |
diff -Nur android-15/android/bordeaux/services/BordeauxService.java android-16/android/bordeaux/services/BordeauxService.java | |
--- android-15/android/bordeaux/services/BordeauxService.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/BordeauxService.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -0,0 +1,176 @@ | |
+/* | |
+ * 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 android.bordeaux.services; | |
+ | |
+import android.app.Activity; | |
+import android.app.Notification; | |
+import android.app.NotificationManager; | |
+import android.app.PendingIntent; | |
+import android.app.Service; | |
+import android.content.ComponentName; | |
+import android.content.Context; | |
+import android.content.Intent; | |
+import android.content.pm.PackageManager; | |
+import android.content.ServiceConnection; | |
+import android.os.Bundle; | |
+import android.os.Handler; | |
+import android.os.IBinder; | |
+import android.os.Message; | |
+import android.os.Process; | |
+import android.os.RemoteCallbackList; | |
+import android.os.RemoteException; | |
+import android.view.View; | |
+import android.view.View.OnClickListener; | |
+import android.widget.Button; | |
+import android.widget.TextView; | |
+import android.widget.Toast; | |
+ | |
+import android.bordeaux.R; | |
+import android.util.Log; | |
+ | |
+import java.io.*; | |
+ | |
+/** | |
+ * Machine Learning service that runs in a remote process. | |
+ * The application doesn't use this class directly. | |
+ * | |
+ */ | |
+public class BordeauxService extends Service { | |
+ private final String TAG = "BordeauxService"; | |
+ /** | |
+ * This is a list of callbacks that have been registered with the | |
+ * service. | |
+ * It's a place holder for future communications with all registered | |
+ * clients. | |
+ */ | |
+ final RemoteCallbackList<IBordeauxServiceCallback> mCallbacks = | |
+ new RemoteCallbackList<IBordeauxServiceCallback>(); | |
+ | |
+ int mValue = 0; | |
+ NotificationManager mNotificationManager; | |
+ | |
+ BordeauxSessionManager mSessionManager; | |
+ | |
+ @Override | |
+ public void onCreate() { | |
+ Log.i(TAG, "Bordeaux service created."); | |
+ mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); | |
+ mSessionManager = new BordeauxSessionManager(this); | |
+ | |
+ // Display a notification about us starting. | |
+ // TODO: don't display the notification after the service is | |
+ // automatically started by the system, currently it's useful for | |
+ // debugging. | |
+ showNotification(); | |
+ } | |
+ | |
+ @Override | |
+ public void onDestroy() { | |
+ // Save the sessions | |
+ mSessionManager.saveSessions(); | |
+ | |
+ // Cancel the persistent notification. | |
+ mNotificationManager.cancel(R.string.remote_service_started); | |
+ | |
+ // Tell the user we stopped. | |
+ Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show(); | |
+ | |
+ // Unregister all callbacks. | |
+ mCallbacks.kill(); | |
+ | |
+ Log.i(TAG, "Bordeaux service stopped."); | |
+ } | |
+ | |
+ @Override | |
+ public IBinder onBind(Intent intent) { | |
+ // Return the requested interface. | |
+ if (IBordeauxService.class.getName().equals(intent.getAction())) { | |
+ return mBinder; | |
+ } | |
+ return null; | |
+ } | |
+ | |
+ | |
+ // The main interface implemented by the service. | |
+ private final IBordeauxService.Stub mBinder = new IBordeauxService.Stub() { | |
+ private IBinder getLearningSession(Class learnerClass, String name) { | |
+ PackageManager pm = getPackageManager(); | |
+ String uidname = pm.getNameForUid(getCallingUid()); | |
+ Log.i(TAG,"Name for uid: " + uidname); | |
+ BordeauxSessionManager.SessionKey key = | |
+ mSessionManager.getSessionKey(uidname, learnerClass, name); | |
+ Log.i(TAG, "request learning session: " + key.value); | |
+ try { | |
+ IBinder iLearner = mSessionManager.getSessionBinder(learnerClass, key); | |
+ return iLearner; | |
+ } catch (RuntimeException e) { | |
+ Log.e(TAG, "Error getting learning interface" + e); | |
+ return null; | |
+ } | |
+ } | |
+ | |
+ public IBinder getClassifier(String name) { | |
+ return getLearningSession(Learning_MulticlassPA.class, name); | |
+ } | |
+ | |
+ public IBinder getRanker(String name) { | |
+ return getLearningSession(Learning_StochasticLinearRanker.class, name); | |
+ } | |
+ | |
+ public void registerCallback(IBordeauxServiceCallback cb) { | |
+ if (cb != null) mCallbacks.register(cb); | |
+ } | |
+ | |
+ public void unregisterCallback(IBordeauxServiceCallback cb) { | |
+ if (cb != null) mCallbacks.unregister(cb); | |
+ } | |
+ }; | |
+ | |
+ @Override | |
+ public void onTaskRemoved(Intent rootIntent) { | |
+ Toast.makeText(this, "Task removed: " + rootIntent, Toast.LENGTH_LONG).show(); | |
+ } | |
+ | |
+ /** | |
+ * Show a notification while this service is running. | |
+ * TODO: remove the code after production (when service is loaded | |
+ * automatically by the system). | |
+ */ | |
+ private void showNotification() { | |
+ // In this sample, we'll use the same text for the ticker and the expanded notification | |
+ CharSequence text = getText(R.string.remote_service_started); | |
+ | |
+ // The PendingIntent to launch our activity if the user selects this notification | |
+ PendingIntent contentIntent = | |
+ PendingIntent.getActivity(this, 0, | |
+ new Intent("android.bordeaux.DEBUG_CONTROLLER"), 0); | |
+ | |
+ // // Set the info for the views that show in the notification panel. | |
+ | |
+ Notification.Builder builder = new Notification.Builder(this); | |
+ builder.setSmallIcon(R.drawable.ic_bordeaux); | |
+ builder.setWhen(System.currentTimeMillis()); | |
+ builder.setTicker(text); | |
+ builder.setContentTitle(text); | |
+ builder.setContentIntent(contentIntent); | |
+ Notification notification = builder.getNotification(); | |
+ // Send the notification. | |
+ // We use a string id because it is a unique number. We use it later to cancel. | |
+ mNotificationManager.notify(R.string.remote_service_started, notification); | |
+ } | |
+ | |
+} | |
diff -Nur android-15/android/bordeaux/services/BordeauxSessionManager.java android-16/android/bordeaux/services/BordeauxSessionManager.java | |
--- android-15/android/bordeaux/services/BordeauxSessionManager.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/BordeauxSessionManager.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -0,0 +1,206 @@ | |
+/* | |
+ * 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 android.bordeaux.services; | |
+ | |
+import android.bordeaux.services.IBordeauxLearner.ModelChangeCallback; | |
+import android.content.Context; | |
+import android.os.IBinder; | |
+import android.util.Log; | |
+ | |
+import java.lang.NoSuchMethodException; | |
+import java.lang.InstantiationException; | |
+import java.util.concurrent.ConcurrentHashMap; | |
+import java.util.HashMap; | |
+import java.util.ArrayList; | |
+import java.util.List; | |
+import java.util.Map; | |
+import java.util.Set; | |
+ | |
+// This class manages the learning sessions from multiple applications. | |
+// The learning sessions are automatically backed up to the storage. | |
+// | |
+class BordeauxSessionManager { | |
+ | |
+ static private final String TAG = "BordeauxSessionManager"; | |
+ private BordeauxSessionStorage mSessionStorage; | |
+ | |
+ static class Session { | |
+ Class learnerClass; | |
+ IBordeauxLearner learner; | |
+ boolean modified = false; | |
+ }; | |
+ | |
+ static class SessionKey { | |
+ String value; | |
+ }; | |
+ | |
+ // Thread to periodically save the sessions to storage | |
+ class PeriodicSave extends Thread implements Runnable { | |
+ long mSavingInterval = 60000; // 60 seconds | |
+ boolean mQuit = false; | |
+ PeriodicSave() {} | |
+ public void run() { | |
+ while (!mQuit) { | |
+ try { | |
+ sleep(mSavingInterval); | |
+ } catch (InterruptedException e) { | |
+ // thread waked up. | |
+ // ignore | |
+ } | |
+ saveSessions(); | |
+ } | |
+ } | |
+ } | |
+ | |
+ PeriodicSave mSavingThread = new PeriodicSave(); | |
+ | |
+ private ConcurrentHashMap<String, Session> mSessions = | |
+ new ConcurrentHashMap<String, Session>(); | |
+ | |
+ public BordeauxSessionManager(final Context context) { | |
+ mSessionStorage = new BordeauxSessionStorage(context); | |
+ mSavingThread.start(); | |
+ } | |
+ | |
+ class LearningUpdateCallback implements ModelChangeCallback { | |
+ private String mKey; | |
+ | |
+ public LearningUpdateCallback(String key) { | |
+ mKey = key; | |
+ } | |
+ | |
+ public void modelChanged(IBordeauxLearner learner) { | |
+ // Save the session | |
+ Session session = mSessions.get(mKey); | |
+ if (session != null) { | |
+ synchronized(session) { | |
+ if (session.learner != learner) { | |
+ throw new RuntimeException("Session data corrupted!"); | |
+ } | |
+ session.modified = true; | |
+ } | |
+ } | |
+ } | |
+ } | |
+ | |
+ // internal unique key that identifies the learning instance. | |
+ // Composed by the package id of the calling process, learning class name | |
+ // and user specified name. | |
+ public SessionKey getSessionKey(String callingUid, Class learnerClass, String name) { | |
+ SessionKey key = new SessionKey(); | |
+ key.value = callingUid + "#" + "_" + name + "_" + learnerClass.getName(); | |
+ return key; | |
+ } | |
+ | |
+ public IBinder getSessionBinder(Class learnerClass, SessionKey key) { | |
+ if (mSessions.containsKey(key.value)) { | |
+ return mSessions.get(key.value).learner.getBinder(); | |
+ } | |
+ // not in memory cache | |
+ try { | |
+ // try to find it in the database | |
+ Session stored = mSessionStorage.getSession(key.value); | |
+ if (stored != null) { | |
+ // set the callback, so that we can save the state | |
+ stored.learner.setModelChangeCallback(new LearningUpdateCallback(key.value)); | |
+ // found session in the storage, put in the cache | |
+ mSessions.put(key.value, stored); | |
+ return stored.learner.getBinder(); | |
+ } | |
+ | |
+ // if session is not already stored, create a new one. | |
+ Log.i(TAG, "create a new learning session: " + key.value); | |
+ IBordeauxLearner learner = | |
+ (IBordeauxLearner) learnerClass.getConstructor().newInstance(); | |
+ // set the callback, so that we can save the state | |
+ learner.setModelChangeCallback(new LearningUpdateCallback(key.value)); | |
+ Session session = new Session(); | |
+ session.learnerClass = learnerClass; | |
+ session.learner = learner; | |
+ mSessions.put(key.value, session); | |
+ return learner.getBinder(); | |
+ } catch (Exception e) { | |
+ throw new RuntimeException("Can't instantiate class: " + | |
+ learnerClass.getName()); | |
+ } | |
+ } | |
+ | |
+ public void saveSessions() { | |
+ for (Map.Entry<String, Session> session : mSessions.entrySet()) { | |
+ synchronized(session) { | |
+ // Save the session if it's modified. | |
+ if (session.getValue().modified) { | |
+ SessionKey skey = new SessionKey(); | |
+ skey.value = session.getKey(); | |
+ saveSession(skey); | |
+ } | |
+ } | |
+ } | |
+ } | |
+ | |
+ public boolean saveSession(SessionKey key) { | |
+ Session session = mSessions.get(key.value); | |
+ if (session != null) { | |
+ synchronized(session) { | |
+ byte[] model = session.learner.getModel(); | |
+ | |
+ // write to database | |
+ boolean res = mSessionStorage.saveSession(key.value, session.learnerClass, model); | |
+ if (res) | |
+ session.modified = false; | |
+ else { | |
+ Log.e(TAG, "Can't save session: " + key.value); | |
+ } | |
+ return res; | |
+ } | |
+ } | |
+ Log.e(TAG, "Session not found: " + key.value); | |
+ return false; | |
+ } | |
+ | |
+ // Load all session data into memory. | |
+ // The session data will be loaded into the memory from the database, even | |
+ // if this method is not called. | |
+ public void loadSessions() { | |
+ synchronized(mSessions) { | |
+ mSessionStorage.getAllSessions(mSessions); | |
+ for (Map.Entry<String, Session> session : mSessions.entrySet()) { | |
+ // set the callback, so that we can save the state | |
+ session.getValue().learner.setModelChangeCallback( | |
+ new LearningUpdateCallback(session.getKey())); | |
+ } | |
+ } | |
+ } | |
+ | |
+ public void removeAllSessionsFromCaller(String callingUid) { | |
+ // remove in the hash table | |
+ ArrayList<String> remove_keys = new ArrayList<String>(); | |
+ for (Map.Entry<String, Session> session : mSessions.entrySet()) { | |
+ if (session.getKey().startsWith(callingUid + "#")) { | |
+ remove_keys.add(session.getKey()); | |
+ } | |
+ } | |
+ for (String key : remove_keys) { | |
+ mSessions.remove(key); | |
+ } | |
+ // remove all session data from the callingUid in database | |
+ // % is used as wild match for the rest of the string in sql | |
+ int nDeleted = mSessionStorage.removeSessions(callingUid + "#%"); | |
+ if (nDeleted > 0) | |
+ Log.i(TAG, "Successfully deleted " + nDeleted + "sessions"); | |
+ } | |
+} | |
diff -Nur android-15/android/bordeaux/services/BordeauxSessionStorage.java android-16/android/bordeaux/services/BordeauxSessionStorage.java | |
--- android-15/android/bordeaux/services/BordeauxSessionStorage.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/BordeauxSessionStorage.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -0,0 +1,146 @@ | |
+/* | |
+ * 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 android.bordeaux.services; | |
+ | |
+import android.content.ContentValues; | |
+import android.content.Context; | |
+import android.database.Cursor; | |
+import android.database.SQLException; | |
+import android.database.sqlite.SQLiteDatabase; | |
+import android.database.sqlite.SQLiteOpenHelper; | |
+import android.util.Log; | |
+ | |
+import java.lang.System; | |
+import java.util.concurrent.ConcurrentHashMap; | |
+ | |
+// This class manages the database for storing the session data. | |
+// | |
+class BordeauxSessionStorage { | |
+ | |
+ private static final String TAG = "BordeauxSessionStorage"; | |
+ // unique key for the session | |
+ public static final String COLUMN_KEY = "key"; | |
+ // name of the learning class | |
+ public static final String COLUMN_CLASS = "class"; | |
+ // data of the learning model | |
+ public static final String COLUMN_MODEL = "model"; | |
+ // last update time | |
+ public static final String COLUMN_TIME = "time"; | |
+ | |
+ private static final String DATABASE_NAME = "bordeaux"; | |
+ private static final String SESSION_TABLE = "sessions"; | |
+ private static final int DATABASE_VERSION = 1; | |
+ private static final String DATABASE_CREATE = | |
+ "create table " + SESSION_TABLE + "( " + COLUMN_KEY + | |
+ " TEXT primary key, " + COLUMN_CLASS + " TEXT, " + | |
+ COLUMN_MODEL + " BLOB, " + COLUMN_TIME + " INTEGER);"; | |
+ | |
+ private SessionDBHelper mDbHelper; | |
+ private SQLiteDatabase mDbSessions; | |
+ | |
+ BordeauxSessionStorage(final Context context) { | |
+ try { | |
+ mDbHelper = new SessionDBHelper(context); | |
+ mDbSessions = mDbHelper.getWritableDatabase(); | |
+ } catch (SQLException e) { | |
+ throw new RuntimeException("Can't open session database"); | |
+ } | |
+ } | |
+ | |
+ private class SessionDBHelper extends SQLiteOpenHelper { | |
+ SessionDBHelper(Context context) { | |
+ super(context, DATABASE_NAME, null, DATABASE_VERSION); | |
+ } | |
+ | |
+ @Override | |
+ public void onCreate(SQLiteDatabase db) { | |
+ db.execSQL(DATABASE_CREATE); | |
+ } | |
+ | |
+ @Override | |
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { | |
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to " | |
+ + newVersion + ", which will destroy all old data"); | |
+ | |
+ db.execSQL("DROP TABLE IF EXISTS " + SESSION_TABLE); | |
+ onCreate(db); | |
+ } | |
+ } | |
+ | |
+ private ContentValues createSessionEntry(String key, Class learner, byte[] model) { | |
+ ContentValues entry = new ContentValues(); | |
+ entry.put(COLUMN_KEY, key); | |
+ entry.put(COLUMN_TIME, System.currentTimeMillis()); | |
+ entry.put(COLUMN_MODEL, model); | |
+ entry.put(COLUMN_CLASS, learner.getName()); | |
+ return entry; | |
+ } | |
+ | |
+ boolean saveSession(String key, Class learner, byte[] model) { | |
+ ContentValues content = createSessionEntry(key, learner, model); | |
+ long rowID = | |
+ mDbSessions.insertWithOnConflict(SESSION_TABLE, null, content, | |
+ SQLiteDatabase.CONFLICT_REPLACE); | |
+ return rowID >= 0; | |
+ } | |
+ | |
+ private BordeauxSessionManager.Session getSessionFromCursor(Cursor cursor) { | |
+ BordeauxSessionManager.Session session = new BordeauxSessionManager.Session(); | |
+ String className = cursor.getString(cursor.getColumnIndex(COLUMN_CLASS)); | |
+ try { | |
+ session.learnerClass = Class.forName(className); | |
+ session.learner = (IBordeauxLearner) session.learnerClass.getConstructor().newInstance(); | |
+ } catch (Exception e) { | |
+ throw new RuntimeException("Can't instantiate class: " + className); | |
+ } | |
+ byte[] model = cursor.getBlob(cursor.getColumnIndex(COLUMN_MODEL)); | |
+ session.learner.setModel(model); | |
+ return session; | |
+ } | |
+ | |
+ BordeauxSessionManager.Session getSession(String key) { | |
+ Cursor cursor = mDbSessions.query(true, SESSION_TABLE, | |
+ new String[]{COLUMN_KEY, COLUMN_CLASS, COLUMN_MODEL, COLUMN_TIME}, | |
+ COLUMN_KEY + "=\"" + key + "\"", null, null, null, null, null); | |
+ if (cursor == null) return null; | |
+ if (cursor.getCount() == 0) return null; | |
+ if (cursor.getCount() > 1) { | |
+ throw new RuntimeException("Unexpected duplication in session table for key:" + key); | |
+ } | |
+ cursor.moveToFirst(); | |
+ return getSessionFromCursor(cursor); | |
+ } | |
+ | |
+ void getAllSessions(ConcurrentHashMap<String, BordeauxSessionManager.Session> sessions) { | |
+ Cursor cursor = mDbSessions.rawQuery("select * from ?;", new String[]{SESSION_TABLE}); | |
+ if (cursor == null) return; | |
+ do { | |
+ String key = cursor.getString(cursor.getColumnIndex(COLUMN_KEY)); | |
+ BordeauxSessionManager.Session session = getSessionFromCursor(cursor); | |
+ sessions.put(key, session); | |
+ } while (cursor.moveToNext()); | |
+ } | |
+ | |
+ // remove all sessions that have the key that matches the given sql regular | |
+ // expression. | |
+ int removeSessions(String reKey) { | |
+ int nDeleteRows = mDbSessions.delete(SESSION_TABLE, "? like \"?\"", | |
+ new String[]{COLUMN_KEY, reKey}); | |
+ Log.i(TAG, "Number of rows in session table deleted: " + nDeleteRows); | |
+ return nDeleteRows; | |
+ } | |
+} | |
diff -Nur android-15/android/bordeaux/services/IBordeauxLearner.java android-16/android/bordeaux/services/IBordeauxLearner.java | |
--- android-15/android/bordeaux/services/IBordeauxLearner.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/IBordeauxLearner.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -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 android.bordeaux.services; | |
+ | |
+import android.os.IBinder; | |
+ | |
+interface IBordeauxLearner { | |
+ | |
+ interface ModelChangeCallback { | |
+ | |
+ public void modelChanged(IBordeauxLearner learner); | |
+ | |
+ } | |
+ | |
+ public byte [] getModel(); | |
+ | |
+ public boolean setModel(final byte [] modelData); | |
+ | |
+ public IBinder getBinder(); | |
+ | |
+ // call back for the learner model change | |
+ public void setModelChangeCallback(ModelChangeCallback callback); | |
+} | |
diff -Nur android-15/android/bordeaux/services/IntFloat.java android-16/android/bordeaux/services/IntFloat.java | |
--- android-15/android/bordeaux/services/IntFloat.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/IntFloat.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -0,0 +1,40 @@ | |
+package android.bordeaux.services; | |
+ | |
+import android.os.Parcel; | |
+import android.os.Parcelable; | |
+ | |
+public final class IntFloat implements Parcelable { | |
+ public int index; | |
+ public float value; | |
+ | |
+ public static final Parcelable.Creator<IntFloat> CREATOR = new Parcelable.Creator<IntFloat>() { | |
+ public IntFloat createFromParcel(Parcel in) { | |
+ return new IntFloat(in); | |
+ } | |
+ | |
+ public IntFloat[] newArray(int size) { | |
+ return new IntFloat[size]; | |
+ } | |
+ }; | |
+ | |
+ public IntFloat() { | |
+ } | |
+ | |
+ private IntFloat(Parcel in) { | |
+ readFromParcel(in); | |
+ } | |
+ | |
+ public int describeContents() { | |
+ return 0; | |
+ } | |
+ | |
+ public void writeToParcel(Parcel out, int flags) { | |
+ out.writeInt(index); | |
+ out.writeFloat(value); | |
+ } | |
+ | |
+ public void readFromParcel(Parcel in) { | |
+ index = in.readInt(); | |
+ value = in.readFloat(); | |
+ } | |
+} | |
diff -Nur android-15/android/bordeaux/services/Learning_MulticlassPA.java android-16/android/bordeaux/services/Learning_MulticlassPA.java | |
--- android-15/android/bordeaux/services/Learning_MulticlassPA.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/Learning_MulticlassPA.java 2012-06-28 08:41:10.000000000 +0900 | |
@@ -0,0 +1,89 @@ | |
+/* | |
+ * 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 android.bordeaux.services; | |
+ | |
+import android.bordeaux.learning.MulticlassPA; | |
+import android.os.IBinder; | |
+ | |
+import java.util.List; | |
+import java.util.ArrayList; | |
+ | |
+public class Learning_MulticlassPA extends ILearning_MulticlassPA.Stub | |
+ implements IBordeauxLearner { | |
+ private MulticlassPA mMulticlassPA_learner; | |
+ private ModelChangeCallback modelChangeCallback = null; | |
+ | |
+ class IntFloatArray { | |
+ int[] indexArray; | |
+ float[] floatArray; | |
+ }; | |
+ | |
+ private IntFloatArray splitIntFloatArray(List<IntFloat> sample) { | |
+ IntFloatArray splited = new IntFloatArray(); | |
+ ArrayList<IntFloat> s = (ArrayList<IntFloat>)sample; | |
+ splited.indexArray = new int[s.size()]; | |
+ splited.floatArray = new float[s.size()]; | |
+ for (int i = 0; i < s.size(); i++) { | |
+ splited.indexArray[i] = s.get(i).index; | |
+ splited.floatArray[i] = s.get(i).value; | |
+ } | |
+ return splited; | |
+ } | |
+ | |
+ public Learning_MulticlassPA() { | |
+ mMulticlassPA_learner = new MulticlassPA(2, 2, 0.001f); | |
+ } | |
+ | |
+ // Beginning of the IBordeauxLearner Interface implementation | |
+ public byte [] getModel() { | |
+ return null; | |
+ } | |
+ | |
+ public boolean setModel(final byte [] modelData) { | |
+ return false; | |
+ } | |
+ | |
+ public IBinder getBinder() { | |
+ return this; | |
+ } | |
+ | |
+ public void setModelChangeCallback(ModelChangeCallback callback) { | |
+ modelChangeCallback = callback; | |
+ } | |
+ // End of IBordeauxLearner Interface implemenation | |
+ | |
+ // This implementation, combines training and prediction in one step. | |
+ // The return value is the prediction value for the supplied sample. It | |
+ // also update the model with the current sample. | |
+ public void TrainOneSample(List<IntFloat> sample, int target) { | |
+ IntFloatArray splited = splitIntFloatArray(sample); | |
+ mMulticlassPA_learner.sparseTrainOneExample(splited.indexArray, | |
+ splited.floatArray, | |
+ target); | |
+ if (modelChangeCallback != null) { | |
+ modelChangeCallback.modelChanged(this); | |
+ } | |
+ } | |
+ | |
+ public int Classify(List<IntFloat> sample) { | |
+ IntFloatArray splited = splitIntFloatArray(sample); | |
+ int prediction = mMulticlassPA_learner.sparseGetClass(splited.indexArray, | |
+ splited.floatArray); | |
+ return prediction; | |
+ } | |
+ | |
+} | |
diff -Nur android-15/android/bordeaux/services/Learning_StochasticLinearRanker.java android-16/android/bordeaux/services/Learning_StochasticLinearRanker.java | |
--- android-15/android/bordeaux/services/Learning_StochasticLinearRanker.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/Learning_StochasticLinearRanker.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -0,0 +1,148 @@ | |
+/* | |
+ * 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 android.bordeaux.services; | |
+ | |
+import android.bordeaux.learning.StochasticLinearRanker; | |
+import android.bordeaux.services.IBordeauxLearner.ModelChangeCallback; | |
+import android.os.IBinder; | |
+import android.util.Log; | |
+import java.util.List; | |
+import java.util.ArrayList; | |
+import java.io.*; | |
+import java.lang.ClassNotFoundException; | |
+import java.util.Arrays; | |
+import java.util.ArrayList; | |
+import java.util.List; | |
+import java.util.Scanner; | |
+import java.io.ByteArrayOutputStream; | |
+import java.util.HashMap; | |
+import java.util.Map; | |
+ | |
+public class Learning_StochasticLinearRanker extends ILearning_StochasticLinearRanker.Stub | |
+ implements IBordeauxLearner { | |
+ | |
+ private final String TAG = "ILearning_StochasticLinearRanker"; | |
+ private StochasticLinearRankerWithPrior mLearningSlRanker = null; | |
+ private ModelChangeCallback modelChangeCallback = null; | |
+ | |
+ public Learning_StochasticLinearRanker(){ | |
+ } | |
+ | |
+ public void ResetRanker(){ | |
+ if (mLearningSlRanker == null) | |
+ mLearningSlRanker = new StochasticLinearRankerWithPrior(); | |
+ mLearningSlRanker.resetRanker(); | |
+ } | |
+ | |
+ public boolean UpdateClassifier(List<StringFloat> sample_1, List<StringFloat> sample_2){ | |
+ ArrayList<StringFloat> temp_1 = (ArrayList<StringFloat>)sample_1; | |
+ String[] keys_1 = new String[temp_1.size()]; | |
+ float[] values_1 = new float[temp_1.size()]; | |
+ for (int i = 0; i < temp_1.size(); i++){ | |
+ keys_1[i] = temp_1.get(i).key; | |
+ values_1[i] = temp_1.get(i).value; | |
+ } | |
+ ArrayList<StringFloat> temp_2 = (ArrayList<StringFloat>)sample_2; | |
+ String[] keys_2 = new String[temp_2.size()]; | |
+ float[] values_2 = new float[temp_2.size()]; | |
+ for (int i = 0; i < temp_2.size(); i++){ | |
+ keys_2[i] = temp_2.get(i).key; | |
+ values_2[i] = temp_2.get(i).value; | |
+ } | |
+ if (mLearningSlRanker == null) | |
+ mLearningSlRanker = new StochasticLinearRankerWithPrior(); | |
+ boolean res = mLearningSlRanker.updateClassifier(keys_1,values_1,keys_2,values_2); | |
+ if (res && modelChangeCallback != null) { | |
+ modelChangeCallback.modelChanged(this); | |
+ } | |
+ return res; | |
+ } | |
+ | |
+ public float ScoreSample(List<StringFloat> sample) { | |
+ ArrayList<StringFloat> temp = (ArrayList<StringFloat>)sample; | |
+ String[] keys = new String[temp.size()]; | |
+ float[] values = new float[temp.size()]; | |
+ for (int i = 0; i < temp.size(); i++){ | |
+ keys[i] = temp.get(i).key; | |
+ values[i] = temp.get(i).value; | |
+ } | |
+ if (mLearningSlRanker == null) | |
+ mLearningSlRanker = new StochasticLinearRankerWithPrior(); | |
+ return mLearningSlRanker.scoreSample(keys,values); | |
+ } | |
+ | |
+ public boolean SetModelPriorWeight(List<StringFloat> sample) { | |
+ ArrayList<StringFloat> temp = (ArrayList<StringFloat>)sample; | |
+ HashMap<String, Float> weights = new HashMap<String, Float>(); | |
+ for (int i = 0; i < temp.size(); i++) | |
+ weights.put(temp.get(i).key, temp.get(i).value); | |
+ if (mLearningSlRanker == null) | |
+ mLearningSlRanker = new StochasticLinearRankerWithPrior(); | |
+ return mLearningSlRanker.setModelPriorWeights(weights); | |
+ } | |
+ | |
+ public boolean SetModelParameter(String key, String value) { | |
+ if (mLearningSlRanker == null) | |
+ mLearningSlRanker = new StochasticLinearRankerWithPrior(); | |
+ return mLearningSlRanker.setModelParameter(key,value); | |
+ } | |
+ | |
+ // Beginning of the IBordeauxLearner Interface implementation | |
+ public byte [] getModel() { | |
+ if (mLearningSlRanker == null) | |
+ mLearningSlRanker = new StochasticLinearRankerWithPrior(); | |
+ StochasticLinearRankerWithPrior.Model model = mLearningSlRanker.getModel(); | |
+ try { | |
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); | |
+ ObjectOutputStream objStream = new ObjectOutputStream(byteStream); | |
+ objStream.writeObject(model); | |
+ //return byteStream.toByteArray(); | |
+ byte[] bytes = byteStream.toByteArray(); | |
+ Log.i(TAG, "getModel: " + bytes); | |
+ return bytes; | |
+ } catch (IOException e) { | |
+ throw new RuntimeException("Can't get model"); | |
+ } | |
+ } | |
+ | |
+ public boolean setModel(final byte [] modelData) { | |
+ try { | |
+ ByteArrayInputStream input = new ByteArrayInputStream(modelData); | |
+ ObjectInputStream objStream = new ObjectInputStream(input); | |
+ StochasticLinearRankerWithPrior.Model model = | |
+ (StochasticLinearRankerWithPrior.Model) objStream.readObject(); | |
+ if (mLearningSlRanker == null) | |
+ mLearningSlRanker = new StochasticLinearRankerWithPrior(); | |
+ boolean res = mLearningSlRanker.loadModel(model); | |
+ Log.i(TAG, "LoadModel: " + modelData); | |
+ return res; | |
+ } catch (IOException e) { | |
+ throw new RuntimeException("Can't load model"); | |
+ } catch (ClassNotFoundException e) { | |
+ throw new RuntimeException("Learning class not found"); | |
+ } | |
+ } | |
+ | |
+ public IBinder getBinder() { | |
+ return this; | |
+ } | |
+ | |
+ public void setModelChangeCallback(ModelChangeCallback callback) { | |
+ modelChangeCallback = callback; | |
+ } | |
+ // End of IBordeauxLearner Interface implemenation | |
+} | |
diff -Nur android-15/android/bordeaux/services/StochasticLinearRankerWithPrior.java android-16/android/bordeaux/services/StochasticLinearRankerWithPrior.java | |
--- android-15/android/bordeaux/services/StochasticLinearRankerWithPrior.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/StochasticLinearRankerWithPrior.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -0,0 +1,211 @@ | |
+/* | |
+ * 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 android.bordeaux.services; | |
+import android.util.Log; | |
+ | |
+import android.bordeaux.learning.StochasticLinearRanker; | |
+import java.util.HashMap; | |
+import java.util.Map; | |
+import java.io.Serializable; | |
+ | |
+public class StochasticLinearRankerWithPrior extends StochasticLinearRanker { | |
+ private final String TAG = "StochasticLinearRankerWithPrior"; | |
+ private final float EPSILON = 0.0001f; | |
+ | |
+ /* If the is parameter is true, the final score would be a | |
+ linear combination of user model and prior model */ | |
+ private final String USE_PRIOR = "usePriorInformation"; | |
+ | |
+ /* When prior model is used, this parmaeter will set the mixing factor, alpha. */ | |
+ private final String SET_ALPHA = "setAlpha"; | |
+ | |
+ /* When prior model is used, If this parameter is true then algorithm will use | |
+ the automatic cross validated alpha for mixing user model and prior model */ | |
+ private final String USE_AUTO_ALPHA = "useAutoAlpha"; | |
+ | |
+ /* When automatic cross validation is active, this parameter will | |
+ set the forget rate in cross validation. */ | |
+ private final String SET_FORGET_RATE = "setForgetRate"; | |
+ | |
+ /* When automatic cross validation is active, this parameter will | |
+ set the minium number of required training pairs before using the user model */ | |
+ private final String SET_MIN_TRAIN_PAIR = "setMinTrainingPair"; | |
+ | |
+ private final String SET_USER_PERF = "setUserPerformance"; | |
+ private final String SET_PRIOR_PERF = "setPriorPerformance"; | |
+ private final String SET_NUM_TRAIN_PAIR = "setNumberTrainingPairs"; | |
+ private final String SET_AUTO_ALPHA = "setAutoAlpha"; | |
+ | |
+ | |
+ | |
+ private HashMap<String, Float> mPriorWeights = new HashMap<String, Float>(); | |
+ private float mAlpha = 0; | |
+ private float mAutoAlpha = 0; | |
+ private float mForgetRate = 0; | |
+ private float mUserRankerPerf = 0; | |
+ private float mPriorRankerPerf = 0; | |
+ private int mMinReqTrainingPair = 0; | |
+ private int mNumTrainPair = 0; | |
+ private boolean mUsePrior = false; | |
+ private boolean mUseAutoAlpha = false; | |
+ | |
+ static public class Model implements Serializable { | |
+ public StochasticLinearRanker.Model uModel = new StochasticLinearRanker.Model(); | |
+ public HashMap<String, Float> priorWeights = new HashMap<String, Float>(); | |
+ public HashMap<String, String> priorParameters = new HashMap<String, String>(); | |
+ } | |
+ | |
+ @Override | |
+ public void resetRanker(){ | |
+ super.resetRanker(); | |
+ mPriorWeights.clear(); | |
+ mAlpha = 0; | |
+ mAutoAlpha = 0; | |
+ mForgetRate = 0; | |
+ mMinReqTrainingPair = 0; | |
+ mUserRankerPerf = 0; | |
+ mPriorRankerPerf = 0; | |
+ mNumTrainPair = 0; | |
+ mUsePrior = false; | |
+ mUseAutoAlpha = false; | |
+ } | |
+ | |
+ @Override | |
+ public float scoreSample(String[] keys, float[] values) { | |
+ if (!mUsePrior){ | |
+ return super.scoreSample(keys, values); | |
+ } else { | |
+ if (mUseAutoAlpha) { | |
+ if (mNumTrainPair > mMinReqTrainingPair) | |
+ return (1 - mAutoAlpha) * super.scoreSample(keys,values) + | |
+ mAutoAlpha * priorScoreSample(keys,values); | |
+ else | |
+ return priorScoreSample(keys,values); | |
+ } else | |
+ return (1 - mAlpha) * super.scoreSample(keys,values) + | |
+ mAlpha * priorScoreSample(keys,values); | |
+ } | |
+ } | |
+ | |
+ public float priorScoreSample(String[] keys, float[] values) { | |
+ float score = 0; | |
+ for (int i=0; i< keys.length; i++){ | |
+ if (mPriorWeights.get(keys[i]) != null ) | |
+ score = score + mPriorWeights.get(keys[i]) * values[i]; | |
+ } | |
+ return score; | |
+ } | |
+ | |
+ @Override | |
+ public boolean updateClassifier(String[] keys_positive, | |
+ float[] values_positive, | |
+ String[] keys_negative, | |
+ float[] values_negative){ | |
+ if (mUsePrior && mUseAutoAlpha && (mNumTrainPair > mMinReqTrainingPair)) | |
+ updateAutoAlpha(keys_positive, values_positive, keys_negative, values_negative); | |
+ mNumTrainPair ++; | |
+ return super.updateClassifier(keys_positive, values_positive, | |
+ keys_negative, values_negative); | |
+ } | |
+ | |
+ void updateAutoAlpha(String[] keys_positive, | |
+ float[] values_positive, | |
+ String[] keys_negative, | |
+ float[] values_negative) { | |
+ float positiveUserScore = super.scoreSample(keys_positive, values_positive); | |
+ float negativeUserScore = super.scoreSample(keys_negative, values_negative); | |
+ float positivePriorScore = priorScoreSample(keys_positive, values_positive); | |
+ float negativePriorScore = priorScoreSample(keys_negative, values_negative); | |
+ float userDecision = 0; | |
+ float priorDecision = 0; | |
+ if (positiveUserScore > negativeUserScore) | |
+ userDecision = 1; | |
+ if (positivePriorScore > negativePriorScore) | |
+ priorDecision = 1; | |
+ mUserRankerPerf = (1 - mForgetRate) * mUserRankerPerf + userDecision; | |
+ mPriorRankerPerf = (1 - mForgetRate) * mPriorRankerPerf + priorDecision; | |
+ mAutoAlpha = (mPriorRankerPerf + EPSILON) / (mUserRankerPerf + mPriorRankerPerf + EPSILON); | |
+ } | |
+ | |
+ public Model getModel(){ | |
+ Model m = new Model(); | |
+ m.uModel = super.getUModel(); | |
+ m.priorWeights.putAll(mPriorWeights); | |
+ m.priorParameters.put(SET_ALPHA, String.valueOf(mAlpha)); | |
+ m.priorParameters.put(SET_AUTO_ALPHA, String.valueOf(mAutoAlpha)); | |
+ m.priorParameters.put(SET_FORGET_RATE, String.valueOf(mForgetRate)); | |
+ m.priorParameters.put(SET_MIN_TRAIN_PAIR, String.valueOf(mMinReqTrainingPair)); | |
+ m.priorParameters.put(SET_USER_PERF, String.valueOf(mUserRankerPerf)); | |
+ m.priorParameters.put(SET_PRIOR_PERF, String.valueOf(mPriorRankerPerf)); | |
+ m.priorParameters.put(SET_NUM_TRAIN_PAIR, String.valueOf(mNumTrainPair)); | |
+ m.priorParameters.put(USE_AUTO_ALPHA, String.valueOf(mUseAutoAlpha)); | |
+ m.priorParameters.put(USE_PRIOR, String.valueOf(mUsePrior)); | |
+ return m; | |
+ } | |
+ | |
+ public boolean loadModel(Model m) { | |
+ mPriorWeights.clear(); | |
+ mPriorWeights.putAll(m.priorWeights); | |
+ for (Map.Entry<String, String> e : m.priorParameters.entrySet()) { | |
+ boolean res = setModelParameter(e.getKey(), e.getValue()); | |
+ if (!res) return false; | |
+ } | |
+ return super.loadModel(m.uModel); | |
+ } | |
+ | |
+ public boolean setModelPriorWeights(HashMap<String, Float> pw){ | |
+ mPriorWeights.clear(); | |
+ mPriorWeights.putAll(pw); | |
+ return true; | |
+ } | |
+ | |
+ public boolean setModelParameter(String key, String value){ | |
+ if (key.equals(USE_AUTO_ALPHA)){ | |
+ mUseAutoAlpha = Boolean.parseBoolean(value); | |
+ } else if (key.equals(USE_PRIOR)){ | |
+ mUsePrior = Boolean.parseBoolean(value); | |
+ } else if (key.equals(SET_ALPHA)){ | |
+ mAlpha = Float.valueOf(value.trim()).floatValue(); | |
+ }else if (key.equals(SET_AUTO_ALPHA)){ | |
+ mAutoAlpha = Float.valueOf(value.trim()).floatValue(); | |
+ }else if (key.equals(SET_FORGET_RATE)){ | |
+ mForgetRate = Float.valueOf(value.trim()).floatValue(); | |
+ }else if (key.equals(SET_MIN_TRAIN_PAIR)){ | |
+ mMinReqTrainingPair = (int) Float.valueOf(value.trim()).floatValue(); | |
+ }else if (key.equals(SET_USER_PERF)){ | |
+ mUserRankerPerf = Float.valueOf(value.trim()).floatValue(); | |
+ }else if (key.equals(SET_PRIOR_PERF)){ | |
+ mPriorRankerPerf = Float.valueOf(value.trim()).floatValue(); | |
+ }else if (key.equals(SET_NUM_TRAIN_PAIR)){ | |
+ mNumTrainPair = (int) Float.valueOf(value.trim()).floatValue(); | |
+ }else | |
+ return super.setModelParameter(key, value); | |
+ return true; | |
+ } | |
+ | |
+ public void print(Model m){ | |
+ super.print(m.uModel); | |
+ String Spw = ""; | |
+ for (Map.Entry<String, Float> e : m.priorWeights.entrySet()) | |
+ Spw = Spw + "<" + e.getKey() + "," + e.getValue() + "> "; | |
+ Log.i(TAG, "Prior model is " + Spw); | |
+ String Spp = ""; | |
+ for (Map.Entry<String, String> e : m.priorParameters.entrySet()) | |
+ Spp = Spp + "<" + e.getKey() + "," + e.getValue() + "> "; | |
+ Log.i(TAG, "Prior parameters are " + Spp); | |
+ } | |
+} | |
diff -Nur android-15/android/bordeaux/services/StringFloat.java android-16/android/bordeaux/services/StringFloat.java | |
--- android-15/android/bordeaux/services/StringFloat.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/bordeaux/services/StringFloat.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -0,0 +1,41 @@ | |
+package android.bordeaux.services; | |
+ | |
+import android.os.Parcel; | |
+import android.os.Parcelable; | |
+ | |
+public final class StringFloat implements Parcelable { | |
+ public String key; | |
+ public float value; | |
+ | |
+ public static final Parcelable.Creator<StringFloat> | |
+ CREATOR = new Parcelable.Creator<StringFloat>() { | |
+ public StringFloat createFromParcel(Parcel in) { | |
+ return new StringFloat(in); | |
+ } | |
+ | |
+ public StringFloat[] newArray(int size) { | |
+ return new StringFloat[size]; | |
+ } | |
+ }; | |
+ | |
+ public StringFloat() { | |
+ } | |
+ | |
+ private StringFloat(Parcel in) { | |
+ readFromParcel(in); | |
+ } | |
+ | |
+ public int describeContents() { | |
+ return 0; | |
+ } | |
+ | |
+ public void writeToParcel(Parcel out, int flags) { | |
+ out.writeString(key); | |
+ out.writeFloat(value); | |
+ } | |
+ | |
+ public void readFromParcel(Parcel in) { | |
+ key = in.readString(); | |
+ value = in.readFloat(); | |
+ } | |
+} | |
diff -Nur android-15/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java android-16/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java | |
--- android-15/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java 1970-01-01 09:00:00.000000000 +0900 | |
+++ android-16/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -0,0 +1,90 @@ | |
+/* | |
+ * Copyright (C) 2011 The Android Open Source Project | |
+ * | |
+ * Licensed under the Apache License, Version 2.0 (the "License"); | |
+ * you may not use this file except in compliance with the License. | |
+ * You may obtain a copy of the License at | |
+ * | |
+ * http://www.apache.org/licenses/LICENSE-2.0 | |
+ * | |
+ * Unless required by applicable law or agreed to in writing, software | |
+ * distributed under the License is distributed on an "AS IS" BASIS, | |
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
+ * See the License for the specific language governing permissions and | |
+ * limitations under the License. | |
+ */ | |
+ | |
+package android.camera.mediaeffects.tests.functional; | |
+ | |
+import android.media.filterfw.samples.CameraEffectsRecordingSample; | |
+import android.app.Activity; | |
+import android.app.Instrumentation; | |
+import android.content.Intent; | |
+import android.test.ActivityInstrumentationTestCase2; | |
+import android.test.suitebuilder.annotation.LargeTest; | |
+import android.view.KeyEvent; | |
+import android.util.Log; | |
+import android.content.Intent; | |
+import android.os.Environment; | |
+import android.media.MediaMetadataRetriever; | |
+import android.net.Uri; | |
+import java.io.File; | |
+ | |
+public class EffectsVideoCapture extends ActivityInstrumentationTestCase2 | |
+ <CameraEffectsRecordingSample> { | |
+ private static final String TAG = "EffectsVideoCaptureTest"; | |
+ private static final long WAIT_FOR_PREVIEW = 4 * 1000; // 4 seconds | |
+ | |
+ public EffectsVideoCapture() { | |
+ super(CameraEffectsRecordingSample.class); | |
+ } | |
+ | |
+ private void captureVideos(String reportTag, Instrumentation inst) throws Exception{ | |
+ int total_num_of_videos = 1; | |
+ int video_duration = 4 * 1000; // 4 seconds | |
+ | |
+ Log.v(TAG, reportTag); | |
+ for (int i = 0; i < total_num_of_videos; i++) { | |
+ Thread.sleep(WAIT_FOR_PREVIEW); | |
+ // record a video | |
+ inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA); | |
+ Thread.sleep(video_duration); | |
+ inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA); | |
+ } | |
+ } | |
+ | |
+ @LargeTest | |
+ public void testBackEffectsVideoCapture() throws Exception { | |
+ Instrumentation inst = getInstrumentation(); | |
+ | |
+ Intent intent = new Intent(); | |
+ intent.setClass(getInstrumentation().getTargetContext(), | |
+ CameraEffectsRecordingSample.class); | |
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
+ intent.putExtra("OUTPUT_FILENAME", Environment.getExternalStorageDirectory().toString() | |
+ + "/CameraEffectsRecordingTest.mp4"); | |
+ Activity act = inst.startActivitySync(intent); | |
+ captureVideos("Back Camera Video Capture\n", inst); | |
+ act.finish(); | |
+ | |
+ // Verification | |
+ File file = new File(Environment.getExternalStorageDirectory(), | |
+ "CameraEffectsRecordingTest.mp4"); | |
+ Uri uri = Uri.fromFile(file); | |
+ verify(getActivity(), uri); | |
+ } | |
+ | |
+ // Verify result code, result data, and the duration. | |
+ private void verify(CameraEffectsRecordingSample activity, Uri uri) throws Exception { | |
+ assertNotNull(uri); | |
+ // Verify the video file | |
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever(); | |
+ retriever.setDataSource(activity, uri); | |
+ String duration = retriever.extractMetadata( | |
+ MediaMetadataRetriever.METADATA_KEY_DURATION); | |
+ assertNotNull(duration); | |
+ int durationValue = Integer.parseInt(duration); | |
+ Log.v(TAG, "Video duration is " + durationValue); | |
+ assertTrue(durationValue > 0); | |
+ } | |
+} | |
diff -Nur android-15/android/content/AbstractThreadedSyncAdapter.java android-16/android/content/AbstractThreadedSyncAdapter.java | |
--- android-15/android/content/AbstractThreadedSyncAdapter.java 2012-06-18 20:00:45.000000000 +0900 | |
+++ android-16/android/content/AbstractThreadedSyncAdapter.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -21,6 +21,7 @@ | |
import android.os.IBinder; | |
import android.os.Process; | |
import android.os.RemoteException; | |
+import android.os.Trace; | |
import java.util.HashMap; | |
import java.util.concurrent.atomic.AtomicInteger; | |
@@ -233,9 +234,15 @@ | |
mThreadsKey = toSyncKey(account); | |
} | |
+ @Override | |
public void run() { | |
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); | |
+ // Trace this sync instance. Note, conceptually this should be in | |
+ // SyncStorageEngine.insertStartSyncEvent(), but the trace functions require unique | |
+ // threads in order to track overlapping operations, so we'll do it here for now. | |
+ Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, mAuthority); | |
+ | |
SyncResult syncResult = new SyncResult(); | |
ContentProviderClient provider = null; | |
try { | |
@@ -250,6 +257,8 @@ | |
syncResult.databaseError = true; | |
} | |
} finally { | |
+ Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER); | |
+ | |
if (provider != null) { | |
provider.release(); | |
} | |
diff -Nur android-15/android/content/AsyncTaskLoader.java android-16/android/content/AsyncTaskLoader.java | |
--- android-15/android/content/AsyncTaskLoader.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/content/AsyncTaskLoader.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -18,6 +18,7 @@ | |
import android.os.AsyncTask; | |
import android.os.Handler; | |
+import android.os.OperationCanceledException; | |
import android.os.SystemClock; | |
import android.util.Slog; | |
import android.util.TimeUtils; | |
@@ -53,19 +54,33 @@ | |
static final boolean DEBUG = false; | |
final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable { | |
+ private final CountDownLatch mDone = new CountDownLatch(1); | |
- D result; | |
+ // Set to true to indicate that the task has been posted to a handler for | |
+ // execution at a later time. Used to throttle updates. | |
boolean waiting; | |
- private CountDownLatch done = new CountDownLatch(1); | |
- | |
/* Runs on a worker thread */ | |
@Override | |
protected D doInBackground(Void... params) { | |
if (DEBUG) Slog.v(TAG, this + " >>> doInBackground"); | |
- result = AsyncTaskLoader.this.onLoadInBackground(); | |
- if (DEBUG) Slog.v(TAG, this + " <<< doInBackground"); | |
- return result; | |
+ try { | |
+ D data = AsyncTaskLoader.this.onLoadInBackground(); | |
+ if (DEBUG) Slog.v(TAG, this + " <<< doInBackground"); | |
+ return data; | |
+ } catch (OperationCanceledException ex) { | |
+ if (!isCancelled()) { | |
+ // onLoadInBackground threw a canceled exception spuriously. | |
+ // This is problematic because it means that the LoaderManager did not | |
+ // cancel the Loader itself and still expects to receive a result. | |
+ // Additionally, the Loader's own state will not have been updated to | |
+ // reflect the fact that the task was being canceled. | |
+ // So we treat this case as an unhandled exception. | |
+ throw ex; | |
+ } | |
+ if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)"); | |
+ return null; | |
+ } | |
} | |
/* Runs on the UI thread */ | |
@@ -75,25 +90,37 @@ | |
try { | |
AsyncTaskLoader.this.dispatchOnLoadComplete(this, data); | |
} finally { | |
- done.countDown(); | |
+ mDone.countDown(); | |
} | |
} | |
+ /* Runs on the UI thread */ | |
@Override | |
- protected void onCancelled() { | |
+ protected void onCancelled(D data) { | |
if (DEBUG) Slog.v(TAG, this + " onCancelled"); | |
try { | |
- AsyncTaskLoader.this.dispatchOnCancelled(this, result); | |
+ AsyncTaskLoader.this.dispatchOnCancelled(this, data); | |
} finally { | |
- done.countDown(); | |
+ mDone.countDown(); | |
} | |
} | |
+ /* Runs on the UI thread, when the waiting task is posted to a handler. | |
+ * This method is only executed when task execution was deferred (waiting was true). */ | |
@Override | |
public void run() { | |
waiting = false; | |
AsyncTaskLoader.this.executePendingTask(); | |
} | |
+ | |
+ /* Used for testing purposes to wait for the task to complete. */ | |
+ public void waitForLoader() { | |
+ try { | |
+ mDone.await(); | |
+ } catch (InterruptedException e) { | |
+ // Ignore | |
+ } | |
+ } | |
} | |
volatile LoadTask mTask; | |
@@ -109,7 +136,7 @@ | |
/** | |
* Set amount to throttle updates by. This is the minimum time from | |
- * when the last {@link #onLoadInBackground()} call has completed until | |
+ * when the last {@link #loadInBackground()} call has completed until | |
* a new load is scheduled. | |
* | |
* @param delayMS Amount of delay, in milliseconds. | |
@@ -130,24 +157,9 @@ | |
executePendingTask(); | |
} | |
- /** | |
- * Attempt to cancel the current load task. See {@link AsyncTask#cancel(boolean)} | |
- * for more info. Must be called on the main thread of the process. | |
- * | |
- * <p>Cancelling is not an immediate operation, since the load is performed | |
- * in a background thread. If there is currently a load in progress, this | |
- * method requests that the load be cancelled, and notes this is the case; | |
- * once the background thread has completed its work its remaining state | |
- * will be cleared. If another load request comes in during this time, | |
- * it will be held until the cancelled load is complete. | |
- * | |
- * @return Returns <tt>false</tt> if the task could not be cancelled, | |
- * typically because it has already completed normally, or | |
- * because {@link #startLoading()} hasn't been called; returns | |
- * <tt>true</tt> otherwise. | |
- */ | |
- public boolean cancelLoad() { | |
- if (DEBUG) Slog.v(TAG, "cancelLoad: mTask=" + mTask); | |
+ @Override | |
+ protected boolean onCancelLoad() { | |
+ if (DEBUG) Slog.v(TAG, "onCancelLoad: mTask=" + mTask); | |
if (mTask != null) { | |
if (mCancellingTask != null) { | |
// There was a pending task already waiting for a previous | |
@@ -173,6 +185,7 @@ | |
if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled); | |
if (cancelled) { | |
mCancellingTask = mTask; | |
+ cancelLoadInBackground(); | |
} | |
mTask = null; | |
return cancelled; | |
@@ -183,7 +196,10 @@ | |
/** | |
* Called if the task was canceled before it was completed. Gives the class a chance | |
- * to properly dispose of the result. | |
+ * to clean up post-cancellation and to properly dispose of the result. | |
+ * | |
+ * @param data The value that was returned by {@link #loadInBackground}, or null | |
+ * if the task threw {@link OperationCanceledException}. | |
*/ | |
public void onCanceled(D data) { | |
} | |
@@ -217,6 +233,8 @@ | |
if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!"); | |
mLastLoadCompleteTime = SystemClock.uptimeMillis(); | |
mCancellingTask = null; | |
+ if (DEBUG) Slog.v(TAG, "Delivering cancellation"); | |
+ deliverCancellation(); | |
executePendingTask(); | |
} | |
} | |
@@ -239,23 +257,76 @@ | |
} | |
/** | |
+ * Called on a worker thread to perform the actual load and to return | |
+ * the result of the load operation. | |
+ * | |
+ * Implementations should not deliver the result directly, but should return them | |
+ * from this method, which will eventually end up calling {@link #deliverResult} on | |
+ * the UI thread. If implementations need to process the results on the UI thread | |
+ * they may override {@link #deliverResult} and do so there. | |
+ * | |
+ * To support cancellation, this method should periodically check the value of | |
+ * {@link #isLoadInBackgroundCanceled} and terminate when it returns true. | |
+ * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load | |
+ * directly instead of polling {@link #isLoadInBackgroundCanceled}. | |
+ * | |
+ * When the load is canceled, this method may either return normally or throw | |
+ * {@link OperationCanceledException}. In either case, the {@link Loader} will | |
+ * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the | |
+ * result object, if any. | |
+ * | |
+ * @return The result of the load operation. | |
+ * | |
+ * @throws OperationCanceledException if the load is canceled during execution. | |
+ * | |
+ * @see #isLoadInBackgroundCanceled | |
+ * @see #cancelLoadInBackground | |
+ * @see #onCanceled | |
*/ | |
public abstract D loadInBackground(); | |
/** | |
- * Called on a worker thread to perform the actual load. Implementations should not deliver the | |
- * result directly, but should return them from this method, which will eventually end up | |
- * calling {@link #deliverResult} on the UI thread. If implementations need to process | |
- * the results on the UI thread they may override {@link #deliverResult} and do so | |
- * there. | |
+ * Calls {@link #loadInBackground()}. | |
+ * | |
+ * This method is reserved for use by the loader framework. | |
+ * Subclasses should override {@link #loadInBackground} instead of this method. | |
+ * | |
+ * @return The result of the load operation. | |
* | |
- * @return Implementations must return the result of their load operation. | |
+ * @throws OperationCanceledException if the load is canceled during execution. | |
+ * | |
+ * @see #loadInBackground | |
*/ | |
protected D onLoadInBackground() { | |
return loadInBackground(); | |
} | |
/** | |
+ * Called on the main thread to abort a load in progress. | |
+ * | |
+ * Override this method to abort the current invocation of {@link #loadInBackground} | |
+ * that is running in the background on a worker thread. | |
+ * | |
+ * This method should do nothing if {@link #loadInBackground} has not started | |
+ * running or if it has already finished. | |
+ * | |
+ * @see #loadInBackground | |
+ */ | |
+ public void cancelLoadInBackground() { | |
+ } | |
+ | |
+ /** | |
+ * Returns true if the current invocation of {@link #loadInBackground} is being canceled. | |
+ * | |
+ * @return True if the current invocation of {@link #loadInBackground} is being canceled. | |
+ * | |
+ * @see #loadInBackground | |
+ */ | |
+ public boolean isLoadInBackgroundCanceled() { | |
+ return mCancellingTask != null; | |
+ } | |
+ | |
+ /** | |
* Locks the current thread until the loader completes the current load | |
* operation. Returns immediately if there is no load operation running. | |
* Should not be called from the UI thread: calling it from the UI | |
@@ -268,11 +339,7 @@ | |
public void waitForLoader() { | |
LoadTask task = mTask; | |
if (task != null) { | |
- try { | |
- task.done.await(); | |
- } catch (InterruptedException e) { | |
- // Ignore | |
- } | |
+ task.waitForLoader(); | |
} | |
} | |
diff -Nur android-15/android/content/BroadcastReceiver.java android-16/android/content/BroadcastReceiver.java | |
--- android-15/android/content/BroadcastReceiver.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/content/BroadcastReceiver.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -446,13 +446,17 @@ | |
/** | |
* This method is called when the BroadcastReceiver is receiving an Intent | |
* broadcast. During this time you can use the other methods on | |
- * BroadcastReceiver to view/modify the current result values. The function | |
- * is normally called within the main thread of its process, so you should | |
+ * BroadcastReceiver to view/modify the current result values. This method | |
+ * is always called within the main thread of its process, unless you | |
+ * explicitly asked for it to be scheduled on a different thread using | |
+ * {@link android.content.Context#registerReceiver(BroadcastReceiver, | |
+ * IntentFilter, String, android.os.Handler)}. When it runs on the main | |
+ * thread you should | |
* never perform long-running operations in it (there is a timeout of | |
* 10 seconds that the system allows before considering the receiver to | |
* be blocked and a candidate to be killed). You cannot launch a popup dialog | |
* in your implementation of onReceive(). | |
- * | |
+ * | |
* <p><b>If this BroadcastReceiver was launched through a <receiver> tag, | |
* then the object is no longer alive after returning from this | |
* function.</b> This means you should not perform any operations that | |
diff -Nur android-15/android/content/ClipData.java android-16/android/content/ClipData.java | |
--- android-15/android/content/ClipData.java 2012-06-18 20:00:42.000000000 +0900 | |
+++ android-16/android/content/ClipData.java 2012-06-28 08:41:09.000000000 +0900 | |
@@ -21,7 +21,12 @@ | |
import android.net.Uri; | |
import android.os.Parcel; | |
import android.os.Parcelable; | |
+import android.text.Html; | |
+import android.text.Spannable; | |
+import android.text.SpannableStringBuilder; | |
+import android.text.Spanned; | |
import android.text.TextUtils; | |
+import android.text.style.URLSpan; | |
import android.util.Log; | |
import java.io.FileInputStream; | |
@@ -144,6 +149,8 @@ | |
public class ClipData implements Parcelable { | |
static final String[] MIMETYPES_TEXT_PLAIN = new String[] { | |
ClipDescription.MIMETYPE_TEXT_PLAIN }; | |
+ static final String[] MIMETYPES_TEXT_HTML = new String[] { | |
+ ClipDescription.MIMETYPE_TEXT_HTML }; | |
static final String[] MIMETYPES_TEXT_URILIST = new String[] { | |
ClipDescription.MIMETYPE_TEXT_URILIST }; | |
static final String[] MIMETYPES_TEXT_INTENT = new String[] { | |
@@ -153,7 +160,7 @@ | |
final Bitmap mIcon; | |
- final ArrayList<Item> mItems = new ArrayList<Item>(); | |
+ final ArrayList<Item> mItems; | |
/** | |
* Description of a single item in a ClippedData. | |
@@ -176,6 +183,7 @@ | |
*/ | |
public static class Item { | |
final CharSequence mText; | |
+ final String mHtmlText; | |
final Intent mIntent; | |
final Uri mUri; | |
@@ -184,6 +192,20 @@ | |
*/ | |
public Item(CharSequence text) { | |
mText = text; | |
+ mHtmlText = null; | |
+ mIntent = null; | |
+ mUri = null; | |
+ } | |
+ | |
+ /** | |
+ * Create an Item consisting of a single block of (possibly styled) text, | |
+ * with an alternative HTML formatted representation. You <em>must</em> | |
+ * supply a plain text representation in addition to HTML text; coercion | |
+ * will not be done from HTML formated text into plain text. | |
+ */ | |
+ public Item(CharSequence text, String htmlText) { | |
+ mText = text; | |
+ mHtmlText = htmlText; | |
mIntent = null; | |
mUri = null; | |
} | |
@@ -193,6 +215,7 @@ | |
*/ | |
public Item(Intent intent) { | |
mText = null; | |
+ mHtmlText = null; | |
mIntent = intent; | |
mUri = null; | |
} | |
@@ -202,16 +225,35 @@ | |
*/ | |
public Item(Uri uri) { | |
mText = null; | |
+ mHtmlText = null; | |
mIntent = null; | |
mUri = uri; | |
} | |
/** | |
* Create a complex Item, containing multiple representations of | |
- * text, intent, and/or URI. | |
+ * text, Intent, and/or URI. | |
*/ | |
public Item(CharSequence text, Intent intent, Uri uri) { | |
mText = text; | |
+ mHtmlText = null; | |
+ mIntent = intent; | |
+ mUri = uri; | |
+ } | |
+ | |
+ /** | |
+ * Create a complex Item, containing multiple representations of | |
+ * text, HTML text, Intent, and/or URI. If providing HTML text, you | |
+ * <em>must</em> supply a plain text representation as well; coercion | |
+ * will not be done from HTML formated text into plain text. | |
+ */ | |
+ public Item(CharSequence text, String htmlText, Intent intent, Uri uri) { | |
+ if (htmlText != null && text == null) { | |
+ throw new IllegalArgumentException( | |
+ "Plain text must be supplied if HTML text is supplied"); | |
+ } | |
+ mText = text; | |
+ mHtmlText = htmlText; | |
mIntent = intent; | |
mUri = uri; | |
} | |
@@ -224,6 +266,13 @@ | |
} | |
/** | |
+ * Retrieve the raw HTML text contained in this Item. | |
+ */ | |
+ public String getHtmlText() { | |
+ return mHtmlText; | |
+ } | |
+ | |
+ /** | |
* Retrieve the raw Intent contained in this Item. | |
*/ | |
public Intent getIntent() { | |
@@ -250,7 +299,7 @@ | |
* the content provider does not supply a text representation, return | |
* the raw URI as a string. | |
* <li> If {@link #getIntent} is non-null, convert that to an intent: | |
- * URI and returnit. | |
+ * URI and return it. | |
* <li> Otherwise, return an empty string. | |
* </ul> | |
* | |
@@ -261,12 +310,14 @@ | |
//BEGIN_INCLUDE(coerceToText) | |
public CharSequence coerceToText(Context context) { | |
// If this Item has an explicit textual value, simply return that. | |
- if (mText != null) { | |
- return mText; | |
+ CharSequence text = getText(); | |
+ if (text != null) { | |
+ return text; | |
} | |
// If this Item has a URI value, try using that. | |
- if (mUri != null) { | |
+ Uri uri = getUri(); | |
+ if (uri != null) { | |
// First see if the URI can be opened as a plain text stream | |
// (of any sub-type). If so, this is the best textual | |
@@ -275,7 +326,7 @@ | |
try { | |
// Ask for a stream of the desired type. | |
AssetFileDescriptor descr = context.getContentResolver() | |
- .openTypedAssetFileDescriptor(mUri, "text/*", null); | |
+ .openTypedAssetFileDescriptor(uri, "text/*", null); | |
stream = descr.createInputStream(); | |
InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); | |
@@ -308,19 +359,254 @@ | |
// If we couldn't open the URI as a stream, then the URI itself | |
// probably serves fairly well as a textual representation. | |
- return mUri.toString(); | |
+ return uri.toString(); | |
} | |
// Finally, if all we have is an Intent, then we can just turn that | |
// into text. Not the most user-friendly thing, but it's something. | |
- if (mIntent != null) { | |
- return mIntent.toUri(Intent.URI_INTENT_SCHEME); | |
+ Intent intent = getIntent(); | |
+ if (intent != null) { | |
+ return intent.toUri(Intent.URI_INTENT_SCHEME); | |
} | |
// Shouldn't get here, but just in case... | |
return ""; | |
} | |
//END_INCLUDE(coerceToText) | |
+ | |
+ /** | |
+ * Like {@link #coerceToHtmlText(Context)}, but any text that would | |
+ * be returned as HTML formatting will be returned as text with | |
+ * style spans. | |
+ * @param context The caller's Context, from which its ContentResolver | |
+ * and other things can be retrieved. | |
+ * @return Returns the item's textual representation. | |
+ */ | |
+ public CharSequence coerceToStyledText(Context context) { | |
+ CharSequence text = getText(); | |
+ if (text instanceof Spanned) { | |
+ return text; | |
+ } | |
+ String htmlText = getHtmlText(); | |
+ if (htmlText != null) { | |
+ try { | |
+ CharSequence newText = Html.fromHtml(htmlText); | |
+ if (newText != null) { | |
+ return newText; | |
+ } | |
+ } catch (RuntimeException e) { | |
+ // If anything bad happens, we'll fall back on the plain text. | |
+ } | |
+ } | |
+ | |
+ if (text != null) { | |
+ return text; | |
+ } | |
+ return coerceToHtmlOrStyledText(context, true); | |
+ } | |
+ | |
+ /** | |
+ * Turn this item into HTML text, regardless of the type of data it | |
+ * actually contains. | |
+ * | |
+ * <p>The algorithm for deciding what text to return is: | |
+ * <ul> | |
+ * <li> If {@link #getHtmlText} is non-null, return that. | |
+ * <li> If {@link #getText} is non-null, return that, converting to | |
+ * valid HTML text. If this text contains style spans, | |
+ * {@link Html#toHtml(Spanned) Html.toHtml(Spanned)} is used to | |
+ * convert them to HTML formatting. | |
+ * <li> If {@link #getUri} is non-null, try to retrieve its data | |
+ * as a text stream from its content provider. If the provider can | |
+ * supply text/html data, that will be preferred and returned as-is. | |
+ * Otherwise, any text/* data will be returned and escaped to HTML. | |
+ * If it is not a content: URI or the content provider does not supply | |
+ * a text representation, HTML text containing a link to the URI | |
+ * will be returned. | |
+ * <li> If {@link #getIntent} is non-null, convert that to an intent: | |
+ * URI and return as an HTML link. | |
+ * <li> Otherwise, return an empty string. | |
+ * </ul> | |
+ * | |
+ * @param context The caller's Context, from which its ContentResolver | |
+ * and other things can be retrieved. | |
+ * @return Returns the item's representation as HTML text. | |
+ */ | |
+ public String coerceToHtmlText(Context context) { | |
+ // If the item has an explicit HTML value, simply return that. | |
+ String htmlText = getHtmlText(); | |
+ if (htmlText != null) { | |
+ return htmlText; | |
+ } | |
+ | |
+ // If this Item has a plain text value, return it as HTML. | |
+ CharSequence text = getText(); | |
+ if (text != null) { | |
+ if (text instanceof Spanned) { | |
+ return Html.toHtml((Spanned)text); | |
+ } | |
+ return Html.escapeHtml(text); | |
+ } | |
+ | |
+ text = coerceToHtmlOrStyledText(context, false); | |
+ return text != null ? text.toString() : null; | |
+ } | |
+ | |
+ private CharSequence coerceToHtmlOrStyledText(Context context, boolean styled) { | |
+ // If this Item has a URI value, try using that. | |
+ if (mUri != null) { | |
+ | |
+ // Check to see what data representations the content | |
+ // provider supports. We would like HTML text, but if that | |
+ // is not possible we'll live with plan text. | |
+ String[] types = context.getContentResolver().getStreamTypes(mUri, "text/*"); | |
+ boolean hasHtml = false; | |
+ boolean hasText = false; | |
+ if (types != null) { | |
+ for (String type : types) { | |
+ if ("text/html".equals(type)) { | |
+ hasHtml = true; | |
+ } else if (type.startsWith("text/")) { | |
+ hasText = true; | |
+ } | |
+ } | |
+ } | |
+ | |
+ // If the provider can serve data we can use, open and load it. | |
+ if (hasHtml || hasText) { | |
+ FileInputStream stream = null; | |
+ try { | |
+ // Ask for a stream of the desired type. | |
+ AssetFileDescriptor descr = context.getContentResolver() | |
+ .openTypedAssetFileDescriptor(mUri, | |
+ hasHtml ? "text/html" : "text/plain", null); | |
+ stream = descr.createInputStream(); | |
+ InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); | |
+ | |
+ // Got it... copy the stream into a local string and return it. | |
+ StringBuilder builder = new StringBuilder(128); | |
+ char[] buffer = new char[8192]; | |
+ int len; | |
+ while ((len=reader.read(buffer)) > 0) { | |
+ builder.append(buffer, 0, len); | |
+ } | |
+ String text = builder.toString(); | |
+ if (hasHtml) { | |
+ if (styled) { | |
+ // We loaded HTML formatted text and the caller | |
+ // want styled text, convert it. | |
+ try { | |
+ CharSequence newText = Html.fromHtml(text); | |
+ return newText != null ? newText : text; | |
+ } catch (RuntimeException e) { | |
+ return text; | |
+ } | |
+ } else { | |
+ // We loaded HTML formatted text and that is what | |
+ // the caller wants, just return it. | |
+ return text.toString(); | |
+ } | |
+ } | |
+ if (styled) { | |
+ // We loaded plain text and the caller wants styled | |
+ // text, that is all we have so return it. | |
+ return text; | |
+ } else { | |
+ // We loaded plain text and the caller wants HTML | |
+ // text, escape it for HTML. | |
+ return Html.escapeHtml(text); | |
+ } | |
+ | |
+ } catch (FileNotFoundException e) { | |
+ // Unable to open content URI as text... not really an | |
+ // error, just something to ignore. | |
+ | |
+ } catch (IOException e) { | |
+ // Something bad has happened. | |
+ Log.w("ClippedData", "Failure loading text", e); | |
+ return Html.escapeHtml(e.toString()); | |
+ | |
+ } finally { | |
+ if (stream != null) { | |
+ try { | |
+ stream.close(); | |
+ } catch (IOException e) { | |
+ } | |
+ } | |
+ } | |
+ } | |
+ | |
+ // If we couldn't open the URI as a stream, then we can build | |
+ // some HTML text with the URI itself. | |
+ // probably serves fairly well as a textual representation. | |
+ if (styled) { | |
+ return uriToStyledText(mUri.toString()); | |
+ } else { | |
+ return uriToHtml(mUri.toString()); | |
+ } | |
+ } | |
+ | |
+ // Finally, if all we have is an Intent, then we can just turn that | |
+ // into text. Not the most user-friendly thing, but it's something. | |
+ if (mIntent != null) { | |
+ if (styled) { | |
+ return uriToStyledText(mIntent.toUri(Intent.URI_INTENT_SCHEME)); | |
+ } else { | |
+ return uriToHtml(mIntent.toUri(Intent.URI_INTENT_SCHEME)); | |
+ } | |
+ } | |
+ | |
+ // Shouldn't get here, but just in case... | |
+ return ""; | |
+ } | |
+ | |
+ private String uriToHtml(String uri) { | |
+ StringBuilder builder = new StringBuilder(256); | |
+ builder.append("<a href=\""); | |
+ builder.append(uri); | |
+ builder.append("\">"); | |
+ builder.append(Html.escapeHtml(uri)); | |
+ builder.append("</a>"); | |
+ return builder.toString(); | |
+ } | |
+ | |
+ private CharSequence uriToStyledText(String uri) { | |
+ SpannableStringBuilder builder = new SpannableStringBuilder(); | |
+ builder.append(uri); | |
+ builder.setSpan(new URLSpan(uri), 0, builder.length(), | |
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); | |
+ return builder; | |
+ } | |
+ | |
+ @Override | |
+ public String toString() { | |
+ StringBuilder b = new StringBuilder(128); | |
+ | |
+ b.append("ClipData.Item { "); | |
+ toShortString(b); | |
+ b.append(" }"); | |
+ | |
+ return b.toString(); | |
+ } | |
+ | |
+ /** @hide */ | |
+ public void toShortString(StringBuilder b) { | |
+ if (mHtmlText != null) { | |
+ b.append("H:"); | |
+ b.append(mHtmlText); | |
+ } else if (mText != null) { | |
+ b.append("T:"); | |
+ b.append(mText); | |
+ } else if (mUri != null) { | |
+ b.append("U:"); | |
+ b.append(mUri); | |
+ } else if (mIntent != null) { | |
+ b.append("I:"); | |
+ mIntent.toShortString(b, true, true, true, true); | |
+ } else { | |
+ b.append("NULL"); | |
+ } | |
+ } | |
} | |
/** | |
@@ -336,6 +622,7 @@ | |
throw new NullPointerException("item is null"); | |
} | |
mIcon = null; | |
+ mItems = new ArrayList<Item>(); | |
mItems.add(item); | |
} | |
@@ -351,10 +638,23 @@ | |
throw new NullPointerException("item is null"); | |
} | |
mIcon = null; | |
+ mItems = new ArrayList<Item>(); | |
mItems.add(item); | |
} | |
/** | |
+ * Create a new clip that is a copy of another clip. This does a deep-copy | |
+ * of all items in the clip. | |
+ * | |
+ * @param other The existing ClipData that is to be copied. | |
+ */ | |
+ public ClipData(ClipData other) { | |
+ mClipDescription = other.mClipDescription; | |
+ mIcon = other.mIcon; | |
+ mItems = new ArrayList<Item>(other.mItems); | |
+ } | |
+ | |
+ /** | |
* Create a new ClipData holding data of the type | |
* {@link ClipDescription#MIMETYPE_TEXT_PLAIN}. | |
* | |
@@ -368,6 +668,22 @@ | |
} | |
/** | |
+ * Create a new ClipData holding data of the type | |
+ * {@link ClipDescription#MIMETYPE_TEXT_HTML}. | |
+ * | |
+ * @param label User-visible label for the clip data. | |
+ * @param text The text of clip as plain text, for receivers that don't | |
+ * handle HTML. This is required. | |
+ * @param htmlText The actual HTML text in the clip. | |
+ * @return Returns a new ClipData containing the specified data. | |
+ */ | |
+ static public ClipData newHtmlText(CharSequence label, CharSequence text, | |
+ String htmlText) { | |
+ Item item = new Item(text, htmlText); | |
+ return new ClipData(label, MIMETYPES_TEXT_HTML, item); | |
+ } | |
+ | |
+ /** | |
* Create a new ClipData holding an Intent with MIME type | |
* {@link ClipDescription#MIMETYPE_TEXT_INTENT}. | |
* | |
@@ -475,6 +791,46 @@ | |
} | |
@Override | |
+ public String toString() { | |
+ StringBuilder b = new StringBuilder(128); | |
+ | |
+ b.append("ClipData { "); | |
+ toShortString(b); | |
+ b.append(" }"); | |
+ | |
+ return b.toString(); | |
+ } | |
+ | |
+ /** @hide */ | |
+ public void toShortString(StringBuilder b) { | |
+ boolean first; | |
+ if (mClipDescription != null) { | |
+ first = !mClipDescription.toShortString(b); | |
+ } else { | |
+ first = true; | |
+ } | |
+ if (mIcon != null) { | |
+ if (!first) { | |
+ b.append(' '); | |
+ } | |
+ first = false; | |
+ b.append("I:"); | |
+ b.append(mIcon.getWidth()); | |
+ b.append('x'); | |
+ b.append(mIcon.getHeight()); | |
+ } | |
+ for (int i=0; i<mItems.size(); i++) { | |
+ if (!first) { | |
+ b.append(' '); | |
+ } | |
+ first = false; | |
+ b.append('{'); | |
+ mItems.get(i).toShortString(b); | |
+ b.append('}'); | |
+ } | |
+ } | |
+ | |
+ @Override | |
public int describeContents() { | |
return 0; | |
} | |
@@ -493,6 +849,7 @@ | |
for (int i=0; i<N; i++) { | |
Item item = mItems.get(i); | |
TextUtils.writeToParcel(item.mText, dest, flags); | |
+ dest.writeString(item.mHtmlText); | |
if (item.mIntent != null) { | |
dest.writeInt(1); | |
item.mIntent.writeToParcel(dest, flags); | |
@@ -515,12 +872,14 @@ | |
} else { | |
mIcon = null; | |
} | |
+ mItems = new ArrayList<Item>(); | |
final int N = in.readInt(); | |
for (int i=0; i<N; i++) { | |
CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); | |
+ String htmlText = in.readString(); | |
Intent intent = in.readInt() != 0 ? Intent.CREATOR.createFromParcel(in) : null; | |
Uri uri = in.readInt() != 0 ? Uri.CREATOR.createFromParcel(in) : null; | |
- mItems.add(new Item(text, intent, uri)); | |
+ mItems.add(new Item(text, htmlText, intent, uri)); | |
} | |
} | |
diff -Nur android-15/android/content/ClipDescription.java android-16/android/content/ClipDescription.java | |
--- android-15/android/content/ClipDescription.java 2012-06-18 20:00:44.000000000 +0900 | |
+++ android-16/android/content/ClipDescription.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -41,6 +41,11 @@ | |
public static final String MIMETYPE_TEXT_PLAIN = "text/plain"; | |
/** | |
+ * The MIME type for a clip holding HTML text. | |
+ */ | |
+ public static final String MIMETYPE_TEXT_HTML = "text/html"; | |
+ | |
+ /** | |
* The MIME type for a clip holding one or more URIs. This should be | |
* used for URIs that are meaningful to a user (such as an http: URI). | |
* It should <em>not</em> be used for a content: URI that references some | |
@@ -184,6 +189,39 @@ | |
} | |
@Override | |
+ public String toString() { | |
+ StringBuilder b = new StringBuilder(128); | |
+ | |
+ b.append("ClipDescription { "); | |
+ toShortString(b); | |
+ b.append(" }"); | |
+ | |
+ return b.toString(); | |
+ } | |
+ | |
+ /** @hide */ | |
+ public boolean toShortString(StringBuilder b) { | |
+ boolean first = true; | |
+ for (int i=0; i<mMimeTypes.length; i++) { | |
+ if (!first) { | |
+ b.append(' '); | |
+ } | |
+ first = false; | |
+ b.append(mMimeTypes[i]); | |
+ } | |
+ if (mLabel != null) { | |
+ if (!first) { | |
+ b.append(' '); | |
+ } | |
+ first = false; | |
+ b.append('"'); | |
+ b.append(mLabel); | |
+ b.append('"'); | |
+ } | |
+ return !first; | |
+ } | |
+ | |
+ @Override | |
public int describeContents() { | |
return 0; | |
} | |
diff -Nur android-15/android/content/ClipboardManager.java android-16/android/content/ClipboardManager.java | |
--- android-15/android/content/ClipboardManager.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/content/ClipboardManager.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -77,7 +77,20 @@ | |
} | |
}; | |
+ /** | |
+ * Defines a listener callback that is invoked when the primary clip on the clipboard changes. | |
+ * Objects that want to register a listener call | |
+ * {@link android.content.ClipboardManager#addPrimaryClipChangedListener(OnPrimaryClipChangedListener) | |
+ * addPrimaryClipChangedListener()} with an | |
+ * object that implements OnPrimaryClipChangedListener. | |
+ * | |
+ */ | |
public interface OnPrimaryClipChangedListener { | |
+ | |
+ /** | |
+ * Callback that is invoked by {@link android.content.ClipboardManager} when the primary | |
+ * clip changes. | |
+ */ | |
void onPrimaryClipChanged(); | |
} | |
diff -Nur android-15/android/content/ComponentCallbacks2.java android-16/android/content/ComponentCallbacks2.java | |
--- android-15/android/content/ComponentCallbacks2.java 2012-06-18 20:00:45.000000000 +0900 | |
+++ android-16/android/content/ComponentCallbacks2.java 2012-06-28 08:41:13.000000000 +0900 | |
@@ -52,15 +52,53 @@ | |
static final int TRIM_MEMORY_UI_HIDDEN = 20; | |
/** | |
+ * Level for {@link #onTrimMemory(int)}: the process is not an expendable | |
+ * background process, but the device is running extremely low on memory | |
+ * and is about to not be able to keep any background processes running. | |
+ * Your running process should free up as many non-critical resources as it | |
+ * can to allow that memory to be used elsewhere. The next thing that | |
+ * will happen after this is {@link #onLowMemory()} called to report that | |
+ * nothing at all can be kept in the background, a situation that can start | |
+ * to notably impact the user. | |
+ */ | |
+ static final int TRIM_MEMORY_RUNNING_CRITICAL = 15; | |
+ | |
+ /** | |
+ * Level for {@link #onTrimMemory(int)}: the process is not an expendable | |
+ * background process, but the device is running low on memory. | |
+ * Your running process should free up unneeded resources to allow that | |
+ * memory to be used elsewhere. | |
+ */ | |
+ static final int TRIM_MEMORY_RUNNING_LOW = 10; | |
+ | |
+ | |
+ /** | |
+ * Level for {@link #onTrimMemory(int)}: the process is not an expendable | |
+ * background process, but the device is running moderately low on memory. | |
+ * Your running process may want to release some unneeded resources for | |
+ * use elsewhere. | |
+ */ | |
+ static final int TRIM_MEMORY_RUNNING_MODERATE = 5; | |
+ | |
+ /** | |
* Called when the operating system has determined that it is a good | |
* time for a process to trim unneeded memory from its process. This will | |
* happen for example when it goes in the background and there is not enough | |
- * memory to keep as many background processes running as desired. | |
- * | |
+ * memory to keep as many background processes running as desired. You | |
+ * should never compare to exact values of the level, since new intermediate | |
+ * values may be added -- you will typically want to compare if the value | |
+ * is greater or equal to a level you are interested in. | |
+ * | |
+ * <p>To retrieve the processes current trim level at any point, you can | |
+ * use {@link android.app.ActivityManager#getMyMemoryState | |
+ * ActivityManager.getMyMemoryState(RunningAppProcessInfo)}. | |
+ * | |
* @param level The context of the trim, giving a hint of the amount of | |
* trimming the application may like to perform. May be | |
* {@link #TRIM_MEMORY_COMPLETE}, {@link #TRIM_MEMORY_MODERATE}, | |
- * {@link #TRIM_MEMORY_BACKGROUND}, or {@link #TRIM_MEMORY_UI_HIDDEN}. | |
+ * {@link #TRIM_MEMORY_BACKGROUND}, {@link #TRIM_MEMORY_UI_HIDDEN}, | |
+ * {@link #TRIM_MEMORY_RUNNING_CRITICAL}, {@link #TRIM_MEMORY_RUNNING_LOW}, | |
+ * or {@link #TRIM_MEMORY_RUNNING_MODERATE}. | |
*/ | |
void onTrimMemory(int level); | |
} | |
diff -Nur android-15/android/content/ContentProvider.java android-16/android/content/ContentProvider.java | |
--- android-15/android/content/ContentProvider.java 2012-06-18 20:00:44.000000000 +0900 | |
+++ android-16/android/content/ContentProvider.java 2012-06-28 08:41:12.000000000 +0900 | |
@@ -16,6 +16,8 @@ | |
package android.content; | |
+import static android.content.pm.PackageManager.PERMISSION_GRANTED; | |
+ | |
import android.content.pm.PackageManager; | |
import android.content.pm.PathPermission; | |
import android.content.pm.ProviderInfo; | |
@@ -27,13 +29,20 @@ | |
import android.os.AsyncTask; | |
import android.os.Binder; | |
import android.os.Bundle; | |
+import android.os.CancellationSignal; | |
+import android.os.ICancellationSignal; | |
+import android.os.OperationCanceledException; | |
import android.os.ParcelFileDescriptor; | |
import android.os.Process; | |
+import android.os.RemoteException; | |
+import android.os.UserId; | |
import android.util.Log; | |
import java.io.File; | |
+import java.io.FileDescriptor; | |
import java.io.FileNotFoundException; | |
import java.io.IOException; | |
+import java.io.PrintWriter; | |
import java.util.ArrayList; | |
/** | |
@@ -172,28 +181,33 @@ | |
return getContentProvider().getClass().getName(); | |
} | |
+ @Override | |
public Cursor query(Uri uri, String[] projection, | |
- String selection, String[] selectionArgs, String sortOrder) { | |
+ String selection, String[] selectionArgs, String sortOrder, | |
+ ICancellationSignal cancellationSignal) { | |
enforceReadPermission(uri); | |
- return ContentProvider.this.query(uri, projection, selection, | |
- selectionArgs, sortOrder); | |
+ return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder, | |
+ CancellationSignal.fromTransport(cancellationSignal)); | |
} | |
+ @Override | |
public String getType(Uri uri) { | |
return ContentProvider.this.getType(uri); | |
} | |
- | |
+ @Override | |
public Uri insert(Uri uri, ContentValues initialValues) { | |
enforceWritePermission(uri); | |
return ContentProvider.this.insert(uri, initialValues); | |
} | |
+ @Override | |
public int bulkInsert(Uri uri, ContentValues[] initialValues) { | |
enforceWritePermission(uri); | |
return ContentProvider.this.bulkInsert(uri, initialValues); | |
} | |
+ @Override | |
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) | |
throws OperationApplicationException { | |
for (ContentProviderOperation operation : operations) { | |
@@ -208,17 +222,20 @@ | |
return ContentProvider.this.applyBatch(operations); | |
} | |
+ @Override | |
public int delete(Uri uri, String selection, String[] selectionArgs) { | |
enforceWritePermission(uri); | |
return ContentProvider.this.delete(uri, selection, selectionArgs); | |
} | |
+ @Override | |
public int update(Uri uri, ContentValues values, String selection, | |
String[] selectionArgs) { | |
enforceWritePermission(uri); | |
return ContentProvider.this.update(uri, values, selection, selectionArgs); | |
} | |
+ @Override | |
public ParcelFileDescriptor openFile(Uri uri, String mode) | |
throws FileNotFoundException { | |
if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri); | |
@@ -226,6 +243,7 @@ | |
return ContentProvider.this.openFile(uri, mode); | |
} | |
+ @Override | |
public AssetFileDescriptor openAssetFile(Uri uri, String mode) | |
throws FileNotFoundException { | |
if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri); | |
@@ -233,6 +251,7 @@ | |
return ContentProvider.this.openAssetFile(uri, mode); | |
} | |
+ @Override | |
public Bundle call(String method, String arg, Bundle extras) { | |
return ContentProvider.this.call(method, arg, extras); | |
} | |
@@ -249,108 +268,134 @@ | |
return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts); | |
} | |
- private void enforceReadPermission(Uri uri) { | |
- final int uid = Binder.getCallingUid(); | |
- if (uid == mMyUid) { | |
- return; | |
- } | |
- | |
+ @Override | |
+ public ICancellationSignal createCancellationSignal() throws RemoteException { | |
+ return CancellationSignal.createTransport(); | |
+ } | |
+ | |
+ private void enforceReadPermission(Uri uri) throws SecurityException { | |
final Context context = getContext(); | |
- final String rperm = getReadPermission(); | |
final int pid = Binder.getCallingPid(); | |
- if (mExported && (rperm == null | |
- || context.checkPermission(rperm, pid, uid) | |
- == PackageManager.PERMISSION_GRANTED)) { | |
+ final int uid = Binder.getCallingUid(); | |
+ String missingPerm = null; | |
+ | |
+ if (uid == mMyUid) { | |
return; | |
} | |
- | |
- PathPermission[] pps = getPathPermissions(); | |
- if (pps != null) { | |
- final String path = uri.getPath(); | |
- int i = pps.length; | |
- while (i > 0) { | |
- i--; | |
- final PathPermission pp = pps[i]; | |
- final String pprperm = pp.getReadPermission(); | |
- if (pprperm != null && pp.match(path)) { | |
- if (context.checkPermission(pprperm, pid, uid) | |
- == PackageManager.PERMISSION_GRANTED) { | |
- return; | |
+ | |
+ if (mExported) { | |
+ final String componentPerm = getReadPermission(); | |
+ if (componentPerm != null) { | |
+ if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { | |
+ return; | |
+ } else { | |
+ missingPerm = componentPerm; | |
+ } | |
+ } | |
+ | |
+ // track if unprotected read is allowed; any denied | |
+ // <path-permission> below removes this ability | |
+ boolean allowDefaultRead = (componentPerm == null); | |
+ | |
+ final PathPermission[] pps = getPathPermissions(); | |
+ if (pps != null) { | |
+ final String path = uri.getPath(); | |
+ for (PathPermission pp : pps) { | |
+ final String pathPerm = pp.getReadPermission(); | |
+ if (pathPerm != null && pp.match(path)) { | |
+ if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { | |
+ return; | |
+ } else { | |
+ // any denied <path-permission> means we lose | |
+ // default <provider> access. | |
+ allowDefaultRead = false; | |
+ missingPerm = pathPerm; | |
+ } | |
} | |
} | |
} | |
+ | |
+ // if we passed <path-permission> checks above, and no default | |
+ // <provider> permission, then allow access. | |
+ if (allowDefaultRead) return; | |
} | |
- | |
- if (context.checkUriPermission(uri, pid, uid, | |
- Intent.FLAG_GRANT_READ_URI_PERMISSION) | |
- == PackageManager.PERMISSION_GRANTED) { | |
+ | |
+ // last chance, check against any uri grants | |
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) | |
+ == PERMISSION_GRANTED) { | |
return; | |
} | |
- | |
- String msg = "Permission Denial: reading " | |
- + ContentProvider.this.getClass().getName() | |
- + " uri " + uri + " from pid=" + Binder.getCallingPid() | |
- + ", uid=" + Binder.getCallingUid() | |
- + " requires " + rperm; | |
- throw new SecurityException(msg); | |
+ | |
+ final String failReason = mExported | |
+ ? " requires " + missingPerm + ", or grantUriPermission()" | |
+ : " requires the provider be exported, or grantUriPermission()"; | |
+ throw new SecurityException("Permission Denial: reading " | |
+ + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid | |
+ + ", uid=" + uid + failReason); | |
} | |
- private boolean hasWritePermission(Uri uri) { | |
- final int uid = Binder.getCallingUid(); | |
- if (uid == mMyUid) { | |
- return true; | |
- } | |
- | |
+ private void enforceWritePermission(Uri uri) throws SecurityException { | |
final Context context = getContext(); | |
- final String wperm = getWritePermission(); | |
final int pid = Binder.getCallingPid(); | |
- if (mExported && (wperm == null | |
- || context.checkPermission(wperm, pid, uid) | |
- == PackageManager.PERMISSION_GRANTED)) { | |
- return true; | |
+ final int uid = Binder.getCallingUid(); | |
+ String missingPerm = null; | |
+ | |
+ if (uid == mMyUid) { | |
+ return; | |
} | |
- | |
- PathPermission[] pps = getPathPermissions(); | |
- if (pps != null) { | |
- final String path = uri.getPath(); | |
- int i = pps.length; | |
- while (i > 0) { | |
- i--; | |
- final PathPermission pp = pps[i]; | |
- final String ppwperm = pp.getWritePermission(); | |
- if (ppwperm != null && pp.match(path)) { | |
- if (context.checkPermission(ppwperm, pid, uid) | |
- == PackageManager.PERMISSION_GRANTED) { | |
- return true; | |
+ | |
+ if (mExported) { | |
+ final String componentPerm = getWritePermission(); | |
+ if (componentPerm != null) { | |
+ if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { | |
+ return; | |
+ } else { | |
+ missingPerm = componentPerm; | |
+ } | |
+ } | |
+ | |
+ // track if unprotected write is allowed; any denied | |
+ // <path-permission> below removes this ability | |
+ boolean allowDefaultWrite = (componentPerm == null); | |
+ | |
+ final PathPermission[] pps = getPathPermissions(); | |
+ if (pps != null) { | |
+ final String path = uri.getPath(); | |
+ for (PathPermission pp : pps) { | |
+ final String pathPerm = pp.getWritePermission(); | |
+ if (pathPerm != null && pp.match(path)) { | |
+ if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { | |
+ return; | |
+ } else { | |
+ // any denied <path-permission> means we lose | |
+ // default <provider> access. | |
+ allowDefaultWrite = false; | |
+ missingPerm = pathPerm; | |
+ } | |
} | |
} | |
} | |
+ | |
+ // if we passed <path-permission> checks above, and no default | |
+ // <provider> permission, then allow access. | |
+ if (allowDefaultWrite) return; | |
} | |
- | |
- if (context.checkUriPermission(uri, pid, uid, | |
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION) | |
- == PackageManager.PERMISSION_GRANTED) { | |
- return true; | |
- } | |
- | |
- return false; | |
- } | |
- | |
- private void enforceWritePermission(Uri uri) { | |
- if (hasWritePermission(uri)) { | |
+ | |
+ // last chance, check against any uri grants | |
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) | |
+ == PERMISSION_GRANTED) { | |
return; | |
} | |
- | |
- String msg = "Permission Denial: writing " | |
- + ContentProvider.this.getClass().getName() | |
- + " uri " + uri + " from pid=" + Binder.getCallingPid() | |
- + ", uid=" + Binder.getCallingUid() | |
- + " requires " + getWritePermission(); | |
- throw new SecurityException(msg); | |
+ | |
+ final String failReason = mExported | |
+ ? " requires " + missingPerm + ", or grantUriPermission()" | |
+ : " requires the provider be exported, or grantUriPermission()"; | |
+ throw new SecurityException("Permission Denial: writing " | |
+ + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid | |
+ + ", uid=" + uid + failReason); | |
} | |
} | |
- | |
/** | |
* Retrieves the Context this provider is running in. Only available once | |
* {@link #onCreate} has been called -- this will return null in the | |
@@ -539,6 +584,75 @@ | |
String selection, String[] selectionArgs, String sortOrder); | |
/** | |
+ * Implement this to handle query requests from clients with support for cancellation. | |
+ * This method can be called from multiple threads, as described in | |
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes | |
+ * and Threads</a>. | |
+ * <p> | |
+ * Example client call:<p> | |
+ * <pre>// Request a specific record. | |
+ * Cursor managedCursor = managedQuery( | |
+ ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2), | |
+ projection, // Which columns to return. | |
+ null, // WHERE clause. | |
+ null, // WHERE clause value substitution | |
+ People.NAME + " ASC"); // Sort order.</pre> | |
+ * Example implementation:<p> | |
+ * <pre>// SQLiteQueryBuilder is a helper class that creates the | |
+ // proper SQL syntax for us. | |
+ SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder(); | |
+ | |
+ // Set the table we're querying. | |
+ qBuilder.setTables(DATABASE_TABLE_NAME); | |
+ | |
+ // If the query ends in a specific record number, we're | |
+ // being asked for a specific record, so set the | |
+ // WHERE clause in our query. | |
+ if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){ | |
+ qBuilder.appendWhere("_id=" + uri.getPathLeafId()); | |
+ } | |
+ | |
+ // Make the query. | |
+ Cursor c = qBuilder.query(mDb, | |
+ projection, | |
+ selection, | |
+ selectionArgs, | |
+ groupBy, | |
+ having, | |
+ sortOrder); | |
+ c.setNotificationUri(getContext().getContentResolver(), uri); | |
+ return c;</pre> | |
+ * <p> | |
+ * If you implement this method then you must also implement the version of | |
+ * {@link #query(Uri, String[], String, String[], String)} that does not take a cancellation | |
+ * signal to ensure correct operation on older versions of the Android Framework in | |
+ * which the cancellation signal overload was not available. | |
+ * | |
+ * @param uri The URI to query. This will be the full URI sent by the client; | |
+ * if the client is requesting a specific record, the URI will end in a record number | |
+ * that the implementation should parse and add to a WHERE or HAVING clause, specifying | |
+ * that _id value. | |
+ * @param projection The list of columns to put into the cursor. If | |
+ * null all columns are included. | |
+ * @param selection A selection criteria to apply when filtering rows. | |
+ * If null then all rows are included. | |
+ * @param selectionArgs You may include ?s in selection, which will be replaced by | |
+ * the values from selectionArgs, in order that they appear in the selection. | |
+ * The values will be bound as Strings. | |
+ * @param sortOrder How the rows in the cursor should be sorted. | |
+ * If null then the provider is free to define the sort order. | |
+ * @param cancellationSignal A signal to cancel the operation in progress, or null if none. | |
+ * If the operation is canceled, then {@link OperationCanceledException} will be thrown | |
+ * when the query is executed. | |
+ * @return a Cursor or null. | |
+ */ | |
+ public Cursor query(Uri uri, String[] projection, | |
+ String selection, String[] selectionArgs, String sortOrder, | |
+ CancellationSignal cancellationSignal) { | |
+ return query(uri, projection, selection, selectionArgs, sortOrder); | |
+ } | |
+ | |
+ /** | |
* Implement this to handle requests for the MIME type of the data at the | |
* given URI. The returned MIME type should start with | |
* <code>vnd.android.cursor.item</code> for a single record, | |
@@ -1013,4 +1127,19 @@ | |
Log.w(TAG, "implement ContentProvider shutdown() to make sure all database " + | |
"connections are gracefully shutdown"); | |
} | |
+ | |
+ /** | |
+ * Print the Provider's state into the given stream. This gets invoked if | |
+ * you run "adb shell dumpsys activity provider <provider_component_name>". | |
+ * | |
+ * @param prefix Desired prefix to prepend at each line of output. | |
+ * @param fd The raw file descriptor that the dump is being sent to. | |
+ * @param writer The PrintWriter to which you should dump your state. This will be | |
+ * closed for you after you return. | |
+ * @param args additional arguments to the dump request. | |
+ * @hide | |
+ */ | |
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { | |
+ writer.println("nothing to dump"); | |
+ } | |
} | |
diff -Nur android-15/android/content/ContentProviderClient.java android-16/android/content/ContentProviderClient.java | |
--- android-15/android/content/ContentProviderClient.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/content/ContentProviderClient.java 2012-06-28 08:41:07.000000000 +0900 | |
@@ -19,6 +19,9 @@ | |
import android.database.Cursor; | |
import android.net.Uri; | |
import android.os.Bundle; | |
+import android.os.CancellationSignal; | |
+import android.os.DeadObjectException; | |
+import android.os.ICancellationSignal; | |
import android.os.RemoteException; | |
import android.os.ParcelFileDescriptor; | |
import android.content.res.AssetFileDescriptor; | |
@@ -31,56 +34,136 @@ | |
* calling {@link ContentResolver#acquireContentProviderClient}. This object must be released | |
* using {@link #release} in order to indicate to the system that the {@link ContentProvider} is | |
* no longer needed and can be killed to free up resources. | |
+ * | |
+ * <p>Note that you should generally create a new ContentProviderClient instance | |
+ * for each thread that will be performing operations. Unlike | |
+ * {@link ContentResolver}, the methods here such as {@link #query} and | |
+ * {@link #openFile} are not thread safe -- you must not call | |
+ * {@link #release()} on the ContentProviderClient those calls are made from | |
+ * until you are finished with the data they have returned. | |
*/ | |
public class ContentProviderClient { | |
private final IContentProvider mContentProvider; | |
private final ContentResolver mContentResolver; | |
+ private final boolean mStable; | |
+ private boolean mReleased; | |
/** | |
* @hide | |
*/ | |
- ContentProviderClient(ContentResolver contentResolver, IContentProvider contentProvider) { | |
+ ContentProviderClient(ContentResolver contentResolver, | |
+ IContentProvider contentProvider, boolean stable) { | |
mContentProvider = contentProvider; | |
mContentResolver = contentResolver; | |
+ mStable = stable; | |
} | |
/** See {@link ContentProvider#query ContentProvider.query} */ | |
public Cursor query(Uri url, String[] projection, String selection, | |
String[] selectionArgs, String sortOrder) throws RemoteException { | |
- return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder); | |
+ try { | |
+ return query(url, projection, selection, selectionArgs, sortOrder, null); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
+ } | |
+ | |
+ /** See {@link ContentProvider#query ContentProvider.query} */ | |
+ public Cursor query(Uri url, String[] projection, String selection, | |
+ String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) | |
+ throws RemoteException { | |
+ ICancellationSignal remoteCancellationSignal = null; | |
+ if (cancellationSignal != null) { | |
+ remoteCancellationSignal = mContentProvider.createCancellationSignal(); | |
+ cancellationSignal.setRemote(remoteCancellationSignal); | |
+ } | |
+ try { | |
+ return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder, | |
+ remoteCancellationSignal); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** See {@link ContentProvider#getType ContentProvider.getType} */ | |
public String getType(Uri url) throws RemoteException { | |
- return mContentProvider.getType(url); | |
+ try { | |
+ return mContentProvider.getType(url); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */ | |
public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException { | |
- return mContentProvider.getStreamTypes(url, mimeTypeFilter); | |
+ try { | |
+ return mContentProvider.getStreamTypes(url, mimeTypeFilter); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** See {@link ContentProvider#insert ContentProvider.insert} */ | |
public Uri insert(Uri url, ContentValues initialValues) | |
throws RemoteException { | |
- return mContentProvider.insert(url, initialValues); | |
+ try { | |
+ return mContentProvider.insert(url, initialValues); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */ | |
public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException { | |
- return mContentProvider.bulkInsert(url, initialValues); | |
+ try { | |
+ return mContentProvider.bulkInsert(url, initialValues); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** See {@link ContentProvider#delete ContentProvider.delete} */ | |
public int delete(Uri url, String selection, String[] selectionArgs) | |
throws RemoteException { | |
- return mContentProvider.delete(url, selection, selectionArgs); | |
+ try { | |
+ return mContentProvider.delete(url, selection, selectionArgs); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** See {@link ContentProvider#update ContentProvider.update} */ | |
public int update(Uri url, ContentValues values, String selection, | |
String[] selectionArgs) throws RemoteException { | |
- return mContentProvider.update(url, values, selection, selectionArgs); | |
+ try { | |
+ return mContentProvider.update(url, values, selection, selectionArgs); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** | |
@@ -92,7 +175,14 @@ | |
*/ | |
public ParcelFileDescriptor openFile(Uri url, String mode) | |
throws RemoteException, FileNotFoundException { | |
- return mContentProvider.openFile(url, mode); | |
+ try { | |
+ return mContentProvider.openFile(url, mode); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** | |
@@ -104,20 +194,41 @@ | |
*/ | |
public AssetFileDescriptor openAssetFile(Uri url, String mode) | |
throws RemoteException, FileNotFoundException { | |
- return mContentProvider.openAssetFile(url, mode); | |
+ try { | |
+ return mContentProvider.openAssetFile(url, mode); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */ | |
public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, | |
String mimeType, Bundle opts) | |
throws RemoteException, FileNotFoundException { | |
- return mContentProvider.openTypedAssetFile(uri, mimeType, opts); | |
+ try { | |
+ return mContentProvider.openTypedAssetFile(uri, mimeType, opts); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */ | |
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) | |
throws RemoteException, OperationApplicationException { | |
- return mContentProvider.applyBatch(operations); | |
+ try { | |
+ return mContentProvider.applyBatch(operations); | |
+ } catch (DeadObjectException e) { | |
+ if (!mStable) { | |
+ mContentResolver.unstableProviderDied(mContentProvider); | |
+ } | |
+ throw e; | |
+ } | |
} | |
/** | |
@@ -126,7 +237,17 @@ | |
* @return true if this was release, false if it was already released | |
*/ | |
public boolean release() { | |
- return mContentResolver.releaseProvider(mContentProvider); | |
+ synchronized (this) { | |
+ if (mReleased) { | |
+ throw new IllegalStateException("Already released"); | |
+ } | |
+ mReleased = true; | |
+ if (mStable) { | |
+ return mContentResolver.releaseProvider(mContentProvider); | |
+ } else { | |
+ return mContentResolver.releaseUnstableProvider(mContentProvider); | |
+ } | |
+ } | |
} | |
/** | |
diff -Nur android-15/android/content/ContentProviderNative.java android-16/android/content/ContentProviderNative.java | |
--- android-15/android/content/ContentProviderNative.java 2012-06-18 20:00:41.000000000 +0900 | |
+++ android-16/android/content/ContentProviderNative.java 2012-06-28 08:41:08.000000000 +0900 | |
@@ -17,11 +17,11 @@ | |
package android.content; | |
import android.content.res.AssetFileDescriptor; | |
+import android.database.BulkCursorDescriptor; | |
import android.database.BulkCursorNative; | |
import android.database.BulkCursorToCursorAdaptor; | |
import android.database.Cursor; | |
import android.database.CursorToBulkCursorAdaptor; | |
-import android.database.CursorWindow; | |
import android.database.DatabaseUtils; | |
import android.database.IBulkCursor; | |
import android.database.IContentObserver; | |
@@ -30,6 +30,7 @@ | |
import android.os.Bundle; | |
import android.os.RemoteException; | |
import android.os.IBinder; | |
+import android.os.ICancellationSignal; | |
import android.os.Parcel; | |
import android.os.ParcelFileDescriptor; | |
import android.os.Parcelable; | |
@@ -41,8 +42,6 @@ | |
* {@hide} | |
*/ | |
abstract public class ContentProviderNative extends Binder implements IContentProvider { | |
- private static final String TAG = "ContentProvider"; | |
- | |
public ContentProviderNative() | |
{ | |
attachInterface(this, descriptor); | |
@@ -108,25 +107,22 @@ | |
String sortOrder = data.readString(); | |
IContentObserver observer = IContentObserver.Stub.asInterface( | |
data.readStrongBinder()); | |
+ ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface( | |
+ data.readStrongBinder()); | |
- Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder); | |
+ Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder, | |
+ cancellationSignal); | |
if (cursor != null) { | |
CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor( | |
cursor, observer, getProviderName()); | |
- final IBinder binder = adaptor.asBinder(); | |
- final int count = adaptor.count(); | |
- final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex( | |
- adaptor.getColumnNames()); | |
- final boolean wantsAllOnMoveCalls = adaptor.getWantsAllOnMoveCalls(); | |
+ BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor(); | |
reply.writeNoException(); | |
- reply.writeStrongBinder(binder); | |
- reply.writeInt(count); | |
- reply.writeInt(index); | |
- reply.writeInt(wantsAllOnMoveCalls ? 1 : 0); | |
+ reply.writeInt(1); | |
+ d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |
} else { | |
reply.writeNoException(); | |
- reply.writeStrongBinder(null); | |
+ reply.writeInt(0); | |
} | |
return true; | |
@@ -295,6 +291,16 @@ | |
} | |
return true; | |
} | |
+ | |
+ case CREATE_CANCELATION_SIGNAL_TRANSACTION: | |
+ { | |
+ data.enforceInterface(IContentProvider.descriptor); | |
+ | |
+ ICancellationSignal cancellationSignal = createCancellationSignal(); | |
+ reply.writeNoException(); | |
+ reply.writeStrongBinder(cancellationSignal.asBinder()); | |
+ return true; | |
+ } | |
} | |
} catch (Exception e) { | |
DatabaseUtils.writeExceptionToParcel(reply, e); | |
@@ -324,7 +330,8 @@ | |
} | |
public Cursor query(Uri url, String[] projection, String selection, | |
- String[] selectionArgs, String sortOrder) throws RemoteException { | |
+ String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal) | |
+ throws RemoteException { | |
BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor(); | |
Parcel data = Parcel.obtain(); | |
Parcel reply = Parcel.obtain(); | |
@@ -352,17 +359,15 @@ | |
} | |
data.writeString(sortOrder); | |
data.writeStrongBinder(adaptor.getObserver().asBinder()); | |
+ data.writeStrongBinder(cancellationSignal != null ? cancellationSignal.asBinder() : null); | |
mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); | |
DatabaseUtils.readExceptionFromParcel(reply); | |
- IBulkCursor bulkCursor = BulkCursorNative.asInterface(reply.readStrongBinder()); | |
- if (bulkCursor != null) { | |
- int rowCount = reply.readInt(); | |
- int idColumnPosition = reply.readInt(); | |
- boolean wantsAllOnMoveCalls = reply.readInt() != 0; | |
- adaptor.initialize(bulkCursor, rowCount, idColumnPosition, wantsAllOnMoveCalls); | |
+ if (reply.readInt() != 0) { | |
+ BulkCursorDescriptor d = BulkCursorDescriptor.CREATOR.createFromParcel(reply); | |
+ adaptor.initialize(d); | |
} else { | |
adaptor.close(); | |
adaptor = null; | |
@@ -620,5 +625,24 @@ | |
} | |
} | |
+ public ICancellationSignal createCancellationSignal() throws RemoteException { | |
+ Parcel data = Parcel.obtain(); | |
+ Parcel reply = Parcel.obtain(); | |
+ try { | |
+ data.writeInterfaceToken(IContentProvider.descriptor); | |
+ | |
+ mRemote.transact(IContentProvider.CREATE_CANCELATION_SIGNAL_TRANSACTION, | |
+ data, reply, 0); | |
+ | |
+ DatabaseUtils.readExceptionFromParcel(reply); | |
+ ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface( | |
+ reply.readStrongBinder()); | |
+ return cancellationSignal; | |
+ } finally { | |
+ data.recycle(); | |
+ reply.recycle(); | |
+ } | |
+ } | |
+ | |
private IBinder mRemote; | |
} | |
diff -Nur android-15/android/content/ContentResolver.java android-16/android/content/ContentResolver.java | |
--- android-15/android/content/ContentResolver.java 2012-06-18 20:00:43.000000000 +0900 | |
+++ android-16/android/content/ContentResolver.java 2012-06-28 08:41:11.000000000 +0900 | |
@@ -20,7 +20,6 @@ | |
import android.accounts.Account; | |
import android.app.ActivityManagerNative; | |
-import android.app.ActivityThread; | |
import android.app.AppGlobals; | |
import android.content.pm.PackageManager.NameNotFoundException; | |
import android.content.res.AssetFileDescriptor; | |
@@ -28,15 +27,17 @@ | |
import android.database.ContentObserver; | |
import android.database.CrossProcessCursorWrapper; | |
import android.database.Cursor; | |
-import android.database.CursorWrapper; | |
import android.database.IContentObserver; | |
import android.net.Uri; | |
import android.os.Bundle; | |
+import android.os.CancellationSignal; | |
+import android.os.DeadObjectException; | |
import android.os.IBinder; | |
+import android.os.ICancellationSignal; | |
+import android.os.OperationCanceledException; | |
import android.os.ParcelFileDescriptor; | |
import android.os.RemoteException; | |
import android.os.ServiceManager; | |
-import android.os.StrictMode; | |
import android.os.SystemClock; | |
import android.text.TextUtils; | |
import android.util.EventLog; | |
@@ -194,6 +195,12 @@ | |
} | |
/** @hide */ | |
public abstract boolean releaseProvider(IContentProvider icp); | |
+ /** @hide */ | |
+ protected abstract IContentProvider acquireUnstableProvider(Context c, String name); | |
+ /** @hide */ | |
+ public abstract boolean releaseUnstableProvider(IContentProvider icp); | |
+ /** @hide */ | |
+ public abstract void unstableProviderDied(IContentProvider icp); | |
/** | |
* Return the MIME type of the given content URL. | |
@@ -203,6 +210,7 @@ | |
* @return A MIME type for the content, or null if the URL is invalid or the type is unknown | |
*/ | |
public final String getType(Uri url) { | |
+ // XXX would like to have an acquireExistingUnstableProvider for this. | |
IContentProvider provider = acquireExistingProvider(url); | |
if (provider != null) { | |
try { | |
@@ -247,7 +255,7 @@ | |
* @param mimeTypeFilter The desired MIME type. This may be a pattern, | |
* such as *\/*, to query for all available MIME types that match the | |
* pattern. | |
- * @return Returns an array of MIME type strings for all availablle | |
+ * @return Returns an array of MIME type strings for all available | |
* data streams that match the given mimeTypeFilter. If there are none, | |
* null is returned. | |
*/ | |
@@ -302,15 +310,78 @@ | |
*/ | |
public final Cursor query(Uri uri, String[] projection, | |
String selection, String[] selectionArgs, String sortOrder) { | |
- IContentProvider provider = acquireProvider(uri); | |
- if (provider == null) { | |
+ return query(uri, projection, selection, selectionArgs, sortOrder, null); | |
+ } | |
+ | |
+ /** | |
+ * <p> | |
+ * Query the given URI, returning a {@link Cursor} over the result set. | |
+ * </p> | |
+ * <p> | |
+ * For best performance, the caller should follow these guidelines: | |
+ * <ul> | |
+ * <li>Provide an explicit projection, to prevent | |
+ * reading data from storage that aren't going to be used.</li> | |
+ * <li>Use question mark parameter markers such as 'phone=?' instead of | |
+ * explicit values in the {@code selection} parameter, so that queries | |
+ * that differ only by those values will be recognized as the same | |
+ * for caching purposes.</li> | |
+ * </ul> | |
+ * </p> | |
+ * | |
+ * @param uri The URI, using the content:// scheme, for the content to | |
+ * retrieve. | |
+ * @param projection A list of which columns to return. Passing null will | |
+ * return all columns, which is inefficient. | |
+ * @param selection A filter declaring which rows to return, formatted as an | |
+ * SQL WHERE clause (excluding the WHERE itself). Passing null will | |
+ * return all rows for the given URI. | |
+ * @param selectionArgs You may include ?s in selection, which will be | |
+ * replaced by the values from selectionArgs, in the order that they | |
+ * appear in the selection. The values will be bound as Strings. | |
+ * @param sortOrder How to order the rows, formatted as an SQL ORDER BY | |
+ * clause (excluding the ORDER BY itself). Passing null will use the | |
+ * default sort order, which may be unordered. | |
+ * @param cancellationSignal A signal to cancel the operation in progress, or null if none. | |
+ * If the operation is canceled, then {@link OperationCanceledException} will be thrown | |
+ * when the query is executed. | |
+ * @return A Cursor object, which is positioned before the first entry, or null | |
+ * @see Cursor | |
+ */ | |
+ public final Cursor query(final Uri uri, String[] projection, | |
+ String selection, String[] selectionArgs, String sortOrder, | |
+ CancellationSignal cancellationSignal) { | |
+ IContentProvider unstableProvider = acquireUnstableProvider(uri); | |
+ if (unstableProvider == null) { | |
return null; | |
} | |
+ IContentProvider stableProvider = null; | |
try { | |
long startTime = SystemClock.uptimeMillis(); | |
- Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder); | |
+ | |
+ ICancellationSignal remoteCancellationSignal = null; | |
+ if (cancellationSignal != null) { | |
+ cancellationSignal.throwIfCanceled(); | |
+ remoteCancellationSignal = unstableProvider.createCancellationSignal(); | |
+ cancellationSignal.setRemote(remoteCancellationSignal); | |
+ } | |
+ Cursor qCursor; | |
+ try { | |
+ qCursor = unstableProvider.query(uri, projection, | |
+ selection, selectionArgs, sortOrder, remoteCancellationSignal); | |
+ } catch (DeadObjectException e) { | |
+ // The remote process has died... but we only hold an unstable | |
+ // reference though, so we might recover!!! Let's try!!!! | |
+ // This is exciting!!1!!1!!!!1 | |
+ unstableProviderDied(unstableProvider); | |
+ stableProvider = acquireProvider(uri); | |
+ if (stableProvider == null) { | |
+ return null; | |
+ } | |
+ qCursor = stableProvider.query(uri, projection, | |
+ selection, selectionArgs, sortOrder, remoteCancellationSignal); | |
+ } | |
if (qCursor == null) { | |
- releaseProvider(provider); | |
return null; | |
} | |
// force query execution | |
@@ -318,16 +389,21 @@ | |
long durationMillis = SystemClock.uptimeMillis() - startTime; | |
maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder); | |
// Wrap the cursor object into CursorWrapperInner object | |
- return new CursorWrapperInner(qCursor, provider); | |
+ CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, | |
+ stableProvider != null ? stableProvider : acquireProvider(uri)); | |
+ stableProvider = null; | |
+ return wrapper; | |
} catch (RemoteException e) { | |
- releaseProvider(provider); | |
- | |
// Arbitrary and not worth documenting, as Activity | |
// Manager will kill this process shortly anyway. | |
return null; | |
- } catch (RuntimeException e) { | |
- releaseProvider(provider); | |
- throw e; | |
+ } finally { | |
+ if (unstableProvider != null) { | |
+ releaseUnstableProvider(unstableProvider); | |
+ } | |
+ if (stableProvider != null) { | |
+ releaseProvider(stableProvider); | |
+ } | |
} | |
} | |
@@ -535,34 +611,62 @@ | |
if ("r".equals(mode)) { | |
return openTypedAssetFileDescriptor(uri, "*/*", null); | |
} else { | |
- IContentProvider provider = acquireProvider(uri); | |
- if (provider == null) { | |
+ IContentProvider unstableProvider = acquireUnstableProvider(uri); | |
+ if (unstableProvider == null) { | |
throw new FileNotFoundException("No content provider: " + uri); | |
} | |
+ IContentProvider stableProvider = null; | |
+ AssetFileDescriptor fd = null; | |
+ | |
try { | |
- AssetFileDescriptor fd = provider.openAssetFile(uri, mode); | |
- if(fd == null) { | |
- // The provider will be released by the finally{} clause | |
- return null; | |
+ try { | |
+ fd = unstableProvider.openAssetFile(uri, mode); | |
+ if (fd == null) { | |
+ // The provider will be released by the finally{} clause | |
+ return null; | |
+ } | |
+ } catch (DeadObjectException e) { | |
+ // The remote process has died... but we only hold an unstable | |
+ // reference though, so we might recover!!! Let's try!!!! | |
+ // This is exciting!!1!!1!!!!1 | |
+ unstableProviderDied(unstableProvider); | |
+ stableProvider = acquireProvider(uri); | |
+ if (stableProvider == null) { | |
+ throw new FileNotFoundException("No content provider: " + uri); | |
+ } | |
+ fd = stableProvider.openAssetFile(uri, mode); | |
+ if (fd == null) { | |
+ // The provider will be released by the finally{} clause | |
+ return null; | |
+ } | |
+ } | |
+ | |
+ if (stableProvider == null) { | |
+ stableProvider = acquireProvider(uri); | |
} | |
+ releaseUnstableProvider(unstableProvider); | |
ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( | |
- fd.getParcelFileDescriptor(), provider); | |
+ fd.getParcelFileDescriptor(), stableProvider); | |
// Success! Don't release the provider when exiting, let | |
// ParcelFileDescriptorInner do that when it is closed. | |
- provider = null; | |
+ stableProvider = null; | |
return new AssetFileDescriptor(pfd, fd.getStartOffset(), | |
fd.getDeclaredLength()); | |
+ | |
} catch (RemoteException e) { | |
- // Somewhat pointless, as Activity Manager will kill this | |
- // process shortly anyway if the depdendent ContentProvider dies. | |
- throw new FileNotFoundException("Dead content provider: " + uri); | |
+ // Whatever, whatever, we'll go away. | |
+ throw new FileNotFoundException( | |
+ "Failed opening content provider: " + uri); | |
} catch (FileNotFoundException e) { | |
throw e; | |
} finally { | |
- if (provider != null) { | |
- releaseProvider(provider); | |
+ if (stableProvider != null) { | |
+ releaseProvider(stableProvider); | |
+ } | |
+ if (unstableProvider != null) { | |
+ releaseUnstableProvider(unstableProvider); | |
} | |
} | |
} | |
@@ -599,32 +703,62 @@ | |
*/ | |
public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, | |
String mimeType, Bundle opts) throws FileNotFoundException { | |
- IContentProvider provider = acquireProvider(uri); | |
- if (provider == null) { | |
+ IContentProvider unstableProvider = acquireUnstableProvider(uri); | |
+ if (unstableProvider == null) { | |
throw new FileNotFoundException("No content provider: " + uri); | |
} | |
+ IContentProvider stableProvider = null; | |
+ AssetFileDescriptor fd = null; | |
+ | |
try { | |
- AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts); | |
- if (fd == null) { | |
- // The provider will be released by the finally{} clause | |
- return null; | |
+ try { | |
+ fd = unstableProvider.openTypedAssetFile(uri, mimeType, opts); | |
+ if (fd == null) { | |
+ // The provider will be released by the finally{} clause | |
+ return null; | |
+ } | |
+ } catch (DeadObjectException e) { | |
+ // The remote process has died... but we only hold an unstable | |
+ // reference though, so we might recover!!! Let's try!!!! | |
+ // This is exciting!!1!!1!!!!1 | |
+ unstableProviderDied(unstableProvider); | |
+ stableProvider = acquireProvider(uri); | |
+ if (stableProvider == null) { | |
+ throw new FileNotFoundException("No content provider: " + uri); | |
+ } | |
+ fd = stableProvider.openTypedAssetFile(uri, mimeType, opts); | |
+ if (fd == null) { | |
+ // The provider will be released by the finally{} clause | |
+ return null; | |
+ } | |
} | |
+ | |
+ if (stableProvider == null) { | |
+ stableProvider = acquireProvider(uri); | |
+ } | |
+ releaseUnstableProvider(unstableProvider); | |
ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( | |
- fd.getParcelFileDescriptor(), provider); | |
+ fd.getParcelFileDescriptor(), stableProvider); | |
// Success! Don't release the provider when exiting, let | |
// ParcelFileDescriptorInner do that when it is closed. | |
- provider = null; | |
+ stableProvider = null; | |
return new AssetFileDescriptor(pfd, fd.getStartOffset(), | |
fd.getDeclaredLength()); | |
+ | |
} catch (RemoteException e) { | |
- throw new FileNotFoundException("Dead content provider: " + uri); | |
+ // Whatever, whatever, we'll go away. | |
+ throw new FileNotFoundException( | |
+ "Failed opening content provider: " + uri); | |
} catch (FileNotFoundException e) { | |
throw e; | |
} finally { | |
- if (provider != null) { | |
- releaseProvider(provider); | |
+ if (stableProvider != null) { | |
+ releaseProvider(stableProvider); | |
+ } | |
+ if (unstableProvider != null) { | |
+ releaseUnstableProvider(unstableProvider); | |
} | |
} | |
} | |
@@ -867,7 +1001,7 @@ | |
} | |
/** | |
- * Call an provider-defined method. This can be used to implement | |
+ * Call a provider-defined method. This can be used to implement | |
* read or write interfaces which are cheaper than using a Cursor and/or | |
* do not fit into the traditional table model. | |
* | |
@@ -950,6 +1084,34 @@ | |
} | |
/** | |
+ * Returns the content provider for the given content URI. | |
+ * | |
+ * @param uri The URI to a content provider | |
+ * @return The ContentProvider for the given URI, or null if no content provider is found. | |
+ * @hide | |
+ */ | |
+ public final IContentProvider acquireUnstableProvider(Uri uri) { | |
+ if (!SCHEME_CONTENT.equals(uri.getScheme())) { | |
+ return null; | |
+ } | |
+ String auth = uri.getAuthority(); | |
+ if (auth != null) { | |
+ return acquireUnstableProvider(mContext, uri.getAuthority()); | |
+ } | |
+ return null; | |
+ } | |
+ | |
+ /** | |
+ * @hide | |
+ */ | |
+ public final IContentProvider acquireUnstableProvider(String name) { | |
+ if (name == null) { | |
+ return null; | |
+ } | |
+ return acquireUnstableProvider(mContext, name); | |
+ } | |
+ | |
+ /** | |
* Returns a {@link ContentProviderClient} that is associated with the {@link ContentProvider} | |
* that services the content at uri, starting the provider if necessary. Returns | |
* null if there is no provider associated wih the uri. The caller must indicate that they are | |
@@ -963,7 +1125,7 @@ | |
public final ContentProviderClient acquireContentProviderClient(Uri uri) { | |
IContentProvider provider = acquireProvider(uri); | |
if (provider != null) { | |
- return new ContentProviderClient(this, provider); | |
+ return new ContentProviderClient(this, provider, true); | |
} | |
return null; | |
@@ -983,7 +1145,57 @@ | |
public final ContentProviderClient acquireContentProviderClient(String name) { | |
IContentProvider provider = acquireProvider(name); | |
if (provider != null) { | |
- return new ContentProviderClient(this, provider); | |
+ return new ContentProviderClient(this, provider, true); | |
+ } | |
+ | |
+ return null; | |
+ } | |
+ | |
+ /** | |
+ * Like {@link #acquireContentProviderClient(Uri)}, but for use when you do | |
+ * not trust the stability of the target content provider. This turns off | |
+ * the mechanism in the platform clean up processes that are dependent on | |
+ * a content provider if that content provider's process goes away. Normally | |
+ * you can safely assume that once you have acquired a provider, you can freely | |
+ * use it as needed and it won't disappear, even if your process is in the | |
+ * background. If using this method, you need to take care to deal with any | |
+ * failures when communicating with the provider, and be sure to close it | |
+ * so that it can be re-opened later. In particular, catching a | |
+ * {@link android.os.DeadObjectException} from the calls there will let you | |
+ * know that the content provider has gone away; at that point the current | |
+ * ContentProviderClient object is invalid, and you should release it. You | |
+ * can acquire a new one if you would like to try to restart the provider | |
+ * and perform new operations on it. | |
+ */ | |
+ public final ContentProviderClient acquireUnstableContentProviderClient(Uri uri) { | |
+ IContentProvider provider = acquireUnstableProvider(uri); | |
+ if (provider != null) { | |
+ return new ContentProviderClient(this, provider, false); | |
+ } | |
+ | |
+ return null; | |
+ } | |
+ | |
+ /** | |
+ * Like {@link #acquireContentProviderClient(String)}, but for use when you do | |
+ * not trust the stability of the target content provider. This turns off | |
+ * the mechanism in the platform clean up processes that are dependent on | |
+ * a content provider if that content provider's process goes away. Normally | |
+ * you can safely assume that once you have acquired a provider, you can freely | |
+ * use it as needed and it won't disappear, even if your process is in the | |
+ * background. If using this method, you need to take care to deal with any | |
+ * failures when communicating with the provider, and be sure to close it | |
+ * so that it can be re-opened later. In particular, catching a | |
+ * {@link android.os.DeadObjectException} from the calls there will let you | |
+ * know that the content provider has gone away; at that point the current | |
+ * ContentProviderClient object is invalid, and you should release it. You | |
+ * can acquire a new one if you would like to try to restart the provider | |
+ * and perform new operations on it. | |
+ */ | |
+ public final ContentProviderClient acquireUnstableContentProviderClient(String name) { | |
+ IContentProvider provider = acquireUnstableProvider(name); | |
+ if (provider != null) { | |
+ return new ContentProviderClient(this, provider, false); | |
} | |
return null; | |
@@ -1030,12 +1242,16 @@ | |
} | |
/** | |
- * Notify registered observers that a row was updated. | |
+ * Notify registered observers that a row was updated and attempt to sync changes | |
+ * to the network. | |
* To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}. | |
* By default, CursorAdapter objects will get this notification. | |
* | |
- * @param uri | |
- * @param observer The observer that originated the change, may be <code>null</null> | |
+ * @param uri The uri of the content that was changed. | |
+ * @param observer The observer that originated the change, may be <code>null</null>. | |
+ * The observer that originated the change will only receive the notification if it | |
+ * has requested to receive self-change notifications by implementing | |
+ * {@link ContentObserver#deliverSelfNotifications()} to return true. | |
*/ | |
public void notifyChange(Uri uri, ContentObserver observer) { | |
notifyChange(uri, observer, true /* sync to network */); | |
@@ -1045,10 +1261,17 @@ | |
* Notify registered observers that a row was updated. | |
* To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}. | |
* By default, CursorAdapter objects will get this notification. | |
- * | |
- * @param uri | |
- * @param observer The observer that originated the change, may be <code>null</null> | |
+ * If syncToNetwork is true, this will attempt to schedule a local sync using the sync | |
+ * adapter that's registered for the authority of the provided uri. No account will be | |
+ * passed to the sync adapter, so all matching accounts will be synchronized. | |
+ * | |
+ * @param uri The uri of the content that was changed. | |
+ * @param observer The observer that originated the change, may be <code>null</null>. | |
+ * The observer that originated the change will only receive the notification if it | |
+ * has requested to receive self-change notifications by implementing | |
+ * {@link ContentObserver#deliverSelfNotifications()} to return true. | |
* @param syncToNetwork If true, attempt to sync the change to the network. | |
+ * @see #requestSync(android.accounts.Account, String, android.os.Bundle) | |
*/ | |
public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { | |
try { | |
@@ -1192,6 +1415,8 @@ | |
/** | |
* Check if the provider should be synced when a network tickle is received | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#READ_SYNC_SETTINGS}. | |
* | |
* @param account the account whose setting we are querying | |
* @param authority the provider whose setting we are querying | |
@@ -1207,6 +1432,8 @@ | |
/** | |
* Set whether or not the provider is synced when it receives a network tickle. | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. | |
* | |
* @param account the account whose setting we are querying | |
* @param authority the provider whose behavior is being controlled | |
@@ -1238,6 +1465,9 @@ | |
* {@link #SYNC_EXTRAS_EXPEDITED}, {@link #SYNC_EXTRAS_MANUAL} set to true. | |
* If any are supplied then an {@link IllegalArgumentException} will be thrown. | |
* | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. | |
+ * | |
* @param account the account to specify in the sync | |
* @param authority the provider to specify in the sync request | |
* @param extras extra parameters to go along with the sync request | |
@@ -1274,6 +1504,8 @@ | |
/** | |
* Remove a periodic sync. Has no affect if account, authority and extras don't match | |
* an existing periodic sync. | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. | |
* | |
* @param account the account of the periodic sync to remove | |
* @param authority the provider of the periodic sync to remove | |
@@ -1296,6 +1528,8 @@ | |
/** | |
* Get the list of information about the periodic syncs for the given account and authority. | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#READ_SYNC_SETTINGS}. | |
* | |
* @param account the account whose periodic syncs we are querying | |
* @param authority the provider whose periodic syncs we are querying | |
@@ -1317,6 +1551,8 @@ | |
/** | |
* Check if this account/provider is syncable. | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#READ_SYNC_SETTINGS}. | |
* @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet. | |
*/ | |
public static int getIsSyncable(Account account, String authority) { | |
@@ -1329,6 +1565,8 @@ | |
/** | |
* Set whether this account/provider is syncable. | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. | |
* @param syncable >0 denotes syncable, 0 means not syncable, <0 means unknown | |
*/ | |
public static void setIsSyncable(Account account, String authority, int syncable) { | |
@@ -1343,6 +1581,8 @@ | |
/** | |
* Gets the master auto-sync setting that applies to all the providers and accounts. | |
* If this is false then the per-provider auto-sync setting is ignored. | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#READ_SYNC_SETTINGS}. | |
* | |
* @return the master auto-sync setting that applies to all the providers and accounts | |
*/ | |
@@ -1357,6 +1597,8 @@ | |
/** | |
* Sets the master auto-sync setting that applies to all the providers and accounts. | |
* If this is false then the per-provider auto-sync setting is ignored. | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. | |
* | |
* @param sync the master auto-sync setting that applies to all the providers and accounts | |
*/ | |
@@ -1372,6 +1614,8 @@ | |
/** | |
* Returns true if there is currently a sync operation for the given | |
* account or authority in the pending list, or actively being processed. | |
+ * <p>This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission#READ_SYNC_STATS}. | |
* @param account the account whose setting we are querying | |
* @param authority the provider whose behavior is being queried | |
* @return true if a sync is active for the given account or authority. | |
@@ -1387,6 +1631,9 @@ | |
/** | |
* If a sync is active returns the information about it, otherwise returns null. | |
* <p> | |
+ * This method requires the caller to hold the permission | |
+ * {@link android.Manifest.permission# |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment