Skip to content

Instantly share code, notes, and snippets.

@zaki50
Created July 3, 2012 07:51
Show Gist options
  • Save zaki50/3038347 to your computer and use it in GitHub Desktop.
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.
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> &lt;service android:name=".MyAccessibilityService"&gt;
+ * <pre> &lt;service android:name=".MyAccessibilityService"
+ * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE&gt;
* &lt;intent-filter&gt;
* &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
* &lt;/intent-filter&gt;
@@ -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>
* &lt;PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"&gt;
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 &lt;uses-permission&gt;}
* 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 &lt;activity_component_name&gt;".
*
* @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(&quot;New mail from &quot; + 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(&quot;New photo from &quot; + 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(&quot;New mail from &quot; + 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(&quot;5 New mails from &quot; + sender.toString())
+ * .setContentText(subject)
+ * .setSmallIcon(R.drawable.new_mail)
+ * .setLargeIcon(aBitmap))
+ * .addLine(str1)
+ * .addLine(str2)
+ * .setContentTitle("")
+ * .setSummaryText(&quot;+3 more&quot;)
+ * .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 &lt;uses-permission&gt;}
* 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 &lt;yourservicename&gt;".
+ * This is distinct from "dumpsys &lt;servicename&gt;", 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 &lt;receiver&gt; 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 &lt;provider_component_name&gt;".
+ *
+ * @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