+
+#include "common.h"
+
+static inline int readx(int s, void *_buf, int count)
+{
+ char *buf = _buf;
+ int n = 0, r;
+ if (count < 0) return -1;
+ while (n < count) {
+ r = read(s, buf + n, count - n);
+ if (r < 0) {
+ if (errno == EINTR) continue;
+ LOGE("read error: %s\n", strerror(errno));
+ return -1;
+ }
+ if (r == 0) {
+ LOGE("eof\n");
+ return -1; /* EOF */
+ }
+ n += r;
+ }
+ return 0;
+}
+
+static inline int writex(int s, const void *_buf, int count)
+{
+ const char *buf = _buf;
+ int n = 0, r;
+ if (count < 0) return -1;
+ while (n < count) {
+ r = write(s, buf + n, count - n);
+ if (r < 0) {
+ if (errno == EINTR) continue;
+ LOGE("write error: %s\n", strerror(errno));
+ return -1;
+ }
+ n += r;
+ }
+ return 0;
+}
+
+static inline int read_marshal(int s, LPC_MARSHAL *cmd)
+{
+ if (readx(s, cmd, 2 * sizeof(uint32_t))) {
+ LOGE("failed to read header\n");
+ return -1;
+ }
+ if (cmd->len > BUFFER_MAX) {
+ LOGE("invalid size %d\n", cmd->len);
+ return -1;
+ }
+ if (readx(s, cmd->data, cmd->len)) {
+ LOGE("failed to read data\n");
+ return -1;
+ }
+ cmd->data[cmd->len] = 0;
+ return 0;
+}
+
+static inline int write_marshal(int s, LPC_MARSHAL *cmd)
+{
+ if (writex(s, cmd, 2 * sizeof(uint32_t))) {
+ LOGE("failed to write marshal header\n");
+ return -1;
+ }
+ if (writex(s, cmd->data, cmd->len)) {
+ LOGE("failed to write marshal data\n");
+ return -1;
+ }
+ return 0;
+}
+
+#endif
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 8212b9212af8d8ad5dc8600a0225acafa341f059..fd9e70884e68641bb117dcb8e9321207bcab693a 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -543,6 +543,9 @@ public final class Pm {
case PackageManager.INSTALL_FAILED_TEST_ONLY:
s = "INSTALL_FAILED_TEST_ONLY";
break;
+ case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE:
+ s = "INSTALL_FAILED_CPU_ABI_INCOMPATIBLE";
+ break;
case PackageManager.INSTALL_PARSE_FAILED_NOT_APK:
s = "INSTALL_PARSE_FAILED_NOT_APK";
break;
diff --git a/cmds/runtime/main_runtime.cpp b/cmds/runtime/main_runtime.cpp
index 1531a9efd154d228b1219c935669cce0fdf07a30..476f38a4d073eb5f943da84486b64bbe95613ac6 100644
--- a/cmds/runtime/main_runtime.cpp
+++ b/cmds/runtime/main_runtime.cpp
@@ -45,9 +45,9 @@ static const char* ZYGOTE_ARGV[] = {
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003",
/* CAP_SYS_TTY_CONFIG & CAP_SYS_RESOURCE & CAP_NET_BROADCAST &
* CAP_NET_ADMIN & CAP_NET_RAW & CAP_NET_BIND_SERVICE & CAP_KILL &
- * CAP_SYS_BOOT
+ * CAP_SYS_BOOT CAP_SYS_NICE
*/
- "--capabilities=88161312,88161312",
+ "--capabilities=96549920,96549920",
"--runtime-init",
"--nice-name=system_server",
"com.android.server.SystemServer"
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3456c756404fb936b417ad787386bd981e82944
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2009 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 com.android.internal.os.HandlerCaller;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * An accessibility service runs in the background and receives callbacks by the system
+ * when {@link AccessibilityEvent}s are fired. Such events denote some state transition
+ * in the user interface, for example, the focus has changed, a button has been clicked,
+ * etc.
+ *
+ * An accessibility service extends this class and implements its abstract methods. Such
+ * a service 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}. Following is an example of such a declaration:
+ *
+ *
+ * <service android:name=".MyAccessibilityService">
+ * <intent-filter>
+ * <action android:name="android.accessibilityservice.AccessibilityService" />
+ * </intent-filter>
+ * </service>
+ *
+ *
+ * The lifecycle of an accessibility service is managed exclusively by the system. Starting
+ * or stopping an accessibility service is triggered by an explicit user action through
+ * enabling or disabling it in the device settings. After the system binds to a service it
+ * calls {@link AccessibilityService#onServiceConnected()}. This method can be
+ * overriden by clients that want to perform post binding setup. An accessibility service
+ * is configured though setting an {@link AccessibilityServiceInfo} by calling
+ * {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. You can call this
+ * method any time to change the service configuration but it is good practice to do that
+ * in the overriden {@link AccessibilityService#onServiceConnected()}.
+ *
+ * An accessibility service can be registered for events in specific packages to provide a
+ * specific type of feedback and is notified with a certain timeout after the last event
+ * of interest has been fired.
+ *
+ * Notification strategy
+ *
+ * For each feedback type only one accessibility service is notified. Services are notified
+ * in the order of registration. Hence, if two services are registered for the same
+ * feedback type in the same package the first one wins. It is possible however, to
+ * register a service as the default one for a given feedback type. In such a case this
+ * service is invoked if no other service was interested in the event. In other words, default
+ * services do not compete with other services and are notified last regardless of the
+ * registration order. This enables "generic" accessibility services that work reasonably
+ * well with most applications to coexist with "polished" ones that are targeted for
+ * specific applications.
+ *
+ * Event types
+ *
+ * {@link AccessibilityEvent#TYPE_VIEW_CLICKED}
+ * {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}
+ * {@link AccessibilityEvent#TYPE_VIEW_FOCUSED}
+ * {@link AccessibilityEvent#TYPE_VIEW_SELECTED}
+ * {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}
+ * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}
+ * {@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}
+ *
+ * Feedback types
+ *
+ * {@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}
+ * {@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}
+ * {@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}
+ * {@link AccessibilityServiceInfo#FEEDBACK_VISUAL}
+ * {@link AccessibilityServiceInfo#FEEDBACK_GENERIC}
+ *
+ * @see AccessibilityEvent
+ * @see AccessibilityServiceInfo
+ * @see android.view.accessibility.AccessibilityManager
+ *
+ * Note: The event notification timeout is useful to avoid propagating events to the client
+ * too frequently since this is accomplished via an expensive interprocess call.
+ * One can think of the timeout as a criteria to determine when event generation has
+ * settled down.
+ */
+public abstract class AccessibilityService extends Service {
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.accessibilityservice.AccessibilityService";
+
+ private static final String LOG_TAG = "AccessibilityService";
+
+ private AccessibilityServiceInfo mInfo;
+
+ IAccessibilityServiceConnection mConnection;
+
+ /**
+ * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
+ *
+ * @param event An event.
+ */
+ public abstract void onAccessibilityEvent(AccessibilityEvent event);
+
+ /**
+ * Callback for interrupting the accessibility feedback.
+ */
+ public abstract void onInterrupt();
+
+ /**
+ * This method is a part of the {@link AccessibilityService} lifecycle and is
+ * called after the system has successfully bound to the service. If is
+ * convenient to use this method for setting the {@link AccessibilityServiceInfo}.
+ *
+ * @see AccessibilityServiceInfo
+ * @see #setServiceInfo(AccessibilityServiceInfo)
+ */
+ protected void onServiceConnected() {
+
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} that describes this service.
+ *
+ * Note: You can call this method any time but the info will be picked up after
+ * the system has bound to this service and when this method is called thereafter.
+ *
+ * @param info The info.
+ */
+ public final void setServiceInfo(AccessibilityServiceInfo info) {
+ mInfo = info;
+ sendServiceInfo();
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} for this service if the latter is
+ * properly set and there is an {@link IAccessibilityServiceConnection} to the
+ * AccessibilityManagerService.
+ */
+ private void sendServiceInfo() {
+ if (mInfo != null && mConnection != null) {
+ try {
+ mConnection.setServiceInfo(mInfo);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
+ }
+ }
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new IEventListenerWrapper(this);
+ }
+
+ /**
+ * Implements the internal {@link IEventListener} interface to convert
+ * incoming calls to it back to calls on an {@link AccessibilityService}.
+ */
+ class IEventListenerWrapper extends IEventListener.Stub
+ implements HandlerCaller.Callback {
+
+ 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 final HandlerCaller mCaller;
+
+ private AccessibilityService mTarget;
+
+ public IEventListenerWrapper(AccessibilityService context) {
+ mTarget = context;
+ mCaller = new HandlerCaller(context, this);
+ }
+
+ public void setConnection(IAccessibilityServiceConnection connection) {
+ Message message = mCaller.obtainMessageO(DO_SET_SET_CONNECTION, connection);
+ mCaller.sendMessage(message);
+ }
+
+ public void onInterrupt() {
+ Message message = mCaller.obtainMessage(DO_ON_INTERRUPT);
+ mCaller.sendMessage(message);
+ }
+
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ Message message = mCaller.obtainMessageO(DO_ON_ACCESSIBILITY_EVENT, event);
+ mCaller.sendMessage(message);
+ }
+
+ public void executeMessage(Message message) {
+ switch (message.what) {
+ case DO_ON_ACCESSIBILITY_EVENT :
+ AccessibilityEvent event = (AccessibilityEvent) message.obj;
+ mTarget.onAccessibilityEvent(event);
+ event.recycle();
+ return;
+ case DO_ON_INTERRUPT :
+ mTarget.onInterrupt();
+ return;
+ case DO_SET_SET_CONNECTION :
+ mConnection = ((IAccessibilityServiceConnection) message.obj);
+ mTarget.onServiceConnected();
+ return;
+ default :
+ Log.w(LOG_TAG, "Unknown message type " + message.what);
+ }
+ }
+ }
+}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl b/core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..1f5d3850d46f18966edd8a3ebd1c0d35193a1a08
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2009 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;
+
+parcelable AccessibilityServiceInfo;
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..4761f98ed7c2722ec90dfeccbff56fd8def06e24
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2009 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class describes an {@link AccessibilityService}. The system
+ * notifies an {@link AccessibilityService} for
+ * {@link android.view.accessibility.AccessibilityEvent}s
+ * according to the information encapsulated in this class.
+ *
+ * @see AccessibilityService
+ * @see android.view.accessibility.AccessibilityEvent
+ */
+public class AccessibilityServiceInfo implements Parcelable {
+
+ /**
+ * Denotes spoken feedback.
+ */
+ public static final int FEEDBACK_SPOKEN = 0x0000001;
+
+ /**
+ * Denotes haptic feedback.
+ */
+ public static final int FEEDBACK_HAPTIC = 0x0000002;
+
+ /**
+ * Denotes audible (not spoken) feedback.
+ */
+ public static final int FEEDBACK_AUDIBLE = 0x0000004;
+
+ /**
+ * Denotes visual feedback.
+ */
+ public static final int FEEDBACK_VISUAL = 0x0000008;
+
+ /**
+ * Denotes generic feedback.
+ */
+ public static final int FEEDBACK_GENERIC = 0x0000010;
+
+ /**
+ * If an {@link AccessibilityService} is the default for a given type.
+ * Default service is invoked only if no package specific one exists. In case of
+ * more than one package specific service only the earlier registered is notified.
+ */
+ public static final int DEFAULT = 0x0000001;
+
+ /**
+ * The event types an {@link AccessibilityService} is interested in.
+ *
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_ACTIVITY_STARTED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
+ */
+ public int eventTypes;
+
+ /**
+ * The package names an {@link AccessibilityService} is interested in. Setting
+ * to null is equivalent to all packages.
+ */
+ public String[] packageNames;
+
+ /**
+ * The feedback type an {@link AccessibilityService} provides.
+ *
+ * @see #FEEDBACK_AUDIBLE
+ * @see #FEEDBACK_GENERIC
+ * @see #FEEDBACK_HAPTIC
+ * @see #FEEDBACK_SPOKEN
+ * @see #FEEDBACK_VISUAL
+ */
+ public int feedbackType;
+
+ /**
+ * The timeout after the most recent event of a given type before an
+ * {@link AccessibilityService} is notified.
+ *
+ * Note: The event notification timeout is useful to avoid propagating events to the client
+ * too frequently since this is accomplished via an expensive interprocess call.
+ * One can think of the timeout as a criteria to determine when event generation has
+ * settled down
+ */
+ public long notificationTimeout;
+
+ /**
+ * This field represents a set of flags used for configuring an
+ * {@link AccessibilityService}.
+ *
+ * @see #DEFAULT
+ */
+ public int flags;
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(eventTypes);
+ parcel.writeStringArray(packageNames);
+ parcel.writeInt(feedbackType);
+ parcel.writeLong(notificationTimeout);
+ parcel.writeInt(flags);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.eventTypes = parcel.readInt();
+ info.packageNames = parcel.readStringArray();
+ info.feedbackType = parcel.readInt();
+ info.notificationTimeout = parcel.readLong();
+ info.flags = parcel.readInt();
+ return info;
+ }
+
+ public AccessibilityServiceInfo[] newArray(int size) {
+ return new AccessibilityServiceInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..7157def606fa2f5b1ed361e3da7de388254a73ec
--- /dev/null
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2009 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.AccessibilityServiceInfo;
+
+/**
+ * Interface AccessibilityManagerService#Service implements, and passes to an
+ * AccessibilityService so it can dynamically configure how the system handles it.
+ *
+ * @hide
+ */
+oneway interface IAccessibilityServiceConnection {
+
+ void setServiceInfo(in AccessibilityServiceInfo info);
+}
diff --git a/core/java/android/accessibilityservice/IEventListener.aidl b/core/java/android/accessibilityservice/IEventListener.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..5b849f1e40fdadaec283a61d7bb8f943d8563400
--- /dev/null
+++ b/core/java/android/accessibilityservice/IEventListener.aidl
@@ -0,0 +1,34 @@
+/*
+** Copyright 2009, 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.IAccessibilityServiceConnection;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * Top-level interface to accessibility service component (implemented in Service).
+ *
+ * @hide
+ */
+ oneway interface IEventListener {
+
+ void setConnection(in IAccessibilityServiceConnection connection);
+
+ void onAccessibilityEvent(in AccessibilityEvent event);
+
+ void onInterrupt();
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 9b1f0f97171f0ea29695cdca49ab8693b9b38a19..ca9632a490b1ad1555f85c6ba4871cbaab7aeef8 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,11 +16,14 @@
package android.app;
+import com.android.internal.policy.PolicyManager;
+
import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IIntentSender;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -32,11 +35,12 @@ import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.text.Selection;
import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
import android.util.Config;
@@ -58,10 +62,10 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
+import android.view.ViewGroup.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
-import com.android.internal.policy.PolicyManager;
-
import java.util.ArrayList;
import java.util.HashMap;
@@ -625,6 +629,8 @@ public class Activity extends ContextThemeWrapper
boolean mStartedActivity;
/*package*/ int mConfigChangeFlags;
/*package*/ Configuration mCurrentConfig;
+ private SearchManager mSearchManager;
+ private Bundle mSearchDialogState = null;
private Window mWindow;
@@ -785,6 +791,9 @@ public class Activity extends ContextThemeWrapper
protected void onCreate(Bundle savedInstanceState) {
mVisibleFromClient = mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, true);
+ // uses super.getSystemService() since this.getSystemService() looks at the
+ // mSearchManager field.
+ mSearchManager = (SearchManager) super.getSystemService(Context.SEARCH_SERVICE);
mCalled = true;
}
@@ -802,9 +811,10 @@ public class Activity extends ContextThemeWrapper
// Also restore the state of a search dialog (if any)
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.restoreSearchDialog(savedInstanceState, SAVED_SEARCH_DIALOG_KEY);
+ Bundle searchState = savedInstanceState.getBundle(SAVED_SEARCH_DIALOG_KEY);
+ if (searchState != null) {
+ mSearchManager.restoreSearchDialog(searchState);
+ }
}
/**
@@ -854,13 +864,26 @@ public class Activity extends ContextThemeWrapper
final Integer dialogId = ids[i];
Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId));
if (dialogState != null) {
- final Dialog dialog = onCreateDialog(dialogId);
- dialog.onRestoreInstanceState(dialogState);
+ // Calling onRestoreInstanceState() below will invoke dispatchOnCreate
+ // so tell createDialog() not to do it, otherwise we get an exception
+ final Dialog dialog = createDialog(dialogId, false);
mManagedDialogs.put(dialogId, dialog);
+ onPrepareDialog(dialogId, dialog);
+ dialog.onRestoreInstanceState(dialogState);
}
}
}
+ private Dialog createDialog(Integer dialogId, boolean dispatchOnCreate) {
+ final Dialog dialog = onCreateDialog(dialogId);
+ if (dialog == null) {
+ throw new IllegalArgumentException("Activity#onCreateDialog did "
+ + "not create a dialog for id " + dialogId);
+ }
+ if (dispatchOnCreate) dialog.dispatchOnCreate(null);
+ return dialog;
+ }
+
private String savedDialogKeyFor(int key) {
return SAVED_DIALOG_KEY_PREFIX + key;
}
@@ -1010,9 +1033,11 @@ public class Activity extends ContextThemeWrapper
// Also save the state of a search dialog (if any)
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.saveSearchDialog(outState, SAVED_SEARCH_DIALOG_KEY);
+ // onPause() should always be called before this method, so mSearchManagerState
+ // should be up to date.
+ if (mSearchDialogState != null) {
+ outState.putBundle(SAVED_SEARCH_DIALOG_KEY, mSearchDialogState);
+ }
}
/**
@@ -1283,12 +1308,6 @@ public class Activity extends ContextThemeWrapper
}
}
}
-
- // also dismiss search dialog if showing
- // TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.stopSearch();
// close any cursors we are managing.
int numCursors = mManagedCursors.size();
@@ -1298,6 +1317,10 @@ public class Activity extends ContextThemeWrapper
c.mCursor.close();
}
}
+
+ // Clear any search state saved in performPause(). If the state may be needed in the
+ // future, it will have been saved by performSaveInstanceState()
+ mSearchDialogState = null;
}
/**
@@ -1321,9 +1344,7 @@ public class Activity extends ContextThemeWrapper
// also update search dialog if showing
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.onConfigurationChanged(newConfig);
+ mSearchManager.onConfigurationChanged(newConfig);
if (mWindow != null) {
// Pass the configuration changed event to the window
@@ -2013,7 +2034,24 @@ public class Activity extends ContextThemeWrapper
}
return onTrackballEvent(ev);
}
-
+
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(getClass().getName());
+ event.setPackageName(getPackageName());
+
+ LayoutParams params = getWindow().getAttributes();
+ boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
+ (params.height == LayoutParams.FILL_PARENT);
+ event.setFullScreen(isFullScreen);
+
+ CharSequence title = getTitle();
+ if (!TextUtils.isEmpty(title)) {
+ event.getText().add(title);
+ }
+
+ return true;
+ }
+
/**
* Default implementation of
* {@link android.view.Window.Callback#onCreatePanelView}
@@ -2394,12 +2432,7 @@ public class Activity extends ContextThemeWrapper
}
Dialog dialog = mManagedDialogs.get(id);
if (dialog == null) {
- dialog = onCreateDialog(id);
- if (dialog == null) {
- throw new IllegalArgumentException("Activity#onCreateDialog did "
- + "not create a dialog for id " + id);
- }
- dialog.dispatchOnCreate(null);
+ dialog = createDialog(id, true);
mManagedDialogs.put(id, dialog);
}
@@ -2523,10 +2556,7 @@ public class Activity extends ContextThemeWrapper
*/
public void startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, boolean globalSearch) {
- // activate the search manager and start it up!
- SearchManager searchManager = (SearchManager)
- getSystemService(Context.SEARCH_SERVICE);
- searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+ mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
appSearchData, globalSearch);
}
@@ -3245,6 +3275,8 @@ public class Activity extends ContextThemeWrapper
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
+ } else if (SEARCH_SERVICE.equals(name)) {
+ return mSearchManager;
}
return super.getSystemService(name);
}
@@ -3543,10 +3575,21 @@ public class Activity extends ContextThemeWrapper
"Activity " + mComponent.toShortString() +
" did not call through to super.onPostResume()");
}
+
+ // restore search dialog, if any
+ if (mSearchDialogState != null) {
+ mSearchManager.restoreSearchDialog(mSearchDialogState);
+ }
+ mSearchDialogState = null;
}
final void performPause() {
onPause();
+
+ // save search dialog state if the search dialog is open,
+ // and then dismiss the search dialog
+ mSearchDialogState = mSearchManager.saveSearchDialog();
+ mSearchManager.stopSearch();
}
final void performUserLeaving() {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 541f413676c8530215344b521f4cff90b0465f5f..dfa8139eecf46b4206d016895d137b1bd459d6a9 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -17,9 +17,11 @@
package android.app;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IIntentSender;
+import android.content.IIntentReceiver;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.res.Configuration;
@@ -984,7 +986,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
String process = data.readString();
boolean start = data.readInt() != 0;
String path = data.readString();
- boolean res = profileControl(process, start, path);
+ ParcelFileDescriptor fd = data.readInt() != 0
+ ? data.readFileDescriptor() : null;
+ boolean res = profileControl(process, start, path, fd);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -998,6 +1002,20 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case STOP_APP_SWITCHES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ stopAppSwitches();
+ reply.writeNoException();
+ return true;
+ }
+
+ case RESUME_APP_SWITCHES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ resumeAppSwitches();
+ reply.writeNoException();
+ return true;
+ }
+
case PEEK_SERVICE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
Intent service = Intent.CREATOR.createFromParcel(data);
@@ -1007,6 +1025,33 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeStrongBinder(binder);
return true;
}
+
+ case START_BACKUP_AGENT_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ ApplicationInfo info = ApplicationInfo.CREATOR.createFromParcel(data);
+ int backupRestoreMode = data.readInt();
+ boolean success = bindBackupAgent(info, backupRestoreMode);
+ reply.writeNoException();
+ reply.writeInt(success ? 1 : 0);
+ return true;
+ }
+
+ case BACKUP_AGENT_CREATED_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String packageName = data.readString();
+ IBinder agent = data.readStrongBinder();
+ backupAgentCreated(packageName, agent);
+ reply.writeNoException();
+ return true;
+ }
+
+ case UNBIND_BACKUP_AGENT_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ ApplicationInfo info = ApplicationInfo.CREATOR.createFromParcel(data);
+ unbindBackupAgent(info);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -1667,6 +1712,43 @@ class ActivityManagerProxy implements IActivityManager
return binder;
}
+ public boolean bindBackupAgent(ApplicationInfo app, int backupRestoreMode)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ app.writeToParcel(data, 0);
+ data.writeInt(backupRestoreMode);
+ mRemote.transact(START_BACKUP_AGENT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean success = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return success;
+ }
+
+ public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ data.writeStrongBinder(agent);
+ mRemote.transact(BACKUP_AGENT_CREATED_TRANSACTION, data, reply, 0);
+ reply.recycle();
+ data.recycle();
+ }
+
+ public void unbindBackupAgent(ApplicationInfo app) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ app.writeToParcel(data, 0);
+ mRemote.transact(UNBIND_BACKUP_AGENT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher)
throws RemoteException {
@@ -2152,7 +2234,7 @@ class ActivityManagerProxy implements IActivityManager
}
public boolean profileControl(String process, boolean start,
- String path) throws RemoteException
+ String path, ParcelFileDescriptor fd) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2160,6 +2242,12 @@ class ActivityManagerProxy implements IActivityManager
data.writeString(process);
data.writeInt(start ? 1 : 0);
data.writeString(path);
+ if (fd != null) {
+ data.writeInt(1);
+ fd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(PROFILE_CONTROL_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
@@ -2182,5 +2270,25 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ public void stopAppSwitches() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(STOP_APP_SWITCHES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
+ public void resumeAppSwitches() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(RESUME_APP_SWITCHES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1e15d14271d3815816b92f1de0ce979a34bd64a4..5ee29ac4f473821581195407bbe69b7b5900fb15 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -23,6 +23,7 @@ import android.content.ContentProvider;
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
+import android.content.IIntentReceiver;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -31,6 +32,7 @@ import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.PackageParser.Component;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -46,6 +48,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -72,6 +75,7 @@ import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -115,6 +119,7 @@ public final class ActivityThread {
private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
private static final boolean DEBUG_BROADCAST = false;
private static final boolean DEBUG_RESULTS = false;
+ private static final boolean DEBUG_BACKUP = true;
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;
@@ -161,7 +166,7 @@ public final class ActivityThread {
return metrics;
}
- Resources getTopLevelResources(String appDir, float applicationScale) {
+ Resources getTopLevelResources(String appDir, PackageInfo pkgInfo) {
synchronized (mPackages) {
//Log.w(TAG, "getTopLevelResources: " + appDir);
WeakReference wr = mActiveResources.get(appDir);
@@ -180,23 +185,17 @@ public final class ActivityThread {
if (assets.addAssetPath(appDir) == 0) {
return null;
}
- DisplayMetrics metrics = getDisplayMetricsLocked(false);
- // density used to load resources
- // scaledDensity is calculated in Resources constructor
- //
- boolean usePreloaded = true;
-
- // TODO: use explicit flag to indicate the compatibility mode.
- if (applicationScale != 1.0f) {
- usePreloaded = false;
- DisplayMetrics newMetrics = new DisplayMetrics();
- newMetrics.setTo(metrics);
- float newDensity = metrics.density / applicationScale;
- newMetrics.updateDensity(newDensity);
- metrics = newMetrics;
+ ApplicationInfo appInfo;
+ try {
+ appInfo = getPackageManager().getApplicationInfo(
+ pkgInfo.getPackageName(),
+ PackageManager.GET_SUPPORTS_DENSITIES);
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
}
//Log.i(TAG, "Resource:" + appDir + ", display metrics=" + metrics);
- r = new Resources(assets, metrics, getConfiguration(), usePreloaded);
+ DisplayMetrics metrics = getDisplayMetricsLocked(false);
+ r = new Resources(assets, metrics, getConfiguration(), appInfo);
//Log.i(TAG, "Created app resources " + r + ": " + r.getConfiguration());
// XXX need to remove entries when weak references go away
mActiveResources.put(appDir, new WeakReference(r));
@@ -224,7 +223,6 @@ public final class ActivityThread {
private Resources mResources;
private ClassLoader mClassLoader;
private Application mApplication;
- private float mApplicationScale;
private final HashMap> mReceivers
= new HashMap>();
@@ -267,8 +265,6 @@ public final class ActivityThread {
mClassLoader = mSystemContext.getClassLoader();
mResources = mSystemContext.getResources();
}
-
- mApplicationScale = -1.0f;
}
public PackageInfo(ActivityThread activityThread, String name,
@@ -287,56 +283,20 @@ public final class ActivityThread {
mIncludeCode = true;
mClassLoader = systemContext.getClassLoader();
mResources = systemContext.getResources();
- mApplicationScale = systemContext.getApplicationScale();
}
public String getPackageName() {
return mPackageName;
}
+ public ApplicationInfo getApplicationInfo() {
+ return mApplicationInfo;
+ }
+
public boolean isSecurityViolation() {
return mSecurityViolation;
}
- public float getApplicationScale() {
- if (mApplicationScale > 0.0f) {
- return mApplicationScale;
- }
- DisplayMetrics metrics = mActivityThread.getDisplayMetricsLocked(false);
- // Find out the density scale (relative to 160) of the supported density that
- // is closest to the system's density.
- try {
- ApplicationInfo ai = getPackageManager().getApplicationInfo(
- mPackageName, PackageManager.GET_SUPPORTS_DENSITIES);
-
- float appScale = -1.0f;
- if (ai.supportsDensities != null) {
- int minDiff = Integer.MAX_VALUE;
- for (int density : ai.supportsDensities) {
- int tmpDiff = (int) Math.abs(DisplayMetrics.DEVICE_DENSITY - density);
- if (tmpDiff == 0) {
- appScale = 1.0f;
- break;
- }
- // prefer higher density (appScale>1.0), unless that's only option.
- if (tmpDiff < minDiff && appScale < 1.0f) {
- appScale = DisplayMetrics.DEVICE_DENSITY / density;
- minDiff = tmpDiff;
- }
- }
- }
- if (appScale < 0.0f) {
- mApplicationScale = metrics.density;
- } else {
- mApplicationScale = appScale;
- }
- } catch (RemoteException e) {
- throw new AssertionError(e);
- }
- if (localLOGV) Log.v(TAG, "appScale=" + mApplicationScale + ", pkg=" + mPackageName);
- return mApplicationScale;
- }
-
/**
* Gets the array of shared libraries that are listed as
* used by the given package.
@@ -494,12 +454,12 @@ public final class ActivityThread {
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
- mResources = mainThread.getTopLevelResources(mResDir, getApplicationScale());
+ mResources = mainThread.getTopLevelResources(mResDir, this);
}
return mResources;
}
- public Application makeApplication() {
+ public Application makeApplication(boolean forceDefaultAppClass) {
if (mApplication != null) {
return mApplication;
}
@@ -507,7 +467,7 @@ public final class ActivityThread {
Application app = null;
String appClass = mApplicationInfo.className;
- if (appClass == null) {
+ if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
@@ -1199,6 +1159,16 @@ public final class ActivityThread {
}
}
+ private static final class CreateBackupAgentData {
+ ApplicationInfo appInfo;
+ int backupMode;
+ public String toString() {
+ return "CreateBackupAgentData{appInfo=" + appInfo
+ + " backupAgent=" + appInfo.backupAgentName
+ + " mode=" + backupMode + "}";
+ }
+ }
+
private static final class CreateServiceData {
IBinder token;
ServiceInfo info;
@@ -1239,6 +1209,7 @@ public final class ActivityThread {
Bundle instrumentationArgs;
IInstrumentationWatcher instrumentationWatcher;
int debugMode;
+ boolean restrictedBackupMode;
Configuration config;
boolean handlingProfiling;
public String toString() {
@@ -1267,6 +1238,11 @@ public final class ActivityThread {
String who;
}
+ private static final class ProfilerControlData {
+ String path;
+ ParcelFileDescriptor fd;
+ }
+
private final class ApplicationThread extends ApplicationThreadNative {
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%17s %8d";
@@ -1374,6 +1350,21 @@ public final class ActivityThread {
queueOrSendMessage(H.RECEIVER, r);
}
+ public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) {
+ CreateBackupAgentData d = new CreateBackupAgentData();
+ d.appInfo = app;
+ d.backupMode = backupMode;
+
+ queueOrSendMessage(H.CREATE_BACKUP_AGENT, d);
+ }
+
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app) {
+ CreateBackupAgentData d = new CreateBackupAgentData();
+ d.appInfo = app;
+
+ queueOrSendMessage(H.DESTROY_BACKUP_AGENT, d);
+ }
+
public final void scheduleCreateService(IBinder token,
ServiceInfo info) {
CreateServiceData s = new CreateServiceData();
@@ -1419,7 +1410,7 @@ public final class ActivityThread {
ApplicationInfo appInfo, List providers,
ComponentName instrumentationName, String profileFile,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- int debugMode, Configuration config,
+ int debugMode, boolean isRestrictedBackupMode, Configuration config,
Map services) {
Process.setArgV0(processName);
@@ -1437,6 +1428,7 @@ public final class ActivityThread {
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.debugMode = debugMode;
+ data.restrictedBackupMode = isRestrictedBackupMode;
data.config = config;
queueOrSendMessage(H.BIND_APPLICATION, data);
}
@@ -1509,10 +1501,25 @@ public final class ActivityThread {
}
}
- public void profilerControl(boolean start, String path) {
- queueOrSendMessage(H.PROFILER_CONTROL, path, start ? 1 : 0);
+ public void profilerControl(boolean start, String path, ParcelFileDescriptor fd) {
+ ProfilerControlData pcd = new ProfilerControlData();
+ pcd.path = path;
+ pcd.fd = fd;
+ queueOrSendMessage(H.PROFILER_CONTROL, pcd, start ? 1 : 0);
+ }
+
+ public void setSchedulingGroup(int group) {
+ // Note: do this immediately, since going into the foreground
+ // should happen regardless of what pending work we have to do
+ // and the activity manager will wait for us to report back that
+ // we are done before sending us to the background.
+ try {
+ Process.setProcessGroup(Process.myPid(), group);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed setting process group to " + group, e);
+ }
}
-
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
long nativeMax = Debug.getNativeHeapSize() / 1024;
@@ -1706,6 +1713,8 @@ public final class ActivityThread {
public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
public static final int RELAUNCH_ACTIVITY = 126;
public static final int PROFILER_CONTROL = 127;
+ public static final int CREATE_BACKUP_AGENT = 128;
+ public static final int DESTROY_BACKUP_AGENT = 129;
String codeToString(int code) {
if (localLOGV) {
switch (code) {
@@ -1737,6 +1746,8 @@ public final class ActivityThread {
case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PROFILER_CONTROL: return "PROFILER_CONTROL";
+ case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
+ case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
}
}
return "(unknown)";
@@ -1837,7 +1848,13 @@ public final class ActivityThread {
handleActivityConfigurationChanged((IBinder)msg.obj);
break;
case PROFILER_CONTROL:
- handleProfilerControl(msg.arg1 != 0, (String)msg.obj);
+ handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj);
+ break;
+ case CREATE_BACKUP_AGENT:
+ handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
+ break;
+ case DESTROY_BACKUP_AGENT:
+ handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);
break;
}
}
@@ -1896,6 +1913,8 @@ public final class ActivityThread {
Application mInitialApplication;
final ArrayList mAllApplications
= new ArrayList();
+ // set of instantiated backup agents, keyed by package name
+ final HashMap mBackupAgents = new HashMap();
static final ThreadLocal sThreadLocal = new ThreadLocal();
Instrumentation mInstrumentation;
String mInstrumentationAppDir = null;
@@ -2079,6 +2098,10 @@ public final class ActivityThread {
return mInitialApplication;
}
+ public String getProcessName() {
+ return mBoundApplication.processName;
+ }
+
public ApplicationContext getSystemContext() {
synchronized (this) {
if (mSystemContext == null) {
@@ -2257,7 +2280,7 @@ public final class ActivityThread {
}
try {
- Application app = r.packageInfo.makeApplication();
+ Application app = r.packageInfo.makeApplication(false);
if (localLOGV) Log.v(TAG, "Performing launch of " + r);
if (localLOGV) Log.v(
@@ -2452,7 +2475,7 @@ public final class ActivityThread {
}
try {
- Application app = packageInfo.makeApplication();
+ Application app = packageInfo.makeApplication(false);
if (localLOGV) Log.v(
TAG, "Performing receive of " + data.intent
@@ -2495,6 +2518,85 @@ public final class ActivityThread {
}
}
+ // Instantiate a BackupAgent and tell it that it's alive
+ private final void handleCreateBackupAgent(CreateBackupAgentData data) {
+ if (DEBUG_BACKUP) Log.v(TAG, "handleCreateBackupAgent: " + data);
+
+ // no longer idle; we have backup work to do
+ unscheduleGcIdler();
+
+ // instantiate the BackupAgent class named in the manifest
+ PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
+ String packageName = packageInfo.mPackageName;
+ if (mBackupAgents.get(packageName) != null) {
+ Log.d(TAG, "BackupAgent " + " for " + packageName
+ + " already exists");
+ return;
+ }
+
+ BackupAgent agent = null;
+ String classname = data.appInfo.backupAgentName;
+ if (classname == null) {
+ if (data.backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) {
+ Log.e(TAG, "Attempted incremental backup but no defined agent for "
+ + packageName);
+ return;
+ }
+ classname = "android.app.FullBackupAgent";
+ }
+ try {
+ java.lang.ClassLoader cl = packageInfo.getClassLoader();
+ agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to instantiate backup agent "
+ + data.appInfo.backupAgentName + ": " + e.toString(), e);
+ }
+
+ // set up the agent's context
+ try {
+ if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent "
+ + data.appInfo.backupAgentName);
+
+ ApplicationContext context = new ApplicationContext();
+ context.init(packageInfo, null, this);
+ context.setOuterContext(agent);
+ agent.attach(context);
+ agent.onCreate();
+
+ // tell the OS that we're live now
+ IBinder binder = agent.onBind();
+ try {
+ ActivityManagerNative.getDefault().backupAgentCreated(packageName, binder);
+ } catch (RemoteException e) {
+ // nothing to do.
+ }
+ mBackupAgents.put(packageName, agent);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to create BackupAgent "
+ + data.appInfo.backupAgentName + ": " + e.toString(), e);
+ }
+ }
+
+ // Tear down a BackupAgent
+ private final void handleDestroyBackupAgent(CreateBackupAgentData data) {
+ if (DEBUG_BACKUP) Log.v(TAG, "handleDestroyBackupAgent: " + data);
+
+ PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
+ String packageName = packageInfo.mPackageName;
+ BackupAgent agent = mBackupAgents.get(packageName);
+ if (agent != null) {
+ try {
+ agent.onDestroy();
+ } catch (Exception e) {
+ Log.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo);
+ e.printStackTrace();
+ }
+ mBackupAgents.remove(packageName);
+ } else {
+ Log.w(TAG, "Attempt to destroy unknown backup agent " + data);
+ }
+ }
+
private final void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
@@ -2520,7 +2622,7 @@ public final class ActivityThread {
ApplicationContext context = new ApplicationContext();
context.init(packageInfo, null, this);
- Application app = packageInfo.makeApplication();
+ Application app = packageInfo.makeApplication(false);
context.setOuterContext(service);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
@@ -3134,7 +3236,7 @@ public final class ActivityThread {
r.activity.getComponentName().getClassName());
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
- "Activity " + r.intent.getComponent().toShortString()
+ "Activity " + safeToComponentShortString(r.intent)
+ " did not call through to super.onPause()");
}
} catch (SuperNotCalledException e) {
@@ -3143,7 +3245,7 @@ public final class ActivityThread {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to pause activity "
- + r.intent.getComponent().toShortString()
+ + safeToComponentShortString(r.intent)
+ ": " + e.toString(), e);
}
}
@@ -3158,7 +3260,7 @@ public final class ActivityThread {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to stop activity "
- + r.intent.getComponent().toShortString()
+ + safeToComponentShortString(r.intent)
+ ": " + e.toString(), e);
}
}
@@ -3183,7 +3285,7 @@ public final class ActivityThread {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to retain child activities "
- + r.intent.getComponent().toShortString()
+ + safeToComponentShortString(r.intent)
+ ": " + e.toString(), e);
}
}
@@ -3194,7 +3296,7 @@ public final class ActivityThread {
r.activity.onDestroy();
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
- "Activity " + r.intent.getComponent().toShortString() +
+ "Activity " + safeToComponentShortString(r.intent) +
" did not call through to super.onDestroy()");
}
if (r.window != null) {
@@ -3205,8 +3307,7 @@ public final class ActivityThread {
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
- "Unable to destroy activity "
- + r.intent.getComponent().toShortString()
+ "Unable to destroy activity " + safeToComponentShortString(r.intent)
+ ": " + e.toString(), e);
}
}
@@ -3216,6 +3317,11 @@ public final class ActivityThread {
return r;
}
+ private static String safeToComponentShortString(Intent intent) {
+ ComponentName component = intent.getComponent();
+ return component == null ? "[Unknown]" : component.toShortString();
+ }
+
private final void handleDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
ActivityRecord r = performDestroyActivity(token, finishing,
@@ -3475,8 +3581,6 @@ public final class ActivityThread {
}
mConfiguration.updateFrom(config);
DisplayMetrics dm = getDisplayMetricsLocked(true);
- DisplayMetrics appDm = new DisplayMetrics();
- appDm.setTo(dm);
// set it for java, this also affects newly created Resources
if (config.locale != null) {
@@ -3496,11 +3600,7 @@ public final class ActivityThread {
WeakReference v = it.next();
Resources r = v.get();
if (r != null) {
- // keep the original density based on application cale.
- appDm.updateDensity(r.getDisplayMetrics().density);
- r.updateConfiguration(config, appDm);
- // reset
- appDm.setTo(dm);
+ r.updateConfiguration(config, dm);
//Log.i(TAG, "Updated app resources " + v.getKey()
// + " " + r + ": " + r.getConfiguration());
} else {
@@ -3528,15 +3628,20 @@ public final class ActivityThread {
performConfigurationChanged(r.activity, mConfiguration);
}
- final void handleProfilerControl(boolean start, String path) {
+ final void handleProfilerControl(boolean start, ProfilerControlData pcd) {
if (start) {
- File file = new File(path);
- file.getParentFile().mkdirs();
try {
- Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
+ Debug.startMethodTracing(pcd.path, pcd.fd.getFileDescriptor(),
+ 8 * 1024 * 1024, 0);
} catch (RuntimeException e) {
- Log.w(TAG, "Profiling failed on path " + path
+ Log.w(TAG, "Profiling failed on path " + pcd.path
+ " -- can the process access this path?");
+ } finally {
+ try {
+ pcd.fd.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failure closing profile fd", e);
+ }
}
} else {
Debug.stopMethodTracing();
@@ -3592,6 +3697,13 @@ public final class ActivityThread {
*/
Locale.setDefault(data.config.locale);
+ /*
+ * Update the system configuration since its preloaded and might not
+ * reflect configuration changes. The configuration object passed
+ * in AppBindData can be safely assumed to be up to date
+ */
+ Resources.getSystem().updateConfiguration(mConfiguration, null);
+
data.info = getPackageInfoNoCheck(data.appInfo);
if (data.debugMode != IApplicationThread.DEBUG_OFF) {
@@ -3682,7 +3794,9 @@ public final class ActivityThread {
mInstrumentation = new Instrumentation();
}
- Application app = data.info.makeApplication();
+ // 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);
mInitialApplication = app;
List providers = data.providers;
@@ -3867,7 +3981,10 @@ public final class ActivityThread {
ProviderRecord pr = mProviderMap.get(name);
if (pr.mProvider.asBinder() == provider.asBinder()) {
Log.i(TAG, "Removing dead content provider: " + name);
- mProviderMap.remove(name);
+ ProviderRecord removed = mProviderMap.remove(name);
+ if (removed != null) {
+ removed.mProvider.asBinder().unlinkToDeath(removed, 0);
+ }
}
}
}
@@ -3876,7 +3993,10 @@ public final class ActivityThread {
ProviderRecord pr = mProviderMap.get(name);
if (pr.mProvider.asBinder() == provider.asBinder()) {
Log.i(TAG, "Removing dead content provider: " + name);
- mProviderMap.remove(name);
+ ProviderRecord removed = mProviderMap.remove(name);
+ if (removed != null) {
+ removed.mProvider.asBinder().unlinkToDeath(removed, 0);
+ }
}
}
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
index bb17dc35b4e098709bf6a86fc079efa881113d0b..38ea686f7077a900d35b3316d1b738878bb44a97 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ApplicationContext.java
@@ -16,8 +16,11 @@
package android.app;
-import com.google.android.collect.Maps;
+import com.android.internal.policy.PolicyManager;
import com.android.internal.util.XmlUtils;
+import com.google.android.collect.Maps;
+
+import org.xmlpull.v1.XmlPullParserException;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.IBluetoothDevice;
@@ -29,6 +32,8 @@ import android.content.ContextWrapper;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IIntentReceiver;
+import android.content.IntentSender;
import android.content.ReceiverCallNotAllowedException;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
@@ -37,9 +42,9 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageStatsObserver;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
+import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -68,29 +73,30 @@ import android.net.wifi.IWifiManager;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Looper;
-import android.os.RemoteException;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.IPowerManager;
+import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Vibrator;
import android.os.FileUtils.FileStatus;
import android.telephony.TelephonyManager;
import android.text.ClipboardManager;
import android.util.AndroidRuntimeException;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ContextThemeWrapper;
+import android.view.Display;
import android.view.LayoutInflater;
import android.view.WindowManagerImpl;
+import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
-import com.android.internal.policy.PolicyManager;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -100,16 +106,14 @@ import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.WeakHashMap;
import java.util.Set;
-import java.util.HashSet;
+import java.util.WeakHashMap;
import java.util.Map.Entry;
-import org.xmlpull.v1.XmlPullParserException;
-
class ReceiverRestrictedContext extends ContextWrapper {
ReceiverRestrictedContext(Context base) {
super(base);
@@ -147,6 +151,7 @@ class ReceiverRestrictedContext extends ContextWrapper {
*/
class ApplicationContext extends Context {
private final static String TAG = "ApplicationContext";
+ private final static boolean DEBUG = false;
private final static boolean DEBUG_ICONS = false;
private static final Object sSync = new Object();
@@ -172,6 +177,7 @@ class ApplicationContext extends Context {
private Resources.Theme mTheme = null;
private PackageManager mPackageManager;
private NotificationManager mNotificationManager = null;
+ private AccessibilityManager mAccessibilityManager = null;
private ActivityManager mActivityManager = null;
private Context mReceiverRestrictedContext = null;
private SearchManager mSearchManager = null;
@@ -181,6 +187,7 @@ class ApplicationContext extends Context {
private StatusBarManager mStatusBarManager = null;
private TelephonyManager mTelephonyManager = null;
private ClipboardManager mClipboardManager = null;
+ private boolean mRestricted;
private final Object mSync = new Object();
@@ -279,6 +286,14 @@ class ApplicationContext extends Context {
throw new RuntimeException("Not supported in system context");
}
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ if (mPackageInfo != null) {
+ return mPackageInfo.getApplicationInfo();
+ }
+ throw new RuntimeException("Not supported in system context");
+ }
+
@Override
public String getPackageResourcePath() {
if (mPackageInfo != null) {
@@ -299,10 +314,14 @@ class ApplicationContext extends Context {
return new File(prefsFile.getPath() + ".bak");
}
+ public File getSharedPrefsFile(String name) {
+ return makeFilename(getPreferencesDir(), name + ".xml");
+ }
+
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
- File f = makeFilename(getPreferencesDir(), name + ".xml");
+ File f = getSharedPrefsFile(name);
synchronized (sSharedPrefs) {
sp = sSharedPrefs.get(f);
if (sp != null && !sp.hasFileChanged()) {
@@ -550,19 +569,6 @@ class ApplicationContext extends Context {
}
}
- /**
- * @hide
- */
- @Override
- public float getApplicationScale() {
- if (mPackageInfo != null) {
- return mPackageInfo.getApplicationScale();
- } else {
- // same as system density
- return 1.0f;
- }
- }
-
@Override
public void setWallpaper(Bitmap bitmap) throws IOException {
try {
@@ -904,6 +910,8 @@ class ApplicationContext extends Context {
return getNotificationManager();
} else if (KEYGUARD_SERVICE.equals(name)) {
return new KeyguardManager();
+ } else if (ACCESSIBILITY_SERVICE.equals(name)) {
+ return AccessibilityManager.getInstance(this);
} else if (LOCATION_SERVICE.equals(name)) {
return getLocationManager();
} else if (SEARCH_SERVICE.equals(name)) {
@@ -1033,11 +1041,6 @@ class ApplicationContext extends Context {
}
private SearchManager getSearchManager() {
- // This is only useable in Activity Contexts
- if (getActivityToken() == null) {
- throw new AndroidRuntimeException(
- "Acquiring SearchManager objects only valid in Activity Contexts.");
- }
synchronized (mSync) {
if (mSearchManager == null) {
mSearchManager = new SearchManager(getOuterContext(), mMainThread.getHandler());
@@ -1238,7 +1241,7 @@ class ApplicationContext extends Context {
@Override
public int checkUriPermission(Uri uri, String readPermission,
String writePermission, int pid, int uid, int modeFlags) {
- if (false) {
+ if (DEBUG) {
Log.i("foo", "checkUriPermission: uri=" + uri + "readPermission="
+ readPermission + " writePermission=" + writePermission
+ " pid=" + pid + " uid=" + uid + " mode" + modeFlags);
@@ -1337,8 +1340,22 @@ class ApplicationContext extends Context {
mMainThread.getPackageInfo(packageName, flags);
if (pi != null) {
ApplicationContext c = new ApplicationContext();
+ c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
c.init(pi, null, mMainThread);
if (c.mResources != null) {
+ Resources newRes = c.mResources;
+ if (mResources.getCompatibilityInfo().applicationScale !=
+ newRes.getCompatibilityInfo().applicationScale) {
+ DisplayMetrics dm = mMainThread.getDisplayMetricsLocked(false);
+ c.mResources = new Resources(newRes.getAssets(), dm,
+ newRes.getConfiguration(),
+ mResources.getCompatibilityInfo().copy());
+ if (DEBUG) {
+ Log.d(TAG, "loaded context has different scaling. Using container's" +
+ " compatiblity info:" + mResources.getDisplayMetrics());
+ }
+
+ }
return c;
}
}
@@ -1348,6 +1365,11 @@ class ApplicationContext extends Context {
"Application package " + packageName + " not found");
}
+ @Override
+ public boolean isRestricted() {
+ return mRestricted;
+ }
+
private File getDataDirFile() {
if (mPackageInfo != null) {
return mPackageInfo.getDataDirFile();
@@ -1453,7 +1475,7 @@ class ApplicationContext extends Context {
if ((mode&MODE_WORLD_WRITEABLE) != 0) {
perms |= FileUtils.S_IWOTH;
}
- if (false) {
+ if (DEBUG) {
Log.i(TAG, "File " + name + ": mode=0x" + Integer.toHexString(mode)
+ ", perms=0x" + Integer.toHexString(perms));
}
@@ -1516,43 +1538,33 @@ class ApplicationContext extends Context {
throw new NameNotFoundException(packageName);
}
- public Intent getLaunchIntentForPackage(String packageName)
- throws NameNotFoundException {
+ @Override
+ public Intent getLaunchIntentForPackage(String packageName) {
// First see if the package has an INFO activity; the existence of
// such an activity is implied to be the desired front-door for the
// overall package (such as if it has multiple launcher entries).
- Intent intent = getLaunchIntentForPackageCategory(this, packageName,
- Intent.CATEGORY_INFO);
- if (intent != null) {
- return intent;
- }
-
+ Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
+ intentToResolve.addCategory(Intent.CATEGORY_INFO);
+ intentToResolve.setPackage(packageName);
+ ResolveInfo resolveInfo = resolveActivity(intentToResolve, 0);
+
// Otherwise, try to find a main launcher activity.
- return getLaunchIntentForPackageCategory(this, packageName,
- Intent.CATEGORY_LAUNCHER);
- }
-
- // XXX This should be implemented as a call to the package manager,
- // to reduce the work needed.
- static Intent getLaunchIntentForPackageCategory(PackageManager pm,
- String packageName, String category) {
+ if (resolveInfo == null) {
+ // reuse the intent instance
+ intentToResolve.removeCategory(Intent.CATEGORY_INFO);
+ intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
+ intentToResolve.setPackage(packageName);
+ resolveInfo = resolveActivity(intentToResolve, 0);
+ }
+ if (resolveInfo == null) {
+ return null;
+ }
Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(packageName, resolveInfo.activityInfo.name);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Intent intentToResolve = new Intent(Intent.ACTION_MAIN, null);
- intentToResolve.addCategory(category);
- final List apps =
- pm.queryIntentActivities(intentToResolve, 0);
- // I wish there were a way to directly get the "main" activity of a
- // package but ...
- for (ResolveInfo app : apps) {
- if (app.activityInfo.packageName.equals(packageName)) {
- intent.setClassName(packageName, app.activityInfo.name);
- return intent;
- }
- }
- return null;
+ return intent;
}
-
+
@Override
public int[] getPackageGids(String packageName)
throws NameNotFoundException {
@@ -2024,8 +2036,7 @@ class ApplicationContext extends Context {
ActivityThread.PackageInfo pi = mContext.mMainThread.getPackageInfoNoCheck(app);
Resources r = mContext.mMainThread.getTopLevelResources(
app.uid == Process.myUid() ? app.sourceDir
- : app.publicSourceDir,
- pi.getApplicationScale());
+ : app.publicSourceDir, pi);
if (r != null) {
return r;
}
@@ -2363,11 +2374,11 @@ class ApplicationContext extends Context {
// Should never happen!
}
}
-
+
@Override
- public void freeStorage(long idealStorageSize, PendingIntent opFinishedIntent) {
+ public void freeStorage(long freeStorageSize, IntentSender pi) {
try {
- mPM.freeStorage(idealStorageSize, opFinishedIntent);
+ mPM.freeStorage(freeStorageSize, pi);
} catch (RemoteException e) {
// Should never happen!
}
@@ -2420,6 +2431,16 @@ class ApplicationContext extends Context {
}
}
+ @Override
+ public void replacePreferredActivity(IntentFilter filter,
+ int match, ComponentName[] set, ComponentName activity) {
+ try {
+ mPM.replacePreferredActivity(filter, match, set, activity);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
@Override
public void clearPackagePreferredActivities(String packageName) {
try {
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b172363296e7a6a6516f72bdcda2d255ccddfe8
--- /dev/null
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Printer;
+
+/**
+ * Describes an application error.
+ *
+ * A report has a type, which is one of
+ *
+ * - {@link #TYPE_CRASH} application crash. Information about the crash
+ * is stored in {@link #crashInfo}.
+ *
- {@link #TYPE_ANR} application not responding. Information about the
+ * ANR is stored in {@link #anrInfo}.
+ *
- {@link #TYPE_NONE} uninitialized instance of {@link ApplicationErrorReport}.
+ *
+ *
+ * @hide
+ */
+
+public class ApplicationErrorReport implements Parcelable {
+ /**
+ * Uninitialized error report.
+ */
+ public static final int TYPE_NONE = 0;
+
+ /**
+ * An error report about an application crash.
+ */
+ public static final int TYPE_CRASH = 1;
+
+ /**
+ * An error report about an application that's not responding.
+ */
+ public static final int TYPE_ANR = 2;
+
+ /**
+ * Type of this report. Can be one of {@link #TYPE_NONE},
+ * {@link #TYPE_CRASH} or {@link #TYPE_ANR}.
+ */
+ public int type;
+
+ /**
+ * Package name of the application.
+ */
+ public String packageName;
+
+ /**
+ * Package name of the application which installed the application this
+ * report pertains to.
+ * This identifies which Market the application came from.
+ */
+ public String installerPackageName;
+
+ /**
+ * Process name of the application.
+ */
+ public String processName;
+
+ /**
+ * Time at which the error occurred.
+ */
+ public long time;
+
+ /**
+ * If this report is of type {@link #TYPE_CRASH}, contains an instance
+ * of CrashInfo describing the crash; otherwise null.
+ */
+ public CrashInfo crashInfo;
+
+ /**
+ * If this report is of type {@link #TYPE_ANR}, contains an instance
+ * of AnrInfo describing the ANR; otherwise null.
+ */
+ public AnrInfo anrInfo;
+
+ /**
+ * Create an uninitialized instance of {@link ApplicationErrorReport}.
+ */
+ public ApplicationErrorReport() {
+ }
+
+ /**
+ * Create an instance of {@link ApplicationErrorReport} initialized from
+ * a parcel.
+ */
+ ApplicationErrorReport(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeString(packageName);
+ dest.writeString(installerPackageName);
+ dest.writeString(processName);
+ dest.writeLong(time);
+
+ switch (type) {
+ case TYPE_CRASH:
+ crashInfo.writeToParcel(dest, flags);
+ break;
+ case TYPE_ANR:
+ anrInfo.writeToParcel(dest, flags);
+ break;
+ }
+ }
+
+ public void readFromParcel(Parcel in) {
+ type = in.readInt();
+ packageName = in.readString();
+ installerPackageName = in.readString();
+ processName = in.readString();
+ time = in.readLong();
+
+ switch (type) {
+ case TYPE_CRASH:
+ crashInfo = new CrashInfo(in);
+ anrInfo = null;
+ break;
+ case TYPE_ANR:
+ anrInfo = new AnrInfo(in);
+ crashInfo = null;
+ break;
+ }
+ }
+
+ /**
+ * Describes an application crash.
+ */
+ public static class CrashInfo {
+ /**
+ * Class name of the exception that caused the crash.
+ */
+ public String exceptionClassName;
+
+ /**
+ * Message stored in the exception.
+ */
+ public String exceptionMessage;
+
+ /**
+ * File which the exception was thrown from.
+ */
+ public String throwFileName;
+
+ /**
+ * Class which the exception was thrown from.
+ */
+ public String throwClassName;
+
+ /**
+ * Method which the exception was thrown from.
+ */
+ public String throwMethodName;
+
+ /**
+ * Stack trace.
+ */
+ public String stackTrace;
+
+ /**
+ * Create an uninitialized instance of CrashInfo.
+ */
+ public CrashInfo() {
+ }
+
+ /**
+ * Create an instance of CrashInfo initialized from a Parcel.
+ */
+ public CrashInfo(Parcel in) {
+ exceptionClassName = in.readString();
+ exceptionMessage = in.readString();
+ throwFileName = in.readString();
+ throwClassName = in.readString();
+ throwMethodName = in.readString();
+ stackTrace = in.readString();
+ }
+
+ /**
+ * Save a CrashInfo instance to a parcel.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(exceptionClassName);
+ dest.writeString(exceptionMessage);
+ dest.writeString(throwFileName);
+ dest.writeString(throwClassName);
+ dest.writeString(throwMethodName);
+ dest.writeString(stackTrace);
+ }
+
+ /**
+ * Dump a CrashInfo instance to a Printer.
+ */
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "exceptionClassName: " + exceptionClassName);
+ pw.println(prefix + "exceptionMessage: " + exceptionMessage);
+ pw.println(prefix + "throwFileName: " + throwFileName);
+ pw.println(prefix + "throwClassName: " + throwClassName);
+ pw.println(prefix + "throwMethodName: " + throwMethodName);
+ pw.println(prefix + "stackTrace: " + stackTrace);
+ }
+ }
+
+ /**
+ * Describes an application not responding error.
+ */
+ public static class AnrInfo {
+ /**
+ * Activity name.
+ */
+ public String activity;
+
+ /**
+ * Description of the operation that timed out.
+ */
+ public String cause;
+
+ /**
+ * Additional info, including CPU stats.
+ */
+ public String info;
+
+ /**
+ * Create an uninitialized instance of AnrInfo.
+ */
+ public AnrInfo() {
+ }
+
+ /**
+ * Create an instance of AnrInfo initialized from a Parcel.
+ */
+ public AnrInfo(Parcel in) {
+ activity = in.readString();
+ cause = in.readString();
+ info = in.readString();
+ }
+
+ /**
+ * Save an AnrInfo instance to a parcel.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(activity);
+ dest.writeString(cause);
+ dest.writeString(info);
+ }
+
+ /**
+ * Dump an AnrInfo instance to a Printer.
+ */
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "activity: " + activity);
+ pw.println(prefix + "cause: " + cause);
+ pw.println(prefix + "info: " + info);
+ }
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public ApplicationErrorReport createFromParcel(Parcel source) {
+ return new ApplicationErrorReport(source);
+ }
+
+ public ApplicationErrorReport[] newArray(int size) {
+ return new ApplicationErrorReport[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Dump the report to a Printer.
+ */
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "type: " + type);
+ pw.println(prefix + "packageName: " + packageName);
+ pw.println(prefix + "installerPackageName: " + installerPackageName);
+ pw.println(prefix + "processName: " + processName);
+ pw.println(prefix + "time: " + time);
+
+ switch (type) {
+ case TYPE_CRASH:
+ crashInfo.dump(pw, prefix);
+ break;
+ case TYPE_ANR:
+ anrInfo.dump(pw, prefix);
+ break;
+ }
+ }
+}
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index bcc9302082060c1ec0e8177d50cfe054432bfd41..b052c99e75fc2bc35b57aa152ab431ffeba4d216 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -18,6 +18,7 @@ package android.app;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IIntentReceiver;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
@@ -25,6 +26,7 @@ import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Parcel;
@@ -230,11 +232,13 @@ public abstract class ApplicationThreadNative extends Binder
IBinder binder = data.readStrongBinder();
IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
int testMode = data.readInt();
+ boolean restrictedBackupMode = (data.readInt() != 0);
Configuration config = Configuration.CREATOR.createFromParcel(data);
HashMap services = data.readHashMap(null);
bindApplication(packageName, info,
providers, testName, profileName,
- testArgs, testWatcher, testMode, config, services);
+ testArgs, testWatcher, testMode, restrictedBackupMode,
+ config, services);
return true;
}
@@ -328,7 +332,34 @@ public abstract class ApplicationThreadNative extends Binder
data.enforceInterface(IApplicationThread.descriptor);
boolean start = data.readInt() != 0;
String path = data.readString();
- profilerControl(start, path);
+ ParcelFileDescriptor fd = data.readInt() != 0
+ ? data.readFileDescriptor() : null;
+ profilerControl(start, path, fd);
+ return true;
+ }
+
+ case SET_SCHEDULING_GROUP_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ int group = data.readInt();
+ setSchedulingGroup(group);
+ return true;
+ }
+
+ case SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(data);
+ int backupMode = data.readInt();
+ scheduleCreateBackupAgent(appInfo, backupMode);
+ return true;
+ }
+
+ case SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(data);
+ scheduleDestroyBackupAgent(appInfo);
return true;
}
}
@@ -484,6 +515,24 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
+ public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ app.writeToParcel(data, 0);
+ data.writeInt(backupMode);
+ mRemote.transact(SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION, data, null, 0);
+ data.recycle();
+ }
+
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ app.writeToParcel(data, 0);
+ mRemote.transact(SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION, data, null, 0);
+ data.recycle();
+ }
+
public final void scheduleCreateService(IBinder token, ServiceInfo info)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -543,7 +592,8 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void bindApplication(String packageName, ApplicationInfo info,
List providers, ComponentName testName,
String profileName, Bundle testArgs, IInstrumentationWatcher testWatcher, int debugMode,
- Configuration config, Map services) throws RemoteException {
+ boolean restrictedBackupMode, Configuration config,
+ Map services) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeString(packageName);
@@ -559,6 +609,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeBundle(testArgs);
data.writeStrongInterface(testWatcher);
data.writeInt(debugMode);
+ data.writeInt(restrictedBackupMode ? 1 : 0);
config.writeToParcel(data, 0);
data.writeMap(services);
mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null,
@@ -663,14 +714,30 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
- public void profilerControl(boolean start, String path) throws RemoteException {
+ public void profilerControl(boolean start, String path,
+ ParcelFileDescriptor fd) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeInt(start ? 1 : 0);
data.writeString(path);
+ if (fd != null) {
+ data.writeInt(1);
+ fd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(PROFILER_CONTROL_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
+
+ public void setSchedulingGroup(int group) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ data.writeInt(group);
+ mRemote.transact(SET_SCHEDULING_GROUP_TRANSACTION, data, null,
+ IBinder.FLAG_ONEWAY);
+ data.recycle();
+ }
}
diff --git a/core/java/android/backup/BackupService.java b/core/java/android/app/BackupAgent.java
similarity index 61%
rename from core/java/android/backup/BackupService.java
rename to core/java/android/app/BackupAgent.java
index 50a5921c0421e4449a12cd8468ecfd5e5765a5de..0ac8a1e4cfdc390ab45a90917bdb66bdbf108fbf 100644
--- a/core/java/android/backup/BackupService.java
+++ b/core/java/android/app/BackupAgent.java
@@ -14,47 +14,38 @@
* limitations under the License.
*/
-package android.backup;
+package android.app;
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.app.Service;
-import android.backup.IBackupService;
-import android.content.Intent;
+import android.app.IBackupAgent;
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.content.Context;
+import android.content.ContextWrapper;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
+import java.io.IOException;
+
/**
* This is the central interface between an application and Android's
* settings backup mechanism.
*
- * In order to use the backup service, your application must implement a
- * subclass of BackupService, and declare an intent filter
- * in the application manifest specifying that your BackupService subclass
- * handles the {@link BackupService#SERVICE_ACTION} intent action. For example:
- *
- *
- * <!-- Use the class "MyBackupService" to perform backups for my app -->
- * <service android:name=".MyBackupService">
- * <intent-filter>
- * <action android:name="android.backup.BackupService.SERVICE" />
- * </intent-filter>
- * </service>
- *
* @hide pending API solidification
*/
+public abstract class BackupAgent extends ContextWrapper {
+ private static final String TAG = "BackupAgent";
-public abstract class BackupService extends Service {
- /**
- * Service Action: Participate in the backup infrastructure. Applications
- * that wish to use the Android backup mechanism must provide an exported
- * subclass of BackupService and give it an {@link android.content.IntentFilter
- * IntentFilter} that accepts this action.
- */
- @SdkConstant(SdkConstantType.SERVICE_ACTION)
- public static final String SERVICE_ACTION = "android.backup.BackupService.SERVICE";
+ public BackupAgent() {
+ super(null);
+ }
+
+ public void onCreate() {
+ }
+
+ public void onDestroy() {
+ }
/**
* The application is being asked to write any data changed since the
@@ -76,7 +67,7 @@ public abstract class BackupService extends Service {
* here after writing the requested data to dataFd.
*/
public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState);
+ ParcelFileDescriptor newState) throws IOException;
/**
* The application is being restored from backup, and should replace any
@@ -87,11 +78,17 @@ public abstract class BackupService extends Service {
*
* @param data An open, read-only ParcelFileDescriptor pointing to a full snapshot
* of the application's data.
+ * @param appVersionCode The android:versionCode value of the application that backed
+ * up this particular data set. This makes it easier for an application's
+ * agent to distinguish among several possible older data versions when
+ * asked to perform the restore operation.
* @param newState An open, read/write ParcelFileDescriptor pointing to an empty
* file. The application should record the final backup state
* here after restoring its data from dataFd.
*/
- public abstract void onRestore(ParcelFileDescriptor /* TODO: BackupDataInput */ data, ParcelFileDescriptor newState);
+ public abstract void onRestore(BackupDataInput data, int appVersionCode,
+ ParcelFileDescriptor newState)
+ throws IOException;
// ----- Core implementation -----
@@ -100,38 +97,52 @@ public abstract class BackupService extends Service {
* Returns the private interface called by the backup system. Applications will
* not typically override this.
*/
- public IBinder onBind(Intent intent) {
- if (intent.getAction().equals(SERVICE_ACTION)) {
- return mBinder;
- }
- return null;
+ public IBinder onBind() {
+ return mBinder;
}
private final IBinder mBinder = new BackupServiceBinder().asBinder();
+ /** @hide */
+ public void attach(Context context) {
+ attachBaseContext(context);
+ }
+
// ----- IBackupService binder interface -----
- private class BackupServiceBinder extends IBackupService.Stub {
+ private class BackupServiceBinder extends IBackupAgent.Stub {
+ private static final String TAG = "BackupServiceBinder";
+
public void doBackup(ParcelFileDescriptor oldState,
ParcelFileDescriptor data,
ParcelFileDescriptor newState) throws RemoteException {
// !!! TODO - real implementation; for now just invoke the callbacks directly
- Log.v("BackupServiceBinder", "doBackup() invoked");
- BackupDataOutput output = new BackupDataOutput(BackupService.this,
- data.getFileDescriptor());
+ Log.v(TAG, "doBackup() invoked");
+ BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
try {
- BackupService.this.onBackup(oldState, output, newState);
+ BackupAgent.this.onBackup(oldState, output, newState);
+ } catch (IOException ex) {
+ Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw new RuntimeException(ex);
} catch (RuntimeException ex) {
- Log.d("BackupService", "onBackup ("
- + BackupService.this.getClass().getName() + ") threw", ex);
+ Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
}
}
- public void doRestore(ParcelFileDescriptor data,
+ public void doRestore(ParcelFileDescriptor data, int appVersionCode,
ParcelFileDescriptor newState) throws RemoteException {
// !!! TODO - real implementation; for now just invoke the callbacks directly
- Log.v("BackupServiceBinder", "doRestore() invoked");
- BackupService.this.onRestore(data, newState);
+ Log.v(TAG, "doRestore() invoked");
+ BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
+ try {
+ BackupAgent.this.onRestore(input, appVersionCode, newState);
+ } catch (IOException ex) {
+ Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw new RuntimeException(ex);
+ } catch (RuntimeException ex) {
+ Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw ex;
+ }
}
}
}
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 863cbcc4d11302a6ddcee5514d597dcd689eaa18..78bbb4f42c19ad24cbaa656b9fd8f645a6a570cd 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -46,7 +46,6 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
private final DatePicker mDatePicker;
private final OnDateSetListener mCallBack;
private final Calendar mCalendar;
- private final java.text.DateFormat mDateFormat;
private final java.text.DateFormat mTitleDateFormat;
private final String[] mWeekDays;
@@ -108,7 +107,6 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
DateFormatSymbols symbols = new DateFormatSymbols();
mWeekDays = symbols.getShortWeekdays();
- mDateFormat = DateFormat.getMediumDateFormat(context);
mTitleDateFormat = java.text.DateFormat.
getDateInstance(java.text.DateFormat.FULL);
mCalendar = Calendar.getInstance();
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index b09a57fb27a524b8e6ed5ae885d979e826d4cccc..222fe75fb3a27a24df466533b807227f2d89c4ad 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,32 +16,34 @@
package android.app;
+import com.android.internal.policy.PolicyManager;
+
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.os.Bundle;
import android.util.Config;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.LayoutInflater;
import android.view.Window;
import android.view.WindowManager;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
-
-import com.android.internal.policy.PolicyManager;
+import android.view.ViewGroup.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
import java.lang.ref.WeakReference;
@@ -81,6 +83,7 @@ public class Dialog implements DialogInterface, Window.Callback,
* {@hide}
*/
protected boolean mCancelable = true;
+
private Message mCancelMessage;
private Message mDismissMessage;
@@ -209,7 +212,9 @@ public class Dialog implements DialogInterface, Window.Callback,
if (mShowing) {
if (Config.LOGV) Log.v(LOG_TAG,
"[Dialog] start: already showing, ignore");
- if (mDecor != null) mDecor.setVisibility(View.VISIBLE);
+ if (mDecor != null) {
+ mDecor.setVisibility(View.VISIBLE);
+ }
return;
}
@@ -236,7 +241,9 @@ public class Dialog implements DialogInterface, Window.Callback,
* Hide the dialog, but do not dismiss it.
*/
public void hide() {
- if (mDecor != null) mDecor.setVisibility(View.GONE);
+ if (mDecor != null) {
+ mDecor.setVisibility(View.GONE);
+ }
}
/**
@@ -266,6 +273,7 @@ public class Dialog implements DialogInterface, Window.Callback,
}
mWindowManager.removeView(mDecor);
+
mDecor = null;
mWindow.closeAllPanels();
onStop();
@@ -280,7 +288,7 @@ public class Dialog implements DialogInterface, Window.Callback,
Message.obtain(mDismissMessage).sendToTarget();
}
}
-
+
// internal method to make sure mcreated is set properly without requiring
// users to call through to super in onCreate
void dispatchOnCreate(Bundle savedInstanceState) {
@@ -608,6 +616,18 @@ public class Dialog implements DialogInterface, Window.Callback,
return onTrackballEvent(ev);
}
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(getClass().getName());
+ event.setPackageName(mContext.getPackageName());
+
+ LayoutParams params = getWindow().getAttributes();
+ boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
+ (params.height == LayoutParams.FILL_PARENT);
+ event.setFullScreen(isFullScreen);
+
+ return false;
+ }
+
/**
* @see Activity#onCreatePanelView(int)
*/
diff --git a/core/java/android/app/FullBackupAgent.java b/core/java/android/app/FullBackupAgent.java
new file mode 100644
index 0000000000000000000000000000000000000000..d89db96674737c21f005ab59bd14cacc95d649ea
--- /dev/null
+++ b/core/java/android/app/FullBackupAgent.java
@@ -0,0 +1,58 @@
+package android.app;
+
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.backup.FileBackupHelper;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * Backs up an application's entire /data/data/<package>/... file system. This
+ * class is used by the desktop full backup mechanism and is not intended for direct
+ * use by applications.
+ *
+ * {@hide}
+ */
+
+public class FullBackupAgent extends BackupAgent {
+ // !!! TODO: turn off debugging
+ private static final String TAG = "FullBackupAgent";
+ private static final boolean DEBUG = true;
+
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ LinkedList dirsToScan = new LinkedList();
+ ArrayList allFiles = new ArrayList();
+
+ // build the list of files in the app's /data/data tree
+ dirsToScan.add(getFilesDir());
+ if (DEBUG) Log.v(TAG, "Backing up dir tree @ " + getFilesDir().getAbsolutePath() + " :");
+ while (dirsToScan.size() > 0) {
+ File dir = dirsToScan.removeFirst();
+ File[] contents = dir.listFiles();
+ if (contents != null) {
+ for (File f : contents) {
+ if (f.isDirectory()) {
+ dirsToScan.add(f);
+ } else if (f.isFile()) {
+ if (DEBUG) Log.v(TAG, " " + f.getAbsolutePath());
+ allFiles.add(f.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ // That's the file set; now back it all up
+ FileBackupHelper helper = new FileBackupHelper(this, (String[])allFiles.toArray());
+ helper.performBackup(oldState, data, newState);
+ }
+
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
+ }
+}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 56b29c1c941fd2a056638de220b68974faed0053..3ec7938b5a10a276978760cb9a7a70c2f31c39d3 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -21,6 +21,9 @@ import android.content.ContentProviderNative;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IIntentSender;
+import android.content.IIntentReceiver;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.ProviderInfo;
@@ -44,9 +47,30 @@ import java.util.List;
* {@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;
@@ -128,6 +152,11 @@ public interface IActivityManager extends IInterface {
public void serviceDoneExecuting(IBinder token) throws RemoteException;
public IBinder peekService(Intent service, String resolvedType) throws RemoteException;
+ public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode)
+ throws RemoteException;
+ public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException;
+ public void unbindBackupAgent(ApplicationInfo appInfo) throws RemoteException;
+
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher)
throws RemoteException;
@@ -221,10 +250,13 @@ public interface IActivityManager extends IInterface {
// Turn on/off profiling in a particular process.
public boolean profileControl(String process, boolean start,
- String path) throws RemoteException;
+ String path, ParcelFileDescriptor fd) throws RemoteException;
public boolean shutdown(int timeout) throws RemoteException;
+ public void stopAppSwitches() throws RemoteException;
+ public void resumeAppSwitches() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -371,4 +403,9 @@ public interface IActivityManager extends IInterface {
int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84;
int PROFILE_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+85;
int SHUTDOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+86;
+ int STOP_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+87;
+ int RESUME_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+88;
+ 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;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 9f3534b0924acf594a16ae20e806d7932ee45ae9..c0bc2a03ac91d9ad55b3634e0b7f22b67a7de971 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -18,12 +18,14 @@ package android.app;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IIntentReceiver;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
@@ -59,6 +61,11 @@ public interface IApplicationThread extends IInterface {
int configChanges) throws RemoteException;
void scheduleReceiver(Intent intent, ActivityInfo info, int resultCode,
String data, Bundle extras, boolean sync) throws RemoteException;
+ static final int BACKUP_MODE_INCREMENTAL = 0;
+ static final int BACKUP_MODE_FULL = 1;
+ static final int BACKUP_MODE_RESTORE = 2;
+ void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) throws RemoteException;
+ void scheduleDestroyBackupAgent(ApplicationInfo app) throws RemoteException;
void scheduleCreateService(IBinder token, ServiceInfo info) throws RemoteException;
void scheduleBindService(IBinder token,
Intent intent, boolean rebind) throws RemoteException;
@@ -71,8 +78,8 @@ public interface IApplicationThread extends IInterface {
static final int DEBUG_WAIT = 2;
void bindApplication(String packageName, ApplicationInfo info, List providers,
ComponentName testName, String profileName, Bundle testArguments,
- IInstrumentationWatcher testWatcher, int debugMode, Configuration config, Map services) throws RemoteException;
+ IInstrumentationWatcher testWatcher, int debugMode, boolean restrictedBackupMode,
+ Configuration config, Map services) throws RemoteException;
void scheduleExit() throws RemoteException;
void requestThumbnail(IBinder token) throws RemoteException;
void scheduleConfigurationChanged(Configuration config) throws RemoteException;
@@ -86,8 +93,10 @@ public interface IApplicationThread extends IInterface {
void scheduleLowMemory() throws RemoteException;
void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException;
void requestPss() throws RemoteException;
- void profilerControl(boolean start, String path) throws RemoteException;
-
+ void profilerControl(boolean start, String path, ParcelFileDescriptor fd)
+ throws RemoteException;
+ void setSchedulingGroup(int group) throws RemoteException;
+
String descriptor = "android.app.IApplicationThread";
int SCHEDULE_PAUSE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
@@ -117,4 +126,7 @@ public interface IApplicationThread extends IInterface {
int SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25;
int REQUEST_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26;
int PROFILER_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27;
+ int SET_SCHEDULING_GROUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28;
+ int SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29;
+ int SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30;
}
diff --git a/core/java/android/backup/IBackupService.aidl b/core/java/android/app/IBackupAgent.aidl
similarity index 82%
rename from core/java/android/backup/IBackupService.aidl
rename to core/java/android/app/IBackupAgent.aidl
index 1bde8eac9bdb175c884ecd323cff23de6b1c1b82..9b0550fce64b1f5a6aec16c9f64902e40e3522f7 100644
--- a/core/java/android/backup/IBackupService.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package android.backup;
+package android.app;
import android.os.ParcelFileDescriptor;
/**
* Interface presented by applications being asked to participate in the
* backup & restore mechanism. End user code does not typically implement
- * this interface; they subclass BackupService instead.
+ * this interface; they subclass BackupAgent instead.
*
* {@hide}
*/
-interface IBackupService {
+interface IBackupAgent {
/**
* Request that the app perform an incremental backup.
*
@@ -51,9 +51,14 @@ interface IBackupService {
* app's backup. This is to be a replacement of the app's
* current data, not to be merged into it.
*
+ * @param appVersionCode The android:versionCode attribute of the application
+ * that created this data set. This can help the agent distinguish among
+ * various historical backup content possibilities.
+ *
* @param newState Read-write file, empty when onRestore() is called,
* that is to be written with the state description that holds after
* the restore has been completed.
*/
- void doRestore(in ParcelFileDescriptor data, in ParcelFileDescriptor newState);
+ void doRestore(in ParcelFileDescriptor data, int appVersionCode,
+ in ParcelFileDescriptor newState);
}
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index 39eb4f1ccbe863438dfceec9fca2d4ce7c3dfb8b..e8bd60a4d86fabc1f50800533a6eeaaa1078483d 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -16,11 +16,28 @@
package android.app;
+import android.app.ISearchManagerCallback;
import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Bundle;
import android.server.search.SearchableInfo;
/** @hide */
interface ISearchManager {
SearchableInfo getSearchableInfo(in ComponentName launchActivity, boolean globalSearch);
List getSearchablesInGlobalSearch();
+ List getSearchablesForWebSearch();
+ SearchableInfo getDefaultSearchableForWebSearch();
+ void setDefaultWebSearch(in ComponentName component);
+ void startSearch(in String initialQuery,
+ boolean selectInitialQuery,
+ in ComponentName launchActivity,
+ in Bundle appSearchData,
+ boolean globalSearch,
+ ISearchManagerCallback searchManagerCallback);
+ void stopSearch();
+ boolean isVisible();
+ Bundle onSaveInstanceState();
+ void onRestoreInstanceState(in Bundle savedInstanceState);
+ void onConfigurationChanged(in Configuration newConfig);
}
diff --git a/core/java/android/app/ISearchManagerCallback.aidl b/core/java/android/app/ISearchManagerCallback.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..bdfb2bad7ab4d2b563e1dde70c6217341053e86c
--- /dev/null
+++ b/core/java/android/app/ISearchManagerCallback.aidl
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2009, 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;
+
+/** @hide */
+oneway interface ISearchManagerCallback {
+ void onDismiss();
+ void onCancel();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index f6a28b23a3a89b362864751d8ebbea08dadfc20e..e31f4f834404d9562e6cab0e3757cda156b844e7 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -446,13 +446,13 @@ public class Instrumentation {
if (ai == null) {
throw new RuntimeException("Unable to resolve activity for: " + intent);
}
- if (!ai.applicationInfo.processName.equals(
- getTargetContext().getPackageName())) {
+ String myProc = mThread.getProcessName();
+ if (!ai.processName.equals(myProc)) {
// todo: if this intent is ambiguous, look here to see if
// there is a single match that is in our package.
- throw new RuntimeException("Intent resolved to different package "
- + ai.applicationInfo.packageName + ": "
- + intent);
+ throw new RuntimeException("Intent in process "
+ + myProc + " resolved to different process "
+ + ai.processName + ": " + intent);
}
intent.setComponent(new ComponentName(
diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java
index 8d249da960bdbbe1fac1f5c437958e65ac3d0dfb..accdda9ba599b1b73ef54ba59816c8631c5504d0 100644
--- a/core/java/android/app/LauncherActivity.java
+++ b/core/java/android/app/LauncherActivity.java
@@ -60,26 +60,20 @@ public abstract class LauncherActivity extends ListActivity {
* An item in the list
*/
public static class ListItem {
+ public ResolveInfo resolveInfo;
public CharSequence label;
- //public CharSequence description;
public Drawable icon;
public String packageName;
public String className;
public Bundle extras;
ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer) {
+ this.resolveInfo = resolveInfo;
label = resolveInfo.loadLabel(pm);
if (label == null && resolveInfo.activityInfo != null) {
label = resolveInfo.activityInfo.name;
}
- /*
- if (resolveInfo.activityInfo != null &&
- resolveInfo.activityInfo.applicationInfo != null) {
- description = resolveInfo.activityInfo.applicationInfo.loadDescription(pm);
- }
- */
-
icon = resizer.createIconThumbnail(resolveInfo.loadIcon(pm));
packageName = resolveInfo.activityInfo.applicationInfo.packageName;
className = resolveInfo.activityInfo.name;
@@ -122,6 +116,14 @@ public abstract class LauncherActivity extends ListActivity {
return intent;
}
+ public ListItem itemForPosition(int position) {
+ if (mActivitiesList == null) {
+ return null;
+ }
+
+ return mActivitiesList.get(position);
+ }
+
public int getCount() {
return mActivitiesList != null ? mActivitiesList.size() : 0;
}
@@ -353,6 +355,16 @@ public abstract class LauncherActivity extends ListActivity {
return adapter.intentForPosition(position);
}
+ /**
+ * Return the {@link ListItem} for a specific position in our
+ * {@link android.widget.ListView}.
+ * @param position The item to return
+ */
+ protected ListItem itemForPosition(int position) {
+ ActivityAdapter adapter = (ActivityAdapter) mAdapter;
+ return adapter.itemForPosition(position);
+ }
+
/**
* Get the base intent to use when running
* {@link PackageManager#queryIntentActivities(Intent, int)}.
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index cb660c7fde6f55540ceec1f2a1424d0f2001ec87..f9c38f998c35ad0cccd1ffb4d49bbbc2add81709 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -18,6 +18,9 @@ package android.app;
import android.content.Context;
import android.content.Intent;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.IntentSender;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.Handler;
@@ -105,7 +108,7 @@ public final class PendingIntent implements Parcelable {
public CanceledException(Exception cause) {
super(cause);
}
- };
+ }
/**
* Callback interface for discovering when a send operation has
@@ -270,6 +273,21 @@ public final class PendingIntent implements Parcelable {
return null;
}
+ private class IntentSenderWrapper extends IntentSender {
+ protected IntentSenderWrapper(IIntentSender target) {
+ super(target);
+ }
+ }
+ /**
+ * Retrieve a IntentSender object that wraps the existing sender of the PendingIntent
+ *
+ * @return Returns a IntentSender object that wraps the sender of PendingIntent
+ *
+ */
+ public IntentSender getIntentSender() {
+ return new IntentSenderWrapper(mTarget);
+ }
+
/**
* Cancel a currently active PendingIntent. Only the original application
* owning an PendingIntent can cancel it.
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 343380cc766e63651b85112700f51baa68dfc49a..fdb619ae2da6ed5634149e649ec8ff925cb5c817 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -24,6 +24,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -32,6 +34,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
@@ -41,8 +44,10 @@ import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
+import android.text.util.Regex;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -51,6 +56,7 @@ import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
@@ -67,8 +73,8 @@ import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
- * In-application-process implementation of Search Bar. This is still controlled by the
- * SearchManager, but it runs in the current activity's process to keep things lighter weight.
+ * System search dialog. This is controlled by the
+ * SearchManagerService and runs in the system process.
*
* @hide
*/
@@ -82,13 +88,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private static final String INSTANCE_KEY_COMPONENT = "comp";
private static final String INSTANCE_KEY_APPDATA = "data";
private static final String INSTANCE_KEY_GLOBALSEARCH = "glob";
- private static final String INSTANCE_KEY_DISPLAY_QUERY = "dQry";
- private static final String INSTANCE_KEY_DISPLAY_SEL_START = "sel1";
- private static final String INSTANCE_KEY_DISPLAY_SEL_END = "sel2";
- private static final String INSTANCE_KEY_SELECTED_ELEMENT = "slEl";
- private static final int INSTANCE_SELECTED_BUTTON = -2;
- private static final int INSTANCE_SELECTED_QUERY = -1;
-
+ private static final String INSTANCE_KEY_STORED_COMPONENT = "sComp";
+ private static final String INSTANCE_KEY_STORED_APPDATA = "sData";
+ private static final String INSTANCE_KEY_PREVIOUS_COMPONENTS = "sPrev";
+ private static final String INSTANCE_KEY_USER_QUERY = "uQry";
+
private static final int SEARCH_PLATE_LEFT_PADDING_GLOBAL = 12;
private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7;
@@ -103,6 +107,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private Button mGoButton;
private ImageButton mVoiceButton;
private View mSearchPlate;
+ private Drawable mWorkingSpinner;
// interaction with searchable application
private SearchableInfo mSearchable;
@@ -129,9 +134,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private SuggestionsAdapter mSuggestionsAdapter;
// Whether to rewrite queries when selecting suggestions
- // TODO: This is disabled because of problems with persistent selections
- // causing non-user-initiated rewrites.
- private static final boolean REWRITE_QUERIES = false;
+ private static final boolean REWRITE_QUERIES = true;
// The query entered by the user. This is not changed when selecting a suggestion
// that modifies the contents of the text field. But if the user then edits
@@ -142,14 +145,17 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// more than once.
private final WeakHashMap mOutsideDrawablesCache =
new WeakHashMap();
-
+
+ // Last known IME options value for the search edit text.
+ private int mSearchAutoCompleteImeOptions;
+
/**
* Constructor - fires it up and makes it look like the search UI.
*
* @param context Application Context we can use for system acess
*/
public SearchDialog(Context context) {
- super(context, com.android.internal.R.style.Theme_SearchBar);
+ super(context, com.android.internal.R.style.Theme_GlobalSearchBar);
}
/**
@@ -160,17 +166,17 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Window theWindow = getWindow();
- theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL);
-
setContentView(com.android.internal.R.layout.search_bar);
- theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT,
- // taking up the whole window (even when transparent) is less than ideal,
- // but necessary to show the popup window until the window manager supports
- // having windows anchored by their parent but not clipped by them.
- ViewGroup.LayoutParams.FILL_PARENT);
+ Window theWindow = getWindow();
WindowManager.LayoutParams lp = theWindow.getAttributes();
+ lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+ lp.width = ViewGroup.LayoutParams.FILL_PARENT;
+ // taking up the whole window (even when transparent) is less than ideal,
+ // but necessary to show the popup window until the window manager supports
+ // having windows anchored by their parent but not clipped by them.
+ lp.height = ViewGroup.LayoutParams.FILL_PARENT;
+ lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
theWindow.setAttributes(lp);
@@ -182,6 +188,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
mSearchPlate = findViewById(com.android.internal.R.id.search_plate);
+ mWorkingSpinner = getContext().getResources().
+ getDrawable(com.android.internal.R.drawable.search_spinner);
// attach listeners
mSearchAutoComplete.addTextChangedListener(mTextWatcher);
@@ -213,10 +221,14 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// Save voice intent for later queries/launching
mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ mSearchAutoCompleteImeOptions = mSearchAutoComplete.getImeOptions();
}
/**
@@ -226,20 +238,21 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/
public boolean show(String initialQuery, boolean selectInitialQuery,
ComponentName componentName, Bundle appSearchData, boolean globalSearch) {
- if (isShowing()) {
- // race condition - already showing but not handling events yet.
- // in this case, just discard the "show" request
- return true;
- }
-
+
// Reset any stored values from last time dialog was shown.
mStoredComponentName = null;
mStoredAppSearchData = null;
-
- return doShow(initialQuery, selectInitialQuery, componentName, appSearchData, globalSearch);
+
+ boolean success = doShow(initialQuery, selectInitialQuery, componentName, appSearchData,
+ globalSearch);
+ if (success) {
+ // Display the drop down as soon as possible instead of waiting for the rest of the
+ // pending UI stuff to get done, so that things appear faster to the user.
+ mSearchAutoComplete.showDropDownAfterLayout();
+ }
+ return success;
}
-
/**
* Called in response to a press of the hard search button in
* {@link #onKeyDown(int, KeyEvent)}, this method toggles between in-app
@@ -309,15 +322,17 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
+ appSearchData + ", " + globalSearch + ")");
}
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
// Try to get the searchable info for the provided component (or for global search,
// if globalSearch == true).
- mSearchable = SearchManager.getSearchableInfo(componentName, globalSearch);
+ mSearchable = searchManager.getSearchableInfo(componentName, globalSearch);
// If we got back nothing, and it wasn't a request for global search, then try again
// for global search, as we'll try to launch that in lieu of any component-specific search.
if (!globalSearch && mSearchable == null) {
globalSearch = true;
- mSearchable = SearchManager.getSearchableInfo(componentName, globalSearch);
+ mSearchable = searchManager.getSearchableInfo(componentName, globalSearch);
// If we still get back null (i.e., there's not even a searchable info available
// for global search), then really give up.
@@ -332,7 +347,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mAppSearchData = appSearchData;
// Using globalSearch here is just an optimization, just calling
// isDefaultSearchable() should always give the same result.
- mGlobalSearchMode = globalSearch || SearchManager.isDefaultSearchable(mSearchable);
+ mGlobalSearchMode = globalSearch || searchManager.isDefaultSearchable(mSearchable);
mActivityContext = mSearchable.getActivityContext(getContext());
// show the dialog. this will call onStart().
@@ -345,6 +360,21 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.showSoftInputUnchecked(0, null);
}
+
+ // The Dialog uses a ContextThemeWrapper for the context; use this to change the
+ // theme out from underneath us, between the global search theme and the in-app
+ // search theme. They are identical except that the global search theme does not
+ // dim the background of the window (because global search is full screen so it's
+ // not needed and this should save a little bit of time on global search invocation).
+ Object context = getContext();
+ if (context instanceof ContextThemeWrapper) {
+ ContextThemeWrapper wrapper = (ContextThemeWrapper) context;
+ if (globalSearch) {
+ wrapper.setTheme(com.android.internal.R.style.Theme_GlobalSearchBar);
+ } else {
+ wrapper.setTheme(com.android.internal.R.style.Theme_SearchBar);
+ }
+ }
show();
}
@@ -372,11 +402,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
public void onStop() {
super.onStop();
- // TODO: Removing the listeners means that they never get called, since
- // Dialog.dismissDialog() calls onStop() before sendDismissMessage().
- setOnCancelListener(null);
- setOnDismissListener(null);
-
// stop receiving broadcasts (throws exception if none registered)
try {
getContext().unregisterReceiver(mBroadcastReceiver);
@@ -394,6 +419,24 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mUserQuery = null;
mPreviousComponents = null;
}
+
+ /**
+ * Sets the search dialog to the 'working' state, which shows a working spinner in the
+ * right hand size of the text field.
+ *
+ * @param working true to show spinner, false to hide spinner
+ */
+ public void setWorking(boolean working) {
+ if (working) {
+ mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
+ null, null, mWorkingSpinner, null);
+ ((Animatable) mWorkingSpinner).start();
+ } else {
+ mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
+ null, null, null, null);
+ ((Animatable) mWorkingSpinner).stop();
+ }
+ }
/**
* Closes and gets rid of the suggestions adapter.
@@ -412,8 +455,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
/**
* Save the minimal set of data necessary to recreate the search
*
- * TODO: go through this and make sure that it saves everything that is needed
- *
* @return A bundle with the state of the dialog.
*/
@Override
@@ -424,20 +465,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
bundle.putBoolean(INSTANCE_KEY_GLOBALSEARCH, mGlobalSearchMode);
-
- // UI state
- bundle.putString(INSTANCE_KEY_DISPLAY_QUERY, mSearchAutoComplete.getText().toString());
- bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_START, mSearchAutoComplete.getSelectionStart());
- bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_END, mSearchAutoComplete.getSelectionEnd());
-
- int selectedElement = INSTANCE_SELECTED_QUERY;
- if (mGoButton.isFocused()) {
- selectedElement = INSTANCE_SELECTED_BUTTON;
- } else if (mSearchAutoComplete.isPopupShowing()) {
- selectedElement = 0; // TODO mSearchTextField.getListSelection() // 0..n
- }
- bundle.putInt(INSTANCE_KEY_SELECTED_ELEMENT, selectedElement);
-
+ bundle.putParcelable(INSTANCE_KEY_STORED_COMPONENT, mStoredComponentName);
+ bundle.putBundle(INSTANCE_KEY_STORED_APPDATA, mStoredAppSearchData);
+ bundle.putParcelableArrayList(INSTANCE_KEY_PREVIOUS_COMPONENTS, mPreviousComponents);
+ bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
+
return bundle;
}
@@ -451,45 +483,27 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
- // Get the launch info
ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH);
-
- // get the UI state
- String displayQuery = savedInstanceState.getString(INSTANCE_KEY_DISPLAY_QUERY);
- int querySelStart = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_START, -1);
- int querySelEnd = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_END, -1);
- int selectedElement = savedInstanceState.getInt(INSTANCE_KEY_SELECTED_ELEMENT);
-
- // show the dialog. skip any show/hide animation, we want to go fast.
- // send the text that actually generates the suggestions here; we'll replace the display
- // text as necessary in a moment.
- if (!show(displayQuery, false, launchComponent, appSearchData, globalSearch)) {
+ ComponentName storedComponentName =
+ savedInstanceState.getParcelable(INSTANCE_KEY_STORED_COMPONENT);
+ Bundle storedAppSearchData =
+ savedInstanceState.getBundle(INSTANCE_KEY_STORED_APPDATA);
+ ArrayList previousComponents =
+ savedInstanceState.getParcelableArrayList(INSTANCE_KEY_PREVIOUS_COMPONENTS);
+ String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
+
+ // Set stored state
+ mStoredComponentName = storedComponentName;
+ mStoredAppSearchData = storedAppSearchData;
+ mPreviousComponents = previousComponents;
+
+ // show the dialog.
+ if (!doShow(userQuery, false, launchComponent, appSearchData, globalSearch)) {
// for some reason, we couldn't re-instantiate
return;
}
-
- mSearchAutoComplete.setText(displayQuery);
-
- // clean up the selection state
- switch (selectedElement) {
- case INSTANCE_SELECTED_BUTTON:
- mGoButton.setEnabled(true);
- mGoButton.setFocusable(true);
- mGoButton.requestFocus();
- break;
- case INSTANCE_SELECTED_QUERY:
- if (querySelStart >= 0 && querySelEnd >= 0) {
- mSearchAutoComplete.requestFocus();
- mSearchAutoComplete.setSelection(querySelStart, querySelEnd);
- }
- break;
- default:
- // TODO: defer selecting a list element until suggestion list appears
-// mSearchAutoComplete.setListSelection(selectedElement)
- break;
- }
}
/**
@@ -534,7 +548,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
}
mSearchAutoComplete.setInputType(inputType);
- mSearchAutoComplete.setImeOptions(mSearchable.getImeOptions());
+ mSearchAutoCompleteImeOptions = mSearchable.getImeOptions();
+ mSearchAutoComplete.setImeOptions(mSearchAutoCompleteImeOptions);
}
}
@@ -547,24 +562,20 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mSearchAutoComplete.setDropDownAnimationStyle(0); // no animation
mSearchAutoComplete.setThreshold(mSearchable.getSuggestThreshold());
+ // we dismiss the entire dialog instead
+ mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
if (mGlobalSearchMode) {
mSearchAutoComplete.setDropDownAlwaysVisible(true); // fill space until results come in
- mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
- mSearchAutoComplete.setDropDownBackgroundResource(
- com.android.internal.R.drawable.search_dropdown_background);
} else {
mSearchAutoComplete.setDropDownAlwaysVisible(false);
- mSearchAutoComplete.setDropDownDismissedOnCompletion(true);
- mSearchAutoComplete.setDropDownBackgroundResource(
- com.android.internal.R.drawable.search_dropdown_background_apps);
}
// attach the suggestions adapter, if suggestions are available
// The existence of a suggestions authority is the proxy for "suggestions available here"
if (mSearchable.getSuggestAuthority() != null) {
- mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable,
- mOutsideDrawablesCache);
+ mSuggestionsAdapter = new SuggestionsAdapter(getContext(), this, mSearchable,
+ mOutsideDrawablesCache, mGlobalSearchMode);
mSearchAutoComplete.setAdapter(mSuggestionsAdapter);
}
}
@@ -597,7 +608,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mSearchPlate.getPaddingBottom());
} else {
PackageManager pm = getContext().getPackageManager();
- Drawable icon = null;
+ Drawable icon;
try {
ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0);
icon = pm.getApplicationIcon(info.applicationInfo);
@@ -765,7 +776,24 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
}
- public void afterTextChanged(Editable s) { }
+ public void afterTextChanged(Editable s) {
+ if (!mSearchAutoComplete.isPerformingCompletion()) {
+ // The user changed the query, check if it is a URL and if so change the search
+ // button in the soft keyboard to the 'Go' button.
+ int options = (mSearchAutoComplete.getImeOptions() & (~EditorInfo.IME_MASK_ACTION));
+ if (Regex.WEB_URL_PATTERN.matcher(mUserQuery).matches()) {
+ options = options | EditorInfo.IME_ACTION_GO;
+ } else {
+ options = options | EditorInfo.IME_ACTION_SEARCH;
+ }
+ if (options != mSearchAutoCompleteImeOptions) {
+ mSearchAutoCompleteImeOptions = options;
+ mSearchAutoComplete.setImeOptions(options);
+ // This call is required to update the soft keyboard UI with latest IME flags.
+ mSearchAutoComplete.setInputType(mSearchAutoComplete.getInputType());
+ }
+ }
+ }
};
/**
@@ -902,6 +930,32 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
return voiceIntent;
}
+ /**
+ * Corrects http/https typo errors in the given url string, and if the protocol specifier was
+ * not present defaults to http.
+ *
+ * @param inUrl URL to check and fix
+ * @return fixed URL string.
+ */
+ private String fixUrl(String inUrl) {
+ if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
+ return inUrl;
+
+ if (inUrl.startsWith("http:") || inUrl.startsWith("https:")) {
+ if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
+ inUrl = inUrl.replaceFirst("/", "//");
+ } else {
+ inUrl = inUrl.replaceFirst(":", "://");
+ }
+ }
+
+ if (inUrl.indexOf("://") == -1) {
+ inUrl = "http://" + inUrl;
+ }
+
+ return inUrl;
+ }
+
/**
* React to the user typing "enter" or other hardwired keys while typing in the search box.
* This handles these special keys while the edit box has focus.
@@ -932,7 +986,19 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (keyCode == KeyEvent.KEYCODE_ENTER
&& event.getAction() == KeyEvent.ACTION_UP) {
v.cancelLongPress();
- launchQuerySearch();
+
+ // If this is a url entered by the user and we displayed the 'Go' button which
+ // the user clicked, launch the url instead of using it as a search query.
+ if ((mSearchAutoCompleteImeOptions & EditorInfo.IME_MASK_ACTION)
+ == EditorInfo.IME_ACTION_GO) {
+ Uri uri = Uri.parse(fixUrl(mSearchAutoComplete.getText().toString()));
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ launchIntent(intent);
+ } else {
+ // Launch as a regular search.
+ launchQuerySearch();
+ }
return true;
}
if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -1069,7 +1135,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/
protected void launchQuerySearch(int actionKey, String actionMsg) {
String query = mSearchAutoComplete.getText().toString();
- Intent intent = createIntent(Intent.ACTION_SEARCH, null, query, null,
+ Intent intent = createIntent(Intent.ACTION_SEARCH, null, null, query, null,
actionKey, actionMsg);
launchIntent(intent);
}
@@ -1097,15 +1163,121 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
protected boolean launchSuggestion(int position, int actionKey, String actionMsg) {
Cursor c = mSuggestionsAdapter.getCursor();
if ((c != null) && c.moveToPosition(position)) {
+
Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
+
+ // report back about the click
+ if (mGlobalSearchMode) {
+ // in global search mode, do it via cursor
+ mSuggestionsAdapter.callCursorOnClick(c, position);
+ } else if (intent != null
+ && mPreviousComponents != null
+ && !mPreviousComponents.isEmpty()) {
+ // in-app search (and we have pivoted in as told by mPreviousComponents,
+ // which is used for keeping track of what we pop back to when we are pivoting into
+ // in app search.)
+ reportInAppClickToGlobalSearch(c, intent);
+ }
+
+ // launch the intent
launchIntent(intent);
+
return true;
}
return false;
}
-
+
+ /**
+ * Report a click from an in app search result back to global search for shortcutting porpoises.
+ *
+ * @param c The cursor that is pointing to the clicked position.
+ * @param intent The intent that will be launched for the click.
+ */
+ private void reportInAppClickToGlobalSearch(Cursor c, Intent intent) {
+ // for in app search, still tell global search via content provider
+ Uri uri = getClickReportingUri();
+ final ContentValues cv = new ContentValues();
+ cv.put(SearchManager.SEARCH_CLICK_REPORT_COLUMN_QUERY, mUserQuery);
+ final ComponentName source = mSearchable.getSearchActivity();
+ cv.put(SearchManager.SEARCH_CLICK_REPORT_COLUMN_COMPONENT, source.flattenToShortString());
+
+ // grab the intent columns from the intent we created since it has additional
+ // logic for falling back on the searchable default
+ cv.put(SearchManager.SUGGEST_COLUMN_INTENT_ACTION, intent.getAction());
+ cv.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA, intent.getDataString());
+ cv.put(SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME,
+ intent.getStringExtra(SearchManager.COMPONENT_NAME_KEY));
+
+ // ensure the icons will work for global search
+ cv.put(SearchManager.SUGGEST_COLUMN_ICON_1,
+ wrapIconForPackage(
+ source,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_ICON_1)));
+ cv.put(SearchManager.SUGGEST_COLUMN_ICON_2,
+ wrapIconForPackage(
+ source,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_ICON_2)));
+
+ // the rest can be passed through directly
+ cv.put(SearchManager.SUGGEST_COLUMN_FORMAT,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_FORMAT));
+ cv.put(SearchManager.SUGGEST_COLUMN_TEXT_1,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_TEXT_1));
+ cv.put(SearchManager.SUGGEST_COLUMN_TEXT_2,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_TEXT_2));
+ cv.put(SearchManager.SUGGEST_COLUMN_QUERY,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY));
+ cv.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_SHORTCUT_ID));
+ // note: deliberately omitting background color since it is only for global search
+ // "more results" entries
+ mContext.getContentResolver().insert(uri, cv);
+ }
+
/**
- * Launches an intent. Also dismisses the search dialog if not in global search mode.
+ * @return A URI appropriate for reporting a click.
+ */
+ private Uri getClickReportingUri() {
+ Uri.Builder uriBuilder = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SearchManager.SEARCH_CLICK_REPORT_AUTHORITY);
+
+ uriBuilder.appendPath(SearchManager.SEARCH_CLICK_REPORT_URI_PATH);
+
+ return uriBuilder
+ .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
+ .fragment("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
+ .build();
+ }
+
+ /**
+ * Wraps an icon for a particular package. If the icon is a resource id, it is converted into
+ * an android.resource:// URI.
+ *
+ * @param source The source of the icon
+ * @param icon The icon retrieved from a suggestion column
+ * @return An icon string appropriate for the package.
+ */
+ private String wrapIconForPackage(ComponentName source, String icon) {
+ if (icon == null || icon.length() == 0 || "0".equals(icon)) {
+ // SearchManager specifies that null or zero can be returned to indicate
+ // no icon. We also allow empty string.
+ return null;
+ } else if (!Character.isDigit(icon.charAt(0))){
+ return icon;
+ } else {
+ String packageName = source.getPackageName();
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(packageName)
+ .encodedPath(icon)
+ .toString();
+ }
+ }
+
+ /**
+ * Launches an intent and dismisses the search dialog (unless the intent
+ * is one of the special intents that modifies the state of the search dialog).
*/
private void launchIntent(Intent intent) {
if (intent == null) {
@@ -1114,9 +1286,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (handleSpecialIntent(intent)){
return;
}
- if (!mGlobalSearchMode) {
- dismiss();
- }
+ dismiss();
getContext().startActivity(intent);
}
@@ -1130,15 +1300,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (SearchManager.INTENT_ACTION_CHANGE_SEARCH_SOURCE.equals(action)) {
handleChangeSourceIntent(intent);
return true;
- } else if (SearchManager.INTENT_ACTION_CURSOR_RESPOND.equals(action)) {
- handleCursorRespondIntent(intent);
- return true;
}
return false;
}
/**
- * Handles SearchManager#INTENT_ACTION_CHANGE_SOURCE.
+ * Handles {@link SearchManager#INTENT_ACTION_CHANGE_SEARCH_SOURCE}.
*/
private void handleChangeSourceIntent(Intent intent) {
Uri dataUri = intent.getData();
@@ -1162,18 +1329,16 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
String query = intent.getStringExtra(SearchManager.QUERY);
setUserQuery(query);
+ mSearchAutoComplete.showDropDown();
}
-
+
/**
- * Handles {@link SearchManager#INTENT_ACTION_CURSOR_RESPOND}.
+ * Sets the list item selection in the AutoCompleteTextView's ListView.
*/
- private void handleCursorRespondIntent(Intent intent) {
- Cursor c = mSuggestionsAdapter.getCursor();
- if (c != null) {
- c.respond(intent.getExtras());
- }
+ public void setListSelection(int index) {
+ mSearchAutoComplete.setListSelection(index);
}
-
+
/**
* Saves the previous component that was searched, so that we can go
* back to it.
@@ -1243,6 +1408,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
try {
// use specific action if supplied, or default action if supplied, or fixed default
String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
+
+ // some items are display only, or have effect via the cursor respond click reporting.
+ if (SearchManager.INTENT_ACTION_NONE.equals(action)) {
+ return null;
+ }
+
if (action == null) {
action = mSearchable.getSuggestIntentAction();
}
@@ -1264,11 +1435,14 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
Uri dataUri = (data == null) ? null : Uri.parse(data);
- String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
-
+ String componentName = getColumnString(
+ c, SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME);
+
String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
+ String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
- return createIntent(action, dataUri, query, extraData, actionKey, actionMsg);
+ return createIntent(action, dataUri, extraData, query, componentName, actionKey,
+ actionMsg);
} catch (RuntimeException e ) {
int rowNum;
try { // be really paranoid now
@@ -1287,27 +1461,33 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*
* @param action Intent action.
* @param data Intent data, or null
.
- * @param query Intent query, or null
.
* @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or null
.
+ * @param query Intent query, or null
.
+ * @param componentName Data for {@link SearchManager#COMPONENT_NAME_KEY} or null
.
* @param actionKey The key code of the action key that was pressed,
* or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
* @param actionMsg The message for the action key that was pressed,
* or null
if none.
* @return The intent.
*/
- private Intent createIntent(String action, Uri data, String query, String extraData,
- int actionKey, String actionMsg) {
+ private Intent createIntent(String action, Uri data, String extraData, String query,
+ String componentName, int actionKey, String actionMsg) {
// Now build the Intent
Intent intent = new Intent(action);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (data != null) {
intent.setData(data);
}
+ intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
if (query != null) {
intent.putExtra(SearchManager.QUERY, query);
}
if (extraData != null) {
intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
}
+ if (componentName != null) {
+ intent.putExtra(SearchManager.COMPONENT_NAME_KEY, componentName);
+ }
if (mAppSearchData != null) {
intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
}
@@ -1383,20 +1563,22 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private boolean isEmpty() {
return TextUtils.getTrimmedLength(getText()) == 0;
}
-
+
/**
- * Clears the entered text.
+ * We override this method to avoid replacing the query box text
+ * when a suggestion is clicked.
*/
- private void clear() {
- setText("");
+ @Override
+ protected void replaceText(CharSequence text) {
}
/**
- * We override this method to avoid replacing the query box text
- * when a suggestion is clicked.
+ * We override this method to avoid an extra onItemClick being called on the
+ * drop-down's OnItemClickListener by {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)}
+ * when an item is clicked with the trackball.
*/
@Override
- protected void replaceText(CharSequence text) {
+ public void performCompletion() {
}
/**
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 3bf37c3b2b0169e7bc5db227d0fd3a07fbd1772c..e5ba6a4a03c7e409e6ffa563ddd40b721c2b528a 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.server.search.SearchableInfo;
+import android.util.Log;
import android.view.KeyEvent;
import java.util.List;
@@ -1108,6 +1109,10 @@ import java.util.List;
public class SearchManager
implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
{
+
+ private static final boolean DBG = false;
+ private static final String TAG = "SearchManager";
+
/**
* This is a shortcut definition for the default menu key to use for invoking search.
*
@@ -1130,6 +1135,20 @@ public class SearchManager
*/
public final static String QUERY = "query";
+ /**
+ * Intent extra data key: Use this key with
+ * {@link android.content.Intent#getStringExtra
+ * content.Intent.getStringExtra()}
+ * to obtain the query string typed in by the user.
+ * This may be different from the value of {@link #QUERY}
+ * if the intent is the result of selecting a suggestion.
+ * In that case, {@link #QUERY} will contain the value of
+ * {@link #SUGGEST_COLUMN_QUERY} for the suggestion, and
+ * {@link #USER_QUERY} will contain the string typed by the
+ * user.
+ */
+ public final static String USER_QUERY = "user_query";
+
/**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
* {@link android.content.Intent#getBundleExtra
@@ -1148,7 +1167,7 @@ public class SearchManager
* @hide
*/
public final static String SOURCE = "source";
-
+
/**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
* {@link android.content.Intent#getIntExtra content.Intent.getIntExtra()}
@@ -1159,13 +1178,67 @@ public class SearchManager
*/
public final static String ACTION_KEY = "action_key";
+ /**
+ * Intent component name key: This key will be used for the extra populated by the
+ * {@link #SUGGEST_COLUMN_INTENT_COMPONENT_NAME} column.
+ *
+ * {@hide}
+ */
+ public final static String COMPONENT_NAME_KEY = "intent_component_name_key";
+
/**
* Intent extra data key: This key will be used for the extra populated by the
* {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column.
+ *
* {@hide}
*/
public final static String EXTRA_DATA_KEY = "intent_extra_data_key";
-
+
+ /**
+ * Defines the constants used in the communication between {@link android.app.SearchDialog} and
+ * the global search provider via {@link Cursor#respond(android.os.Bundle)}.
+ *
+ * @hide
+ */
+ public static class DialogCursorProtocol {
+
+ /**
+ * The sent bundle will contain this integer key, with a value set to one of the events
+ * below.
+ */
+ public final static String METHOD = "DialogCursorProtocol.method";
+
+ /**
+ * After data has been refreshed.
+ */
+ public final static int POST_REFRESH = 0;
+ public final static String POST_REFRESH_RECEIVE_ISPENDING
+ = "DialogCursorProtocol.POST_REFRESH.isPending";
+ public final static String POST_REFRESH_RECEIVE_DISPLAY_NOTIFY
+ = "DialogCursorProtocol.POST_REFRESH.displayNotify";
+
+ /**
+ * Just before closing the cursor.
+ */
+ public final static int PRE_CLOSE = 1;
+ public final static String PRE_CLOSE_SEND_MAX_DISPLAY_POS
+ = "DialogCursorProtocol.PRE_CLOSE.sendDisplayPosition";
+
+ /**
+ * When a position has been clicked.
+ */
+ public final static int CLICK = 2;
+ public final static String CLICK_SEND_POSITION
+ = "DialogCursorProtocol.CLICK.sendPosition";
+ public final static String CLICK_RECEIVE_SELECTED_POS
+ = "DialogCursorProtocol.CLICK.receiveSelectedPosition";
+
+ /**
+ * When the threshold received in {@link #POST_REFRESH_RECEIVE_DISPLAY_NOTIFY} is displayed.
+ */
+ public final static int THRESH_HIT = 3;
+ }
+
/**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
* {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()}
@@ -1210,6 +1283,41 @@ public class SearchManager
*/
public final static String SHORTCUT_MIME_TYPE =
"vnd.android.cursor.item/vnd.android.search.suggest";
+
+
+ /**
+ * The authority of the provider to report clicks to when a click is detected after pivoting
+ * into a specific app's search from global search.
+ *
+ * In addition to the columns below, the suggestion columns are used to pass along the full
+ * suggestion so it can be shortcutted.
+ *
+ * @hide
+ */
+ public final static String SEARCH_CLICK_REPORT_AUTHORITY =
+ "com.android.globalsearch.stats";
+
+ /**
+ * The path the write goes to.
+ *
+ * @hide
+ */
+ public final static String SEARCH_CLICK_REPORT_URI_PATH = "click";
+
+ /**
+ * The column storing the query for the click.
+ *
+ * @hide
+ */
+ public final static String SEARCH_CLICK_REPORT_COLUMN_QUERY = "query";
+
+ /**
+ * The column storing the component name of the application that was pivoted into.
+ *
+ * @hide
+ */
+ public final static String SEARCH_CLICK_REPORT_COLUMN_COMPONENT = "component";
+
/**
* Column name for suggestions cursor. Unused - can be null or column can be omitted.
*/
@@ -1257,28 +1365,6 @@ public class SearchManager
* for more information on these schemes.
*/
public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2";
- /**
- * Column name for suggestions cursor. Optional. If your cursor includes this column,
- * then all suggestions will be provided in a format that includes space for two small icons,
- * one at the left and one at the right of each suggestion. The data in the column must
- * be a blob that contains a bitmap.
- *
- * This column overrides any icon provided in the {@link #SUGGEST_COLUMN_ICON_1} column.
- *
- * @hide
- */
- public final static String SUGGEST_COLUMN_ICON_1_BITMAP = "suggest_icon_1_bitmap";
- /**
- * Column name for suggestions cursor. Optional. If your cursor includes this column,
- * then all suggestions will be provided in a format that includes space for two small icons,
- * one at the left and one at the right of each suggestion. The data in the column must
- * be a blob that contains a bitmap.
- *
- * This column overrides any icon provided in the {@link #SUGGEST_COLUMN_ICON_2} column.
- *
- * @hide
- */
- public final static String SUGGEST_COLUMN_ICON_2_BITMAP = "suggest_icon_2_bitmap";
/**
* Column name for suggestions cursor. Optional. If this column exists and
* this element exists at the given row, this is the action that will be used when
@@ -1299,14 +1385,25 @@ public class SearchManager
* it is more efficient to specify it using XML metadata and omit it from the cursor.
*/
public final static String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data";
+ /**
+ * Column name for suggestions cursor. Optional. If this column exists and
+ * this element exists at the given row, this is the data that will be used when
+ * forming the suggestion's intent. If not provided, the Intent's extra data field will be null.
+ * This column allows suggestions to provide additional arbitrary data which will be included as
+ * an extra under the key EXTRA_DATA_KEY.
+ *
+ * @hide Pending API council approval.
+ */
+ public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
/**
* Column name for suggestions cursor. Optional. This column allows suggestions
* to provide additional arbitrary data which will be included as an extra under the key
- * {@link #EXTRA_DATA_KEY}.
- *
- * @hide pending API council approval
+ * {@link #COMPONENT_NAME_KEY}. For use by the global search system only - if other providers
+ * attempt to use this column, the value will be overwritten by global search.
+ *
+ * @hide
*/
- public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
+ public final static String SUGGEST_COLUMN_INTENT_COMPONENT_NAME = "suggest_intent_component";
/**
* Column name for suggestions cursor. Optional. If this column exists and
* this element exists at the given row, then "/" and this value will be appended to the data
@@ -1334,6 +1431,25 @@ public class SearchManager
*/
public final static String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id";
+ /**
+ * Column name for suggestions cursor. Optional. This column is used to specify the
+ * cursor item's background color if it needs a non-default background color. A non-zero value
+ * indicates a valid background color to override the default.
+ *
+ * @hide For internal use, not part of the public API.
+ */
+ public final static String SUGGEST_COLUMN_BACKGROUND_COLOR = "suggest_background_color";
+
+ /**
+ * Column name for suggestions cursor. Optional. This column is used to specify
+ * that a spinner should be shown in lieu of an icon2 while the shortcut of this suggestion
+ * is being refreshed.
+ *
+ * @hide Pending API council approval.
+ */
+ public final static String SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING =
+ "suggest_spinner_while_refreshing";
+
/**
* Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion
* should not be stored as a shortcut in global search.
@@ -1362,21 +1478,7 @@ public class SearchManager
*/
public final static String INTENT_ACTION_CHANGE_SEARCH_SOURCE
= "android.search.action.CHANGE_SEARCH_SOURCE";
-
- /**
- * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
- * the search dialog will call {@link Cursor#respond(Bundle)} when the
- * suggestion is clicked.
- *
- * The {@link Bundle} argument will be constructed
- * in the same way as the "extra" bundle included in an Intent constructed
- * from the suggestion.
- *
- * @hide Pending API council approval.
- */
- public final static String INTENT_ACTION_CURSOR_RESPOND
- = "android.search.action.CURSOR_RESPOND";
-
+
/**
* Intent action for finding the global search activity.
* The global search provider should handle this intent.
@@ -1395,22 +1497,54 @@ public class SearchManager
public final static String INTENT_ACTION_SEARCH_SETTINGS
= "android.search.action.SEARCH_SETTINGS";
+ /**
+ * Intent action for starting a web search provider's settings activity.
+ * Web search providers should handle this intent if they have provider-specific
+ * settings to implement.
+ *
+ * @hide Pending API council approval.
+ */
+ public final static String INTENT_ACTION_WEB_SEARCH_SETTINGS
+ = "android.search.action.WEB_SEARCH_SETTINGS";
+
+ /**
+ * Intent action broadcasted to inform that the searchables list or default have changed.
+ * Components should handle this intent if they cache any searchable data and wish to stay
+ * up to date on changes.
+ *
+ * @hide Pending API council approval.
+ */
+ public final static String INTENT_ACTION_SEARCHABLES_CHANGED
+ = "android.search.action.SEARCHABLES_CHANGED";
+
+ /**
+ * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
+ * the search dialog will take no action.
+ *
+ * @hide
+ */
+ public final static String INTENT_ACTION_NONE = "android.search.action.ZILCH";
+
/**
* Reference to the shared system search service.
*/
- private static ISearchManager sService = getSearchManagerService();
+ private static ISearchManager mService;
private final Context mContext;
- private final Handler mHandler;
-
- private SearchDialog mSearchDialog;
-
- private OnDismissListener mDismissListener = null;
- private OnCancelListener mCancelListener = null;
+
+ // package private since they are used by the inner class SearchManagerCallback
+ /* package */ boolean mIsShowing = false;
+ /* package */ final Handler mHandler;
+ /* package */ OnDismissListener mDismissListener = null;
+ /* package */ OnCancelListener mCancelListener = null;
+
+ private final SearchManagerCallback mSearchManagerCallback = new SearchManagerCallback();
/*package*/ SearchManager(Context context, Handler handler) {
mContext = context;
mHandler = handler;
+ mService = ISearchManager.Stub.asInterface(
+ ServiceManager.getService(Context.SEARCH_SERVICE));
}
/**
@@ -1458,17 +1592,16 @@ public class SearchManager
ComponentName launchActivity,
Bundle appSearchData,
boolean globalSearch) {
-
- if (mSearchDialog == null) {
- mSearchDialog = new SearchDialog(mContext);
+ if (DBG) debug("startSearch(), mIsShowing=" + mIsShowing);
+ if (mIsShowing) return;
+ try {
+ mIsShowing = true;
+ // activate the search manager and start it up!
+ mService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData,
+ globalSearch, mSearchManagerCallback);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "startSearch() failed: " + ex);
}
-
- // activate the search manager and start it up!
- mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
- globalSearch);
-
- mSearchDialog.setOnCancelListener(this);
- mSearchDialog.setOnDismissListener(this);
}
/**
@@ -1482,9 +1615,16 @@ public class SearchManager
*
* @see #startSearch
*/
- public void stopSearch() {
- if (mSearchDialog != null) {
- mSearchDialog.cancel();
+ public void stopSearch() {
+ if (DBG) debug("stopSearch(), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return;
+ try {
+ mService.stopSearch();
+ // onDismiss will also clear this, but we do it here too since onDismiss() is
+ // called asynchronously.
+ mIsShowing = false;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "stopSearch() failed: " + ex);
}
}
@@ -1497,33 +1637,33 @@ public class SearchManager
*
* @hide
*/
- public boolean isVisible() {
- if (mSearchDialog != null) {
- return mSearchDialog.isShowing();
- }
- return false;
+ public boolean isVisible() {
+ if (DBG) debug("isVisible(), mIsShowing=" + mIsShowing);
+ return mIsShowing;
}
-
+
/**
- * See {@link #setOnDismissListener} for configuring your activity to monitor search UI state.
+ * See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor
+ * search UI state.
*/
public interface OnDismissListener {
/**
- * This method will be called when the search UI is dismissed. To make use if it, you must
- * implement this method in your activity, and call {@link #setOnDismissListener} to
- * register it.
+ * This method will be called when the search UI is dismissed. To make use of it, you must
+ * implement this method in your activity, and call
+ * {@link SearchManager#setOnDismissListener} to register it.
*/
public void onDismiss();
}
/**
- * See {@link #setOnCancelListener} for configuring your activity to monitor search UI state.
+ * See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor
+ * search UI state.
*/
public interface OnCancelListener {
/**
* This method will be called when the search UI is canceled. To make use if it, you must
- * implement this method in your activity, and call {@link #setOnCancelListener} to
- * register it.
+ * implement this method in your activity, and call
+ * {@link SearchManager#setOnCancelListener} to register it.
*/
public void onCancel();
}
@@ -1536,84 +1676,112 @@ public class SearchManager
public void setOnDismissListener(final OnDismissListener listener) {
mDismissListener = listener;
}
-
- /**
- * The callback from the search dialog when dismissed
- * @hide
- */
- public void onDismiss(DialogInterface dialog) {
- if (dialog == mSearchDialog) {
- if (mDismissListener != null) {
- mDismissListener.onDismiss();
- }
- }
- }
/**
* Set or clear the callback that will be invoked whenever the search UI is canceled.
*
* @param listener The {@link OnCancelListener} to use, or null.
*/
- public void setOnCancelListener(final OnCancelListener listener) {
+ public void setOnCancelListener(OnCancelListener listener) {
mCancelListener = listener;
}
-
-
- /**
- * The callback from the search dialog when canceled
- * @hide
- */
- public void onCancel(DialogInterface dialog) {
- if (dialog == mSearchDialog) {
- if (mCancelListener != null) {
- mCancelListener.onCancel();
+
+ private class SearchManagerCallback extends ISearchManagerCallback.Stub {
+
+ private final Runnable mFireOnDismiss = new Runnable() {
+ public void run() {
+ if (DBG) debug("mFireOnDismiss");
+ mIsShowing = false;
+ if (mDismissListener != null) {
+ mDismissListener.onDismiss();
+ }
+ }
+ };
+
+ private final Runnable mFireOnCancel = new Runnable() {
+ public void run() {
+ if (DBG) debug("mFireOnCancel");
+ // doesn't need to clear mIsShowing since onDismiss() always gets called too
+ if (mCancelListener != null) {
+ mCancelListener.onCancel();
+ }
}
+ };
+
+ public void onDismiss() {
+ if (DBG) debug("onDismiss()");
+ mHandler.post(mFireOnDismiss);
+ }
+
+ public void onCancel() {
+ if (DBG) debug("onCancel()");
+ mHandler.post(mFireOnCancel);
}
+
+ }
+
+ // TODO: remove the DialogInterface interfaces from SearchManager.
+ // This changes the public API, so I'll do it in a separate change.
+ public void onCancel(DialogInterface dialog) {
+ throw new UnsupportedOperationException();
+ }
+ public void onDismiss(DialogInterface dialog) {
+ throw new UnsupportedOperationException();
}
/**
- * Save instance state so we can recreate after a rotation.
- *
+ * Saves the state of the search UI.
+ *
+ * @return A Bundle containing the state of the search dialog, or {@code null}
+ * if the search UI is not visible.
+ *
* @hide
*/
- void saveSearchDialog(Bundle outState, String key) {
- if (mSearchDialog != null && mSearchDialog.isShowing()) {
- Bundle searchDialogState = mSearchDialog.onSaveInstanceState();
- outState.putBundle(key, searchDialogState);
+ public Bundle saveSearchDialog() {
+ if (DBG) debug("saveSearchDialog(), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return null;
+ try {
+ return mService.onSaveInstanceState();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onSaveInstanceState() failed: " + ex);
+ return null;
}
}
/**
- * Restore instance state after a rotation.
- *
+ * Restores the state of the search dialog.
+ *
+ * @param searchDialogState Bundle to read the state from.
+ *
* @hide
*/
- void restoreSearchDialog(Bundle inState, String key) {
- Bundle searchDialogState = inState.getBundle(key);
- if (searchDialogState != null) {
- if (mSearchDialog == null) {
- mSearchDialog = new SearchDialog(mContext);
- }
- mSearchDialog.onRestoreInstanceState(searchDialogState);
+ public void restoreSearchDialog(Bundle searchDialogState) {
+ if (DBG) debug("restoreSearchDialog(" + searchDialogState + ")");
+ if (searchDialogState == null) return;
+ try {
+ mService.onRestoreInstanceState(searchDialogState);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onRestoreInstanceState() failed: " + ex);
}
}
-
+
/**
- * Hook for updating layout on a rotation
- *
+ * Update the search dialog after a configuration change.
+ *
+ * @param newConfig The new configuration.
+ *
* @hide
*/
- void onConfigurationChanged(Configuration newConfig) {
- if (mSearchDialog != null && mSearchDialog.isShowing()) {
- mSearchDialog.onConfigurationChanged(newConfig);
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (DBG) debug("onConfigurationChanged(" + newConfig + "), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return;
+ try {
+ mService.onConfigurationChanged(newConfig);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onConfigurationChanged() failed:" + ex);
}
}
-
- private static ISearchManager getSearchManagerService() {
- return ISearchManager.Stub.asInterface(
- ServiceManager.getService(Context.SEARCH_SERVICE));
- }
-
+
/**
* Gets information about a searchable activity. This method is static so that it can
* be used from non-Activity contexts.
@@ -1625,11 +1793,12 @@ public class SearchManager
*
* @hide because SearchableInfo is not part of the API.
*/
- public static SearchableInfo getSearchableInfo(ComponentName componentName,
+ public SearchableInfo getSearchableInfo(ComponentName componentName,
boolean globalSearch) {
try {
- return sService.getSearchableInfo(componentName, globalSearch);
- } catch (RemoteException e) {
+ return mService.getSearchableInfo(componentName, globalSearch);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getSearchableInfo() failed: " + ex);
return null;
}
}
@@ -1639,23 +1808,22 @@ public class SearchManager
*
* @hide because SearchableInfo is not part of the API.
*/
- public static boolean isDefaultSearchable(SearchableInfo searchable) {
- SearchableInfo defaultSearchable = SearchManager.getSearchableInfo(null, true);
+ public boolean isDefaultSearchable(SearchableInfo searchable) {
+ SearchableInfo defaultSearchable = getSearchableInfo(null, true);
return defaultSearchable != null
&& defaultSearchable.getSearchActivity().equals(searchable.getSearchActivity());
}
-
+
/**
- * Gets a cursor with search suggestions. This method is static so that it can
- * be used from non-Activity context.
+ * Gets a cursor with search suggestions.
*
* @param searchable Information about how to get the suggestions.
* @param query The search text entered (so far).
- * @return a cursor with suggestions, or null the suggestion query failed.
- *
+ * @return a cursor with suggestions, or null the suggestion query failed.
+ *
* @hide because SearchableInfo is not part of the API.
*/
- public static Cursor getSuggestions(Context context, SearchableInfo searchable, String query) {
+ public Cursor getSuggestions(SearchableInfo searchable, String query) {
if (searchable == null) {
return null;
}
@@ -1694,7 +1862,7 @@ public class SearchManager
.build();
// finally, make the query
- return context.getContentResolver().query(uri, null, selection, selArgs, null);
+ return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
}
/**
@@ -1706,11 +1874,65 @@ public class SearchManager
*
* @hide because SearchableInfo is not part of the API.
*/
- public static List getSearchablesInGlobalSearch() {
+ public List getSearchablesInGlobalSearch() {
try {
- return sService.getSearchablesInGlobalSearch();
+ return mService.getSearchablesInGlobalSearch();
} catch (RemoteException e) {
+ Log.e(TAG, "getSearchablesInGlobalSearch() failed: " + e);
return null;
}
}
+
+ /**
+ * Returns a list of the searchable activities that handle web searches.
+ *
+ * @return a list of all searchable activities that handle
+ * {@link android.content.Intent#ACTION_WEB_SEARCH}.
+ *
+ * @hide because SearchableInfo is not part of the API.
+ */
+ public List getSearchablesForWebSearch() {
+ try {
+ return mService.getSearchablesForWebSearch();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getSearchablesForWebSearch() failed: " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the default searchable activity for web searches.
+ *
+ * @return searchable information for the activity handling web searches by default.
+ *
+ * @hide because SearchableInfo is not part of the API.
+ */
+ public SearchableInfo getDefaultSearchableForWebSearch() {
+ try {
+ return mService.getDefaultSearchableForWebSearch();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getDefaultSearchableForWebSearch() failed: " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Sets the default searchable activity for web searches.
+ *
+ * @param component Name of the component to set as default activity for web searches.
+ *
+ * @hide
+ */
+ public void setDefaultWebSearch(ComponentName component) {
+ try {
+ mService.setDefaultWebSearch(component);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setDefaultWebSearch() failed: " + e);
+ }
+ }
+
+ private static void debug(String msg) {
+ Thread thread = Thread.currentThread();
+ Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
+ }
}
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 6a02fc930a1c3bdf1a45e3fe72398459dbd4dbc1..49c94d12f8d03ce41ee739fc9a7712d82451f268 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -20,62 +20,103 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.server.search.SearchableInfo;
import android.text.Html;
import android.text.TextUtils;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.CursorAdapter;
+import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
+import static android.app.SearchManager.DialogCursorProtocol;
+
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.WeakHashMap;
/**
* Provides the contents for the suggestion drop-down list.in {@link SearchDialog}.
- *
+ *
* @hide
*/
class SuggestionsAdapter extends ResourceCursorAdapter {
+
private static final boolean DBG = false;
private static final String LOG_TAG = "SuggestionsAdapter";
-
+
+ private SearchManager mSearchManager;
+ private SearchDialog mSearchDialog;
private SearchableInfo mSearchable;
private Context mProviderContext;
private WeakHashMap mOutsideDrawablesCache;
+ private boolean mGlobalSearchMode;
- // Cached column indexes, updated when the cursor changes.
+ // Cached column indexes, updated when the cursor changes.
private int mFormatCol;
private int mText1Col;
private int mText2Col;
private int mIconName1Col;
private int mIconName2Col;
- private int mIconBitmap1Col;
- private int mIconBitmap2Col;
-
- public SuggestionsAdapter(Context context, SearchableInfo searchable,
- WeakHashMap outsideDrawablesCache) {
+ private int mBackgroundColorCol;
+
+ // This value is stored in SuggestionsAdapter by the SearchDialog to indicate whether
+ // a particular list item should be selected upon the next call to notifyDataSetChanged.
+ // This is used to indicate the index of the "More results..." list item so that when
+ // the data set changes after a click of "More results...", we can correctly tell the
+ // ListView to scroll to the right line item. It gets reset to NONE every time it
+ // is consumed.
+ private int mListItemToSelect = NONE;
+ static final int NONE = -1;
+
+ // holds the maximum position that has been displayed to the user
+ int mMaxDisplayed = NONE;
+
+ // holds the position that, when displayed, should result in notifying the cursor
+ int mDisplayNotifyPos = NONE;
+
+ private final Runnable mStartSpinnerRunnable;
+ private final Runnable mStopSpinnerRunnable;
+
+ public SuggestionsAdapter(Context context, SearchDialog searchDialog, SearchableInfo searchable,
+ WeakHashMap outsideDrawablesCache, boolean globalSearchMode) {
super(context,
com.android.internal.R.layout.search_dropdown_item_icons_2line,
null, // no initial cursor
true); // auto-requery
+ mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
+ mSearchDialog = searchDialog;
mSearchable = searchable;
-
+
// set up provider resources (gives us icons, etc.)
Context activityContext = mSearchable.getActivityContext(mContext);
mProviderContext = mSearchable.getProviderContext(mContext, activityContext);
-
+
mOutsideDrawablesCache = outsideDrawablesCache;
+ mGlobalSearchMode = globalSearchMode;
+
+ mStartSpinnerRunnable = new Runnable() {
+ public void run() {
+ mSearchDialog.setWorking(true);
+ }
+ };
+
+ mStopSpinnerRunnable = new Runnable() {
+ public void run() {
+ mSearchDialog.setWorking(false);
+ }
+ };
}
-
+
/**
* Overridden to always return false
, since we cannot be sure that
* suggestion sources return stable IDs.
@@ -94,20 +135,41 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")");
String query = (constraint == null) ? "" : constraint.toString();
+ if (!mGlobalSearchMode) {
+ /**
+ * for in app search we show the progress spinner until the cursor is returned with
+ * the results. for global search we manage the progress bar using
+ * {@link DialogCursorProtocol#POST_REFRESH_RECEIVE_ISPENDING}.
+ */
+ mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable);
+ }
try {
- return SearchManager.getSuggestions(mContext, mSearchable, query);
+ final Cursor cursor = mSearchManager.getSuggestions(mSearchable, query);
+ // trigger fill window so the spinner stays up until the results are copied over and
+ // closer to being ready
+ if (!mGlobalSearchMode && cursor != null) cursor.getCount();
+ return cursor;
} catch (RuntimeException e) {
Log.w(LOG_TAG, "Search suggestions query threw an exception.", e);
return null;
+ } finally {
+ if (!mGlobalSearchMode) {
+ mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
+ }
}
}
-
+
/**
* Cache columns.
*/
@Override
public void changeCursor(Cursor c) {
if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")");
+
+ if (mCursor != null) {
+ callCursorPreClose(mCursor);
+ }
+
super.changeCursor(c);
if (c != null) {
mFormatCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT);
@@ -115,21 +177,86 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
- mIconBitmap1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1_BITMAP);
- mIconBitmap2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2_BITMAP);
+ mBackgroundColorCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_BACKGROUND_COLOR);
+ }
+ }
+
+ /**
+ * Handle sending and receiving information associated with
+ * {@link DialogCursorProtocol#PRE_CLOSE}.
+ *
+ * @param cursor The cursor to call.
+ */
+ private void callCursorPreClose(Cursor cursor) {
+ if (!mGlobalSearchMode) return;
+ final Bundle request = new Bundle();
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.PRE_CLOSE);
+ request.putInt(DialogCursorProtocol.PRE_CLOSE_SEND_MAX_DISPLAY_POS, mMaxDisplayed);
+ final Bundle response = cursor.respond(request);
+
+ mMaxDisplayed = -1;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
+ super.notifyDataSetChanged();
+
+ callCursorPostRefresh(mCursor);
+
+ // look out for the pending item we are supposed to scroll to
+ if (mListItemToSelect != NONE) {
+ mSearchDialog.setListSelection(mListItemToSelect);
+ mListItemToSelect = NONE;
}
}
-
+
+ /**
+ * Handle sending and receiving information associated with
+ * {@link DialogCursorProtocol#POST_REFRESH}.
+ *
+ * @param cursor The cursor to call.
+ */
+ private void callCursorPostRefresh(Cursor cursor) {
+ if (!mGlobalSearchMode) return;
+ final Bundle request = new Bundle();
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.POST_REFRESH);
+ final Bundle response = cursor.respond(request);
+
+ mSearchDialog.setWorking(
+ response.getBoolean(DialogCursorProtocol.POST_REFRESH_RECEIVE_ISPENDING, false));
+
+ mDisplayNotifyPos =
+ response.getInt(DialogCursorProtocol.POST_REFRESH_RECEIVE_DISPLAY_NOTIFY, -1);
+ }
+
+ /**
+ * Tell the cursor which position was clicked, handling sending and receiving information
+ * associated with {@link DialogCursorProtocol#CLICK}.
+ *
+ * @param cursor The cursor
+ * @param position The position that was clicked.
+ */
+ void callCursorOnClick(Cursor cursor, int position) {
+ if (!mGlobalSearchMode) return;
+ final Bundle request = new Bundle(1);
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.CLICK);
+ request.putInt(DialogCursorProtocol.CLICK_SEND_POSITION, position);
+ final Bundle response = cursor.respond(request);
+ mListItemToSelect = response.getInt(
+ DialogCursorProtocol.CLICK_RECEIVE_SELECTED_POS, SuggestionsAdapter.NONE);
+ }
+
/**
* Tags the view with cached child view look-ups.
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- View v = super.newView(context, cursor, parent);
+ View v = new SuggestionItemView(context, cursor);
v.setTag(new ChildViewCache(v));
return v;
}
-
+
/**
* Cache of the child views of drop-drown list items, to avoid looking up the children
* each time the contents of a list item are changed.
@@ -139,7 +266,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
public final TextView mText2;
public final ImageView mIcon1;
public final ImageView mIcon2;
-
+
public ChildViewCache(View v) {
mText1 = (TextView) v.findViewById(com.android.internal.R.id.text1);
mText2 = (TextView) v.findViewById(com.android.internal.R.id.text2);
@@ -147,21 +274,38 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
mIcon2 = (ImageView) v.findViewById(com.android.internal.R.id.icon2);
}
}
-
+
@Override
public void bindView(View view, Context context, Cursor cursor) {
ChildViewCache views = (ChildViewCache) view.getTag();
- boolean isHtml = false;
- if (mFormatCol >= 0) {
- String format = cursor.getString(mFormatCol);
- isHtml = "html".equals(format);
+ final int pos = cursor.getPosition();
+
+ // update the maximum position displayed since last refresh
+ if (pos > mMaxDisplayed) {
+ mMaxDisplayed = pos;
}
+
+ // if the cursor wishes to be notified about this position, send it
+ if (mGlobalSearchMode && mDisplayNotifyPos != NONE && pos == mDisplayNotifyPos) {
+ final Bundle request = new Bundle();
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.THRESH_HIT);
+ mCursor.respond(request);
+ mDisplayNotifyPos = NONE; // only notify the first time
+ }
+
+ int backgroundColor = 0;
+ if (mBackgroundColorCol != -1) {
+ backgroundColor = cursor.getInt(mBackgroundColorCol);
+ }
+ ((SuggestionItemView)view).setColor(backgroundColor);
+
+ final boolean isHtml = mFormatCol > 0 && "html".equals(cursor.getString(mFormatCol));
setViewText(cursor, views.mText1, mText1Col, isHtml);
setViewText(cursor, views.mText2, mText2Col, isHtml);
- setViewIcon(cursor, views.mIcon1, mIconBitmap1Col, mIconName1Col);
- setViewIcon(cursor, views.mIcon2, mIconBitmap2Col, mIconName2Col);
+ setViewIcon(cursor, views.mIcon1, mIconName1Col);
+ setViewIcon(cursor, views.mIcon2, mIconName2Col);
}
-
+
private void setViewText(Cursor cursor, TextView v, int textCol, boolean isHtml) {
if (v == null) {
return;
@@ -173,49 +317,46 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
}
// Set the text even if it's null, since we need to clear any previous text.
v.setText(text);
-
+
if (TextUtils.isEmpty(text)) {
v.setVisibility(View.GONE);
} else {
v.setVisibility(View.VISIBLE);
}
}
-
- private void setViewIcon(Cursor cursor, ImageView v, int iconBitmapCol, int iconNameCol) {
+
+ private void setViewIcon(Cursor cursor, ImageView v, int iconNameCol) {
if (v == null) {
return;
}
- Drawable drawable = null;
- // First try the bitmap column
- if (iconBitmapCol >= 0) {
- byte[] data = cursor.getBlob(iconBitmapCol);
- if (data != null) {
- Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- if (bitmap != null) {
- drawable = new BitmapDrawable(bitmap);
- }
- }
- }
- // If there was no bitmap, try the icon resource column.
- if (drawable == null && iconNameCol >= 0) {
- String value = cursor.getString(iconNameCol);
- drawable = getDrawableFromResourceValue(value);
+ if (iconNameCol < 0) {
+ return;
}
+ String value = cursor.getString(iconNameCol);
+ Drawable drawable = getDrawableFromResourceValue(value);
// Set the icon even if the drawable is null, since we need to clear any
// previous icon.
v.setImageDrawable(drawable);
-
+
if (drawable == null) {
v.setVisibility(View.GONE);
} else {
v.setVisibility(View.VISIBLE);
+
+ // This is a hack to get any animated drawables (like a 'working' spinner)
+ // to animate. You have to setVisible true on an AnimationDrawable to get
+ // it to start animating, but it must first have been false or else the
+ // call to setVisible will be ineffective. We need to clear up the story
+ // about animated drawables in the future, see http://b/1878430.
+ drawable.setVisible(false, false);
+ drawable.setVisible(true, false);
}
}
-
+
/**
* Gets the text to show in the query field when a suggestion is selected.
- *
- * @param cursor The Cursor to read the suggestion data from. The Cursor should already
+ *
+ * @param cursor The Cursor to read the suggestion data from. The Cursor should already
* be moved to the suggestion that is to be read from.
* @return The text to show, or null
if the query should not be
* changed when selecting this suggestion.
@@ -225,36 +366,36 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
if (cursor == null) {
return null;
}
-
+
String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY);
if (query != null) {
return query;
}
-
+
if (mSearchable.shouldRewriteQueryFromData()) {
String data = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
if (data != null) {
return data;
}
}
-
+
if (mSearchable.shouldRewriteQueryFromText()) {
String text1 = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_TEXT_1);
if (text1 != null) {
return text1;
}
}
-
+
return null;
}
-
+
/**
* This method is overridden purely to provide a bit of protection against
* flaky content providers.
- *
+ *
* @see android.widget.ListAdapter#getView(int, View, ViewGroup)
*/
- @Override
+ @Override
public View getView(int position, View convertView, ViewGroup parent) {
try {
return super.getView(position, convertView, parent);
@@ -263,28 +404,28 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
// Put exception string in item title
View v = newView(mContext, mCursor, parent);
if (v != null) {
- ChildViewCache views = (ChildViewCache) v.getTag();
+ ChildViewCache views = (ChildViewCache) v.getTag();
TextView tv = views.mText1;
tv.setText(e.toString());
}
return v;
}
}
-
+
/**
* Gets a drawable given a value provided by a suggestion provider.
- *
+ *
* This value could be just the string value of a resource id
* (e.g., "2130837524"), in which case we will try to retrieve a drawable from
* the provider's resources. If the value is not an integer, it is
- * treated as a Uri and opened with
+ * treated as a Uri and opened with
* {@link ContentResolver#openOutputStream(android.net.Uri, String)}.
*
* All resources and URIs are read using the suggestion provider's context.
*
* If the string is not formatted as expected, or no drawable can be found for
* the provided value, this method returns null.
- *
+ *
* @param drawableId a string like "2130837524",
* "android.resource://com.android.alarmclock/2130837524",
* or "content://contacts/photos/253".
@@ -294,43 +435,58 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) {
return null;
}
-
+
// First, check the cache.
Drawable drawable = mOutsideDrawablesCache.get(drawableId);
- if (drawable != null) return drawable;
+ if (drawable != null) {
+ if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + drawableId);
+ return drawable;
+ }
try {
// Not cached, try using it as a plain resource ID in the provider's context.
int resourceId = Integer.parseInt(drawableId);
drawable = mProviderContext.getResources().getDrawable(resourceId);
+ if (DBG) Log.d(LOG_TAG, "Found icon by resource ID: " + drawableId);
} catch (NumberFormatException nfe) {
// The id was not an integer resource id.
// Let the ContentResolver handle content, android.resource and file URIs.
try {
Uri uri = Uri.parse(drawableId);
- drawable = Drawable.createFromStream(
- mProviderContext.getContentResolver().openInputStream(uri),
- null);
+ InputStream stream = mProviderContext.getContentResolver().openInputStream(uri);
+ if (stream != null) {
+ try {
+ drawable = Drawable.createFromStream(stream, null);
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException ex) {
+ Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex);
+ }
+ }
+ }
+ if (DBG) Log.d(LOG_TAG, "Opened icon input stream: " + drawableId);
} catch (FileNotFoundException fnfe) {
+ if (DBG) Log.d(LOG_TAG, "Icon stream not found: " + drawableId);
// drawable = null;
}
-
+
// If we got a drawable for this resource id, then stick it in the
// map so we don't do this lookup again.
if (drawable != null) {
mOutsideDrawablesCache.put(drawableId, drawable);
}
} catch (NotFoundException nfe) {
- // Resource could not be found
+ if (DBG) Log.d(LOG_TAG, "Icon resource not found: " + drawableId);
// drawable = null;
}
-
+
return drawable;
}
-
+
/**
* Gets the value of a string column by name.
- *
+ *
* @param cursor Cursor to read the value from.
* @param columnName The name of the column to read.
* @return The value of the given column, or null
@@ -338,10 +494,75 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
*/
public static String getColumnString(Cursor cursor, String columnName) {
int col = cursor.getColumnIndex(columnName);
- if (col == -1) {
+ if (col == NONE) {
return null;
}
return cursor.getString(col);
}
+ /**
+ * A parent viewgroup class which holds the actual suggestion item as a child.
+ *
+ * The sole purpose of this class is to draw the given background color when the item is in
+ * normal state and not draw the background color when it is pressed, so that when pressed the
+ * list view's selection highlight will be displayed properly (if we draw our background it
+ * draws on top of the list view selection highlight).
+ */
+ private class SuggestionItemView extends ViewGroup {
+ private int mBackgroundColor; // the background color to draw in normal state.
+ private View mView; // the suggestion item's view.
+
+ protected SuggestionItemView(Context context, Cursor cursor) {
+ // Initialize ourselves
+ super(context);
+ mBackgroundColor = 0; // transparent by default.
+
+ // For our layout use the default list item height from the current theme.
+ TypedValue lineHeight = new TypedValue();
+ context.getTheme().resolveAttribute(
+ com.android.internal.R.attr.searchResultListItemHeight, lineHeight, true);
+ DisplayMetrics metrics = new DisplayMetrics();
+ metrics.setToDefaults();
+ AbsListView.LayoutParams layout = new AbsListView.LayoutParams(
+ AbsListView.LayoutParams.FILL_PARENT,
+ (int)lineHeight.getDimension(metrics));
+
+ setLayoutParams(layout);
+
+ // Initialize the child view
+ mView = SuggestionsAdapter.super.newView(context, cursor, this);
+ if (mView != null) {
+ addView(mView, layout.width, layout.height);
+ mView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ public void setColor(int backgroundColor) {
+ mBackgroundColor = backgroundColor;
+ }
+
+ @Override
+ public void dispatchDraw(Canvas canvas) {
+ if (mBackgroundColor != 0 && !isPressed() && !isSelected()) {
+ canvas.drawColor(mBackgroundColor);
+ }
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mView != null) {
+ mView.measure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (mView != null) {
+ mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+ }
+ }
+ }
+
}
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 10c2b02d4c3529004a6b147564cfb0f822f185b3..03e8623a63de43223003e107e6d9600a9d2f55b2 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -26,7 +26,6 @@ import android.widget.RemoteViews;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
@@ -40,7 +39,7 @@ public class AppWidgetHost {
static final int HANDLE_UPDATE = 1;
static final int HANDLE_PROVIDER_CHANGED = 2;
- static Object sServiceLock = new Object();
+ final static Object sServiceLock = new Object();
static IAppWidgetService sService;
Context mContext;
@@ -85,7 +84,7 @@ public class AppWidgetHost {
int mHostId;
Callbacks mCallbacks = new Callbacks();
- HashMap mViews = new HashMap();
+ final HashMap mViews = new HashMap();
public AppWidgetHost(Context context, int hostId) {
mContext = context;
@@ -104,8 +103,8 @@ public class AppWidgetHost {
* becomes visible, i.e. from onStart() in your Activity.
*/
public void startListening() {
- int[] updatedIds = null;
- ArrayList updatedViews = new ArrayList();
+ int[] updatedIds;
+ ArrayList updatedViews = new ArrayList();
try {
if (mPackageName == null) {
@@ -209,7 +208,7 @@ public class AppWidgetHost {
synchronized (mViews) {
mViews.put(appWidgetId, view);
}
- RemoteViews views = null;
+ RemoteViews views;
try {
views = sService.getAppWidgetViews(appWidgetId);
} catch (RemoteException e) {
@@ -231,6 +230,7 @@ public class AppWidgetHost {
/**
* Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
*/
+ @SuppressWarnings({"UnusedDeclaration"})
protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index be0f96e8ab6a6d6cef4dcdf4ba305c9604589fa5..62d92674d86485b2b19521f00407b50d267ab99e 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -22,16 +22,12 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.os.Handler;
-import android.os.Message;
import android.os.SystemClock;
-import android.util.Config;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.Animation;
import android.widget.FrameLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -86,6 +82,7 @@ public class AppWidgetHostView extends FrameLayout {
* @param animationIn Resource ID of in animation to use
* @param animationOut Resource ID of out animation to use
*/
+ @SuppressWarnings({"UnusedDeclaration"})
public AppWidgetHostView(Context context, int animationIn, int animationOut) {
super(context);
mContext = context;
@@ -272,7 +269,7 @@ public class AppWidgetHostView extends FrameLayout {
try {
if (mInfo != null) {
Context theirContext = mContext.createPackageContext(
- mInfo.provider.getPackageName(), 0 /* no flags */);
+ mInfo.provider.getPackageName(), Context.CONTEXT_RESTRICTED);
LayoutInflater inflater = (LayoutInflater)
theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(theirContext);
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index eca04b311330837d84efa1b5f6a1e703c637ac87..3660001b1968901a3d17e60616c89d36e5186f96 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -21,7 +21,9 @@ import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import android.widget.RemoteViews;
import com.android.internal.appwidget.IAppWidgetService;
@@ -187,6 +189,8 @@ public class AppWidgetManager {
Context mContext;
+ private DisplayMetrics mDisplayMetrics;
+
/**
* Get the AppWidgetManager instance to use for the supplied {@link android.content.Context
* Context} object.
@@ -213,6 +217,7 @@ public class AppWidgetManager {
private AppWidgetManager(Context context) {
mContext = context;
+ mDisplayMetrics = context.getResources().getDisplayMetrics();
}
/**
@@ -292,7 +297,15 @@ public class AppWidgetManager {
*/
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
try {
- return sService.getAppWidgetInfo(appWidgetId);
+ AppWidgetProviderInfo info = sService.getAppWidgetInfo(appWidgetId);
+ if (info != null) {
+ // Converting complex to dp.
+ info.minWidth =
+ TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics);
+ info.minHeight =
+ TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics);
+ }
+ return info;
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
diff --git a/core/java/android/backup/AbsoluteFileBackupHelper.java b/core/java/android/backup/AbsoluteFileBackupHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab246754b5afa8fd350b4fc745a3feb28a73ec70
--- /dev/null
+++ b/core/java/android/backup/AbsoluteFileBackupHelper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2009 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.backup;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileDescriptor;
+
+/**
+ * Like FileBackupHelper, but takes absolute paths for the files instead of
+ * subpaths of getFilesDir()
+ *
+ * @hide
+ */
+public class AbsoluteFileBackupHelper extends FileBackupHelperBase implements BackupHelper {
+ private static final String TAG = "AbsoluteFileBackupHelper";
+
+ Context mContext;
+ String[] mFiles;
+
+ public AbsoluteFileBackupHelper(Context context, String... files) {
+ super(context);
+
+ mContext = context;
+ mFiles = files;
+ }
+
+ /**
+ * Based on oldState, determine which of the files from the application's data directory
+ * need to be backed up, write them to the data stream, and fill in newState with the
+ * state as it exists now.
+ */
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ // use the file paths as the keys, too
+ performBackup_checked(oldState, data, newState, mFiles, mFiles);
+ }
+
+ public void restoreEntity(BackupDataInputStream data) {
+ // TODO: turn this off before ship
+ Log.d(TAG, "got entity '" + data.getKey() + "' size=" + data.size());
+ String key = data.getKey();
+ if (isKeyInList(key, mFiles)) {
+ File f = new File(key);
+ writeFile(f, data);
+ }
+ }
+}
+
diff --git a/core/java/android/backup/BackupDataInput.java b/core/java/android/backup/BackupDataInput.java
new file mode 100644
index 0000000000000000000000000000000000000000..69c206ce1c8155092d080133af95996260c9afdc
--- /dev/null
+++ b/core/java/android/backup/BackupDataInput.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2009 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.backup;
+
+import android.content.Context;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/** @hide */
+public class BackupDataInput {
+ int mBackupReader;
+
+ private EntityHeader mHeader = new EntityHeader();
+ private boolean mHeaderReady;
+
+ private static class EntityHeader {
+ String key;
+ int dataSize;
+ }
+
+ public BackupDataInput(FileDescriptor fd) {
+ if (fd == null) throw new NullPointerException();
+ mBackupReader = ctor(fd);
+ if (mBackupReader == 0) {
+ throw new RuntimeException("Native initialization failed with fd=" + fd);
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ dtor(mBackupReader);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public boolean readNextHeader() throws IOException {
+ int result = readNextHeader_native(mBackupReader, mHeader);
+ if (result == 0) {
+ // read successfully
+ mHeaderReady = true;
+ return true;
+ } else if (result > 0) {
+ // done
+ mHeaderReady = false;
+ return false;
+ } else {
+ // error
+ mHeaderReady = false;
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ }
+
+ public String getKey() {
+ if (mHeaderReady) {
+ return mHeader.key;
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ public int getDataSize() {
+ if (mHeaderReady) {
+ return mHeader.dataSize;
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ public int readEntityData(byte[] data, int offset, int size) throws IOException {
+ if (mHeaderReady) {
+ int result = readEntityData_native(mBackupReader, data, offset, size);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ public void skipEntityData() throws IOException {
+ if (mHeaderReady) {
+ int result = skipEntityData_native(mBackupReader);
+ if (result >= 0) {
+ return;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ private native static int ctor(FileDescriptor fd);
+ private native static void dtor(int mBackupReader);
+
+ private native int readNextHeader_native(int mBackupReader, EntityHeader entity);
+ private native int readEntityData_native(int mBackupReader, byte[] data, int offset, int size);
+ private native int skipEntityData_native(int mBackupReader);
+}
diff --git a/core/java/android/backup/BackupDataInputStream.java b/core/java/android/backup/BackupDataInputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..b705c4c3602237a4dbba13cd2dac298e9aab58f6
--- /dev/null
+++ b/core/java/android/backup/BackupDataInputStream.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2009 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.backup;
+
+import android.util.Log;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/** @hide */
+public class BackupDataInputStream extends InputStream {
+
+ String key;
+ int dataSize;
+
+ BackupDataInput mData;
+ byte[] mOneByte;
+
+ BackupDataInputStream(BackupDataInput data) {
+ mData = data;
+ }
+
+ public int read() throws IOException {
+ byte[] one = mOneByte;
+ if (mOneByte == null) {
+ one = mOneByte = new byte[1];
+ }
+ mData.readEntityData(one, 0, 1);
+ return one[0];
+ }
+
+ public int read(byte[] b, int offset, int size) throws IOException {
+ return mData.readEntityData(b, offset, size);
+ }
+
+ public int read(byte[] b) throws IOException {
+ return mData.readEntityData(b, 0, b.length);
+ }
+
+ public String getKey() {
+ return this.key;
+ }
+
+ public int size() {
+ return this.dataSize;
+ }
+}
+
+
diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java
index 555494e595240b6ba99379b94492d48d82bd40a1..d29c5ba02f1403cfdf8cfa533751cb9f5321e3ff 100644
--- a/core/java/android/backup/BackupDataOutput.java
+++ b/core/java/android/backup/BackupDataOutput.java
@@ -19,27 +19,59 @@ package android.backup;
import android.content.Context;
import java.io.FileDescriptor;
+import java.io.IOException;
/** @hide */
public class BackupDataOutput {
- /* package */ FileDescriptor fd;
+ int mBackupWriter;
public static final int OP_UPDATE = 1;
public static final int OP_DELETE = 2;
- public BackupDataOutput(Context context, FileDescriptor fd) {
- this.fd = fd;
+ public BackupDataOutput(FileDescriptor fd) {
+ if (fd == null) throw new NullPointerException();
+ mBackupWriter = ctor(fd);
+ if (mBackupWriter == 0) {
+ throw new RuntimeException("Native initialization failed with fd=" + fd);
+ }
}
- public void close() {
- // do we close the fd?
+ // A dataSize of -1 indicates that the record under this key should be deleted
+ public int writeEntityHeader(String key, int dataSize) throws IOException {
+ int result = writeEntityHeader_native(mBackupWriter, key, dataSize);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
}
- public native void flush();
- public native void write(byte[] buffer);
- public native void write(int oneByte);
- public native void write(byte[] buffer, int offset, int count);
- public native void writeOperation(int op);
- public native void writeKey(String key);
+ public int writeEntityData(byte[] data, int size) throws IOException {
+ int result = writeEntityData_native(mBackupWriter, data, size);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ }
+
+ public void setKeyPrefix(String keyPrefix) {
+ setKeyPrefix_native(mBackupWriter, keyPrefix);
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ dtor(mBackupWriter);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private native static int ctor(FileDescriptor fd);
+ private native static void dtor(int mBackupWriter);
+
+ private native static int writeEntityHeader_native(int mBackupWriter, String key, int dataSize);
+ private native static int writeEntityData_native(int mBackupWriter, byte[] data, int size);
+ private native static void setKeyPrefix_native(int mBackupWriter, String keyPrefix);
}
diff --git a/core/java/android/backup/BackupHelper.java b/core/java/android/backup/BackupHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..3983e28cce39927331cbece04685952bfadf8b11
--- /dev/null
+++ b/core/java/android/backup/BackupHelper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2009 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.backup;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.InputStream;
+
+/** @hide */
+public interface BackupHelper {
+ /**
+ * Based on oldState, determine which of the files from the application's data directory
+ * need to be backed up, write them to the data stream, and fill in newState with the
+ * state as it exists now.
+ */
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState);
+
+ /**
+ * Called by BackupHelperDispatcher to dispatch one entity of data.
+ *
+ * Do not close the data
stream. Do not read more than
+ * dataSize
bytes from data
.
+ */
+ public void restoreEntity(BackupDataInputStream data);
+
+ /**
+ *
+ */
+ public void writeRestoreSnapshot(ParcelFileDescriptor fd);
+}
+
diff --git a/core/java/android/backup/BackupHelperAgent.java b/core/java/android/backup/BackupHelperAgent.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d0c4a2514db31725f4cb486d2ff0cd685edac80
--- /dev/null
+++ b/core/java/android/backup/BackupHelperAgent.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2007 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.backup;
+
+import android.app.BackupAgent;
+import android.backup.BackupHelper;
+import android.backup.BackupHelperDispatcher;
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+
+/** @hide */
+public class BackupHelperAgent extends BackupAgent {
+ static final String TAG = "BackupHelperAgent";
+
+ BackupHelperDispatcher mDispatcher = new BackupHelperDispatcher();
+
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ mDispatcher.performBackup(oldState, data, newState);
+ }
+
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+ throws IOException {
+ mDispatcher.performRestore(data, appVersionCode, newState);
+ }
+
+ public BackupHelperDispatcher getDispatcher() {
+ return mDispatcher;
+ }
+
+ public void addHelper(String keyPrefix, BackupHelper helper) {
+ mDispatcher.addHelper(keyPrefix, helper);
+ }
+}
+
+
diff --git a/core/java/android/backup/BackupHelperDispatcher.java b/core/java/android/backup/BackupHelperDispatcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ccb83effd45400c81cf69d3c531005a96b48fab
--- /dev/null
+++ b/core/java/android/backup/BackupHelperDispatcher.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2009 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.backup;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.FileDescriptor;
+import java.util.TreeMap;
+import java.util.Map;
+
+/** @hide */
+public class BackupHelperDispatcher {
+ private static final String TAG = "BackupHelperDispatcher";
+
+ private static class Header {
+ int chunkSize; // not including the header
+ String keyPrefix;
+ }
+
+ TreeMap mHelpers = new TreeMap();
+
+ public BackupHelperDispatcher() {
+ }
+
+ public void addHelper(String keyPrefix, BackupHelper helper) {
+ mHelpers.put(keyPrefix, helper);
+ }
+
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ // First, do the helpers that we've already done, since they're already in the state
+ // file.
+ int err;
+ Header header = new Header();
+ TreeMap helpers = (TreeMap)mHelpers.clone();
+ FileDescriptor oldStateFD = null;
+ FileDescriptor newStateFD = newState.getFileDescriptor();
+
+ if (oldState != null) {
+ oldStateFD = oldState.getFileDescriptor();
+ while ((err = readHeader_native(header, oldStateFD)) >= 0) {
+ if (err == 0) {
+ BackupHelper helper = helpers.get(header.keyPrefix);
+ Log.d(TAG, "handling existing helper '" + header.keyPrefix + "' " + helper);
+ if (helper != null) {
+ doOneBackup(oldState, data, newState, header, helper);
+ helpers.remove(header.keyPrefix);
+ } else {
+ skipChunk_native(oldStateFD, header.chunkSize);
+ }
+ }
+ }
+ }
+
+ // Then go through and do the rest that we haven't done.
+ for (Map.Entry entry: helpers.entrySet()) {
+ header.keyPrefix = entry.getKey();
+ Log.d(TAG, "handling new helper '" + header.keyPrefix + "'");
+ BackupHelper helper = entry.getValue();
+ doOneBackup(oldState, data, newState, header, helper);
+ }
+ }
+
+ private void doOneBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState, Header header, BackupHelper helper)
+ throws IOException {
+ int err;
+ FileDescriptor newStateFD = newState.getFileDescriptor();
+
+ // allocate space for the header in the file
+ int pos = allocateHeader_native(header, newStateFD);
+ if (pos < 0) {
+ throw new IOException("allocateHeader_native failed (error " + pos + ")");
+ }
+
+ data.setKeyPrefix(header.keyPrefix);
+
+ // do the backup
+ helper.performBackup(oldState, data, newState);
+
+ // fill in the header (seeking back to pos). The file pointer will be returned to
+ // where it was at the end of performBackup. Header.chunkSize will not be filled in.
+ err = writeHeader_native(header, newStateFD, pos);
+ if (err != 0) {
+ throw new IOException("writeHeader_native failed (error " + err + ")");
+ }
+ }
+
+ public void performRestore(BackupDataInput input, int appVersionCode,
+ ParcelFileDescriptor newState)
+ throws IOException {
+ boolean alreadyComplained = false;
+
+ BackupDataInputStream stream = new BackupDataInputStream(input);
+ while (input.readNextHeader()) {
+
+ String rawKey = input.getKey();
+ int pos = rawKey.indexOf(':');
+ if (pos > 0) {
+ String prefix = rawKey.substring(0, pos);
+ BackupHelper helper = mHelpers.get(prefix);
+ if (helper != null) {
+ stream.dataSize = input.getDataSize();
+ stream.key = rawKey.substring(pos+1);
+ helper.restoreEntity(stream);
+ } else {
+ if (!alreadyComplained) {
+ Log.w(TAG, "Couldn't find helper for: '" + rawKey + "'");
+ alreadyComplained = true;
+ }
+ }
+ } else {
+ if (!alreadyComplained) {
+ Log.w(TAG, "Entity with no prefix: '" + rawKey + "'");
+ alreadyComplained = true;
+ }
+ }
+ input.skipEntityData(); // In case they didn't consume the data.
+ }
+
+ // Write out the state files -- mHelpers is a TreeMap, so the order is well defined.
+ for (BackupHelper helper: mHelpers.values()) {
+ helper.writeRestoreSnapshot(newState);
+ }
+ }
+
+ private static native int readHeader_native(Header h, FileDescriptor fd);
+ private static native int skipChunk_native(FileDescriptor fd, int bytesToSkip);
+
+ private static native int allocateHeader_native(Header h, FileDescriptor fd);
+ private static native int writeHeader_native(Header h, FileDescriptor fd, int pos);
+}
+
diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
index 6f0b2eef603f7f720072dedaadbd02734fcd4973..34a1a0c8f2f47d85157b2a7ddb1df8f1f0205288 100644
--- a/core/java/android/backup/BackupManager.java
+++ b/core/java/android/backup/BackupManager.java
@@ -19,6 +19,7 @@ package android.backup;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.util.Log;
/**
* BackupManager is the interface to the system's backup service.
@@ -32,14 +33,24 @@ import android.os.ServiceManager;
* until the backup operation actually occurs.
*
* The backup operation itself begins with the system launching the
- * {@link BackupService} subclass declared in your manifest. See the documentation
- * for {@link BackupService} for a detailed description of how the backup then proceeds.
+ * {@link android.app.BackupAgent} subclass declared in your manifest. See the
+ * documentation for {@link android.app.BackupAgent} for a detailed description
+ * of how the backup then proceeds.
*
* @hide pending API solidification
*/
public class BackupManager {
+ private static final String TAG = "BackupManager";
+
private Context mContext;
- private IBackupManager mService;
+ private static IBackupManager sService;
+
+ private static void checkServiceBinder() {
+ if (sService == null) {
+ sService = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ }
+ }
/**
* Constructs a BackupManager object through which the application can
@@ -51,19 +62,60 @@ public class BackupManager {
*/
public BackupManager(Context context) {
mContext = context;
- mService = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
}
/**
* Notifies the Android backup system that your application wishes to back up
* new changes to its data. A backup operation using your application's
- * {@link BackupService} subclass will be scheduled when you call this method.
+ * {@link android.app.BackupAgent} subclass will be scheduled when you call this method.
*/
public void dataChanged() {
- try {
- mService.dataChanged(mContext.getPackageName());
- } catch (RemoteException e) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.dataChanged(mContext.getPackageName());
+ } catch (RemoteException e) {
+ Log.d(TAG, "dataChanged() couldn't connect");
+ }
+ }
+ }
+
+ /**
+ * Convenience method for callers who need to indicate that some other package
+ * needs a backup pass. This can be relevant in the case of groups of packages
+ * that share a uid, for example.
+ *
+ * This method requires that the application hold the "android.permission.BACKUP"
+ * permission if the package named in the argument is not the caller's own.
+ */
+ public static void dataChanged(String packageName) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.dataChanged(packageName);
+ } catch (RemoteException e) {
+ Log.d(TAG, "dataChanged(pkg) couldn't connect");
+ }
+ }
+ }
+
+ /**
+ * Begin the process of restoring system data from backup. This method requires
+ * that the application hold the "android.permission.BACKUP" permission, and is
+ * not public.
+ *
+ * {@hide}
+ */
+ public IRestoreSession beginRestoreSession(String transport) {
+ IRestoreSession binder = null;
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ binder = sService.beginRestoreSession(transport);
+ } catch (RemoteException e) {
+ Log.d(TAG, "beginRestoreSession() couldn't connect");
+ }
}
+ return binder;
}
}
diff --git a/core/java/android/backup/FileBackupHelper.java b/core/java/android/backup/FileBackupHelper.java
index 05159dc3bdf21bb34a7029cea18aa01deff98317..405849705ff769b14619786213df945bde9395cf 100644
--- a/core/java/android/backup/FileBackupHelper.java
+++ b/core/java/android/backup/FileBackupHelper.java
@@ -20,54 +20,53 @@ import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import java.io.File;
import java.io.FileDescriptor;
/** @hide */
-public class FileBackupHelper {
+public class FileBackupHelper extends FileBackupHelperBase implements BackupHelper {
private static final String TAG = "FileBackupHelper";
+ Context mContext;
+ File mFilesDir;
+ String[] mFiles;
+
+ public FileBackupHelper(Context context, String... files) {
+ super(context);
+
+ mContext = context;
+ mFilesDir = context.getFilesDir();
+ mFiles = files;
+ }
+
/**
* Based on oldState, determine which of the files from the application's data directory
* need to be backed up, write them to the data stream, and fill in newState with the
* state as it exists now.
*/
- public static void performBackup(Context context,
- ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState, String[] files) {
- String basePath = context.getFilesDir().getAbsolutePath();
- performBackup_checked(basePath, oldState, data, newState, files);
- }
-
- /**
- * Check the parameters so the native code doens't have to throw all the exceptions
- * since it's easier to do that from java.
- */
- static void performBackup_checked(String basePath,
- ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState, String[] files) {
- if (files.length == 0) {
- return;
- }
- if (basePath == null) {
- throw new NullPointerException();
- }
- // oldStateFd can be null
- FileDescriptor oldStateFd = oldState != null ? oldState.getFileDescriptor() : null;
- if (data.fd == null) {
- throw new NullPointerException();
- }
- FileDescriptor newStateFd = newState.getFileDescriptor();
- if (newStateFd == null) {
- throw new NullPointerException();
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ // file names
+ String[] files = mFiles;
+ File base = mContext.getFilesDir();
+ final int N = files.length;
+ String[] fullPaths = new String[N];
+ for (int i=0; iCallers must hold the android.permission.BACKUP permission to use this method.
+ */
+ void setBackupEnabled(boolean isEnabled);
+
+ /**
+ * Indicate that any necessary one-time provisioning has occurred.
+ *
+ * Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ void setBackupProvisioned(boolean isProvisioned);
+
+ /**
+ * Report whether the backup mechanism is currently enabled.
+ *
+ *
Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ boolean isBackupEnabled();
+
+ /**
+ * Schedule an immediate backup attempt for all pending updates. This is
+ * primarily intended for transports to use when they detect a suitable
+ * opportunity for doing a backup pass. If there are no pending updates to
+ * be sent, no action will be taken. Even if some updates are pending, the
+ * transport will still be asked to confirm via the usual requestBackupTime()
+ * method.
+ *
+ *
Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ void backupNow();
+
+ /**
+ * Identify the currently selected transport. Callers must hold the
+ * android.permission.BACKUP permission to use this method.
+ */
+ String getCurrentTransport();
+
+ /**
+ * Request a list of all available backup transports' names. Callers must
+ * hold the android.permission.BACKUP permission to use this method.
+ */
+ String[] listAllTransports();
+
+ /**
+ * Specify the current backup transport. Callers must hold the
+ * android.permission.BACKUP permission to use this method.
+ *
+ * @param transport The name of the transport to select. This should be one
+ * of {@link BackupManager.TRANSPORT_GOOGLE} or {@link BackupManager.TRANSPORT_ADB}.
+ * @return The name of the previously selected transport. If the given transport
+ * name is not one of the currently available transports, no change is made to
+ * the current transport setting and the method returns null.
*/
- oneway void dataChanged(String packageName);
+ String selectBackupTransport(String transport);
/**
- * Schedule a full backup of the given package.
- * !!! TODO: protect with a signature-or-system permission?
+ * Begin a restore session with the given transport (which may differ from the
+ * currently-active backup transport).
+ *
+ * @param transport The name of the transport to use for the restore operation.
+ * @return An interface to the restore session, or null on error.
*/
- oneway void scheduleFullBackup(String packageName);
+ IRestoreSession beginRestoreSession(String transportID);
}
diff --git a/core/java/android/backup/IRestoreObserver.aidl b/core/java/android/backup/IRestoreObserver.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..59e59fc1f70aa62c0352e78c9a456adf73a439df
--- /dev/null
+++ b/core/java/android/backup/IRestoreObserver.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2009 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.backup;
+
+/**
+ * Callback class for receiving progress reports during a restore operation.
+ *
+ * @hide
+ */
+interface IRestoreObserver {
+ /**
+ * The restore operation has begun.
+ *
+ * @param numPackages The total number of packages being processed in
+ * this restore operation.
+ */
+ void restoreStarting(int numPackages);
+
+ /**
+ * An indication of which package is being restored currently, out of the
+ * total number provided in the restoreStarting() callback. This method
+ * is not guaranteed to be called.
+ *
+ * @param nowBeingRestored The index, between 1 and the numPackages parameter
+ * to the restoreStarting() callback, of the package now being restored.
+ */
+ void onUpdate(int nowBeingRestored);
+
+ /**
+ * The restore operation has completed.
+ *
+ * @param error Zero on success; a nonzero error code if the restore operation
+ * as a whole failed.
+ */
+ void restoreFinished(int error);
+}
diff --git a/core/java/android/backup/IRestoreSession.aidl b/core/java/android/backup/IRestoreSession.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..2a1fbc179933c3114dfb22cd50489a23964a58b7
--- /dev/null
+++ b/core/java/android/backup/IRestoreSession.aidl
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2009 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.backup;
+
+import android.backup.RestoreSet;
+import android.backup.IRestoreObserver;
+
+/**
+ * Binder interface used by clients who wish to manage a restore operation. Every
+ * method in this interface requires the android.permission.BACKUP permission.
+ *
+ * {@hide}
+ */
+interface IRestoreSession {
+ /**
+ * Ask the current transport what the available restore sets are.
+ *
+ * @return A bundle containing two elements: an int array under the key
+ * "tokens" whose entries are a transport-private identifier for each backup set;
+ * and a String array under the key "names" whose entries are the user-meaningful
+ * text corresponding to the backup sets at each index in the tokens array.
+ */
+ RestoreSet[] getAvailableRestoreSets();
+
+ /**
+ * Restore the given set onto the device, replacing the current data of any app
+ * contained in the restore set with the data previously backed up.
+ *
+ * @param token The token from {@link getAvailableRestoreSets()} corresponding to
+ * the restore set that should be used.
+ * @param observer If non-null, this binder points to an object that will receive
+ * progress callbacks during the restore operation.
+ */
+ int performRestore(long token, IRestoreObserver observer);
+
+ /**
+ * End this restore session. After this method is called, the IRestoreSession binder
+ * is no longer valid.
+ */
+ void endRestoreSession();
+}
diff --git a/core/java/android/backup/RestoreSet.aidl b/core/java/android/backup/RestoreSet.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..42e77bfe59901b933632815c0323e2eee95eb7b7
--- /dev/null
+++ b/core/java/android/backup/RestoreSet.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2009 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.backup;
+
+parcelable RestoreSet;
\ No newline at end of file
diff --git a/core/java/android/backup/RestoreSet.java b/core/java/android/backup/RestoreSet.java
new file mode 100644
index 0000000000000000000000000000000000000000..eeca148667f3a9dd018496e43cca933e684255e9
--- /dev/null
+++ b/core/java/android/backup/RestoreSet.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2009 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.backup;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Descriptive information about a set of backed-up app data available for restore.
+ * Used by IRestoreSession clients.
+ *
+ * @hide
+ */
+public class RestoreSet implements Parcelable {
+ /**
+ * Name of this restore set. May be user generated, may simply be the name
+ * of the handset model, e.g. "T-Mobile G1".
+ */
+ public String name;
+
+ /**
+ * Identifier of the device whose data this is. This will be as unique as
+ * is practically possible; for example, it might be an IMEI.
+ */
+ public String device;
+
+ /**
+ * Token that identifies this backup set unambiguously to the backup/restore
+ * transport. This is guaranteed to be valid for the duration of a restore
+ * session, but is meaningless once the session has ended.
+ */
+ public long token;
+
+
+ public RestoreSet() {
+ // Leave everything zero / null
+ }
+
+ public RestoreSet(String _name, String _dev, long _token) {
+ name = _name;
+ device = _dev;
+ token = _token;
+ }
+
+
+ // Parcelable implementation
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(name);
+ out.writeString(device);
+ out.writeLong(token);
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public RestoreSet createFromParcel(Parcel in) {
+ return new RestoreSet(in);
+ }
+
+ public RestoreSet[] newArray(int size) {
+ return new RestoreSet[size];
+ }
+ };
+
+ private RestoreSet(Parcel in) {
+ name = in.readString();
+ device = in.readString();
+ token = in.readLong();
+ }
+}
diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java
index 8627f08cf57b16884ace6386e9d93e73895ac22e..4a7b399a5bde0aeeedffac378472f0c343e8013a 100644
--- a/core/java/android/backup/SharedPreferencesBackupHelper.java
+++ b/core/java/android/backup/SharedPreferencesBackupHelper.java
@@ -18,24 +18,51 @@ package android.backup;
import android.content.Context;
import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import java.io.File;
import java.io.FileDescriptor;
/** @hide */
-public class SharedPreferencesBackupHelper {
- public static void performBackup(Context context,
- ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot,
- BackupDataOutput data, String[] prefGroups) {
- String basePath = "/xxx"; //context.getPreferencesDir();
+public class SharedPreferencesBackupHelper extends FileBackupHelperBase implements BackupHelper {
+ private static final String TAG = "SharedPreferencesBackupHelper";
+ private Context mContext;
+ private String[] mPrefGroups;
+
+ public SharedPreferencesBackupHelper(Context context, String... prefGroups) {
+ super(context);
+
+ mContext = context;
+ mPrefGroups = prefGroups;
+ }
+
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ Context context = mContext;
+
// make filenames for the prefGroups
+ String[] prefGroups = mPrefGroups;
final int N = prefGroups.length;
String[] files = new String[N];
for (int i=0; i
+ * Call mOpenHelper.getWritableDatabase() and mDb.beginTransaction().
+ * {@link #endTransaction} MUST be called after calling this method.
+ * Those methods should be used like this:
+ *
+ *
+ *
+ * boolean successful = false;
+ * beginTransaction();
+ * try {
+ * // Do something related to mDb
+ * successful = true;
+ * return ret;
+ * } finally {
+ * endTransaction(successful);
+ * }
+ *
+ *
+ * @hide This method is dangerous from the view of database manipulation, though using
+ * this makes batch insertion/update/delete much faster.
+ */
+ public final void beginTransaction() {
mDb = mOpenHelper.getWritableDatabase();
mDb.beginTransaction();
+ }
+
+ /**
+ *
+ * Call mDb.endTransaction(). If successful is true, try to call
+ * mDb.setTransactionSuccessful() before calling mDb.endTransaction().
+ * This method MUST be used with {@link #beginTransaction()}.
+ *
+ *
+ * @hide This method is dangerous from the view of database manipulation, though using
+ * this makes batch insertion/update/delete much faster.
+ */
+ public final void endTransaction(boolean successful) {
try {
- if (isTemporary() && mSyncState.matches(url)) {
- int numRows = mSyncState.asContentProvider().update(
- url, values, selection, selectionArgs);
+ if (successful) {
+ // setTransactionSuccessful() must be called just once during opening the
+ // transaction.
mDb.setTransactionSuccessful();
- return numRows;
}
+ } finally {
+ mDb.endTransaction();
+ }
+ }
- int result = updateInternal(url, values, selection, selectionArgs);
- mDb.setTransactionSuccessful();
+ @Override
+ public final int update(final Uri uri, final ContentValues values,
+ final String selection, final String[] selectionArgs) {
+ boolean successful = false;
+ beginTransaction();
+ try {
+ int ret = nonTransactionalUpdate(uri, values, selection, selectionArgs);
+ successful = true;
+ return ret;
+ } finally {
+ endTransaction(successful);
+ }
+ }
- if (!isTemporary() && result > 0) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
+ /**
+ * @hide
+ */
+ public final int nonTransactionalUpdate(final Uri uri, final ContentValues values,
+ final String selection, final String[] selectionArgs) {
+ if (isTemporary() && mSyncState.matches(uri)) {
+ int numRows = mSyncState.asContentProvider().update(
+ uri, values, selection, selectionArgs);
+ return numRows;
+ }
- return result;
- } finally {
- mDb.endTransaction();
+ int result = updateInternal(uri, values, selection, selectionArgs);
+ if (!isTemporary() && result > 0) {
+ getContext().getContentResolver().notifyChange(uri, null /* observer */,
+ changeRequiresLocalSync(uri));
}
+
+ return result;
}
@Override
- public final int delete(final Uri url, final String selection,
+ public final int delete(final Uri uri, final String selection,
final String[] selectionArgs) {
- mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransaction();
+ boolean successful = false;
+ beginTransaction();
try {
- if (isTemporary() && mSyncState.matches(url)) {
- int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
- mDb.setTransactionSuccessful();
- return numRows;
- }
- int result = deleteInternal(url, selection, selectionArgs);
- mDb.setTransactionSuccessful();
- if (!isTemporary() && result > 0) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
- return result;
+ int ret = nonTransactionalDelete(uri, selection, selectionArgs);
+ successful = true;
+ return ret;
} finally {
- mDb.endTransaction();
+ endTransaction(successful);
}
}
+ /**
+ * @hide
+ */
+ public final int nonTransactionalDelete(final Uri uri, final String selection,
+ final String[] selectionArgs) {
+ if (isTemporary() && mSyncState.matches(uri)) {
+ int numRows = mSyncState.asContentProvider().delete(uri, selection, selectionArgs);
+ return numRows;
+ }
+ int result = deleteInternal(uri, selection, selectionArgs);
+ if (!isTemporary() && result > 0) {
+ getContext().getContentResolver().notifyChange(uri, null /* observer */,
+ changeRequiresLocalSync(uri));
+ }
+ return result;
+ }
+
@Override
- public final Uri insert(final Uri url, final ContentValues values) {
- mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransaction();
+ public final Uri insert(final Uri uri, final ContentValues values) {
+ boolean successful = false;
+ beginTransaction();
try {
- if (isTemporary() && mSyncState.matches(url)) {
- Uri result = mSyncState.asContentProvider().insert(url, values);
- mDb.setTransactionSuccessful();
- return result;
- }
- Uri result = insertInternal(url, values);
- mDb.setTransactionSuccessful();
- if (!isTemporary() && result != null) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
- return result;
+ Uri ret = nonTransactionalInsert(uri, values);
+ successful = true;
+ return ret;
} finally {
- mDb.endTransaction();
+ endTransaction(successful);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public final Uri nonTransactionalInsert(final Uri uri, final ContentValues values) {
+ if (isTemporary() && mSyncState.matches(uri)) {
+ Uri result = mSyncState.asContentProvider().insert(uri, values);
+ return result;
+ }
+ Uri result = insertInternal(uri, values);
+ if (!isTemporary() && result != null) {
+ getContext().getContentResolver().notifyChange(uri, null /* observer */,
+ changeRequiresLocalSync(uri));
}
+ return result;
}
@Override
diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java
index 700f1d88241c18333642c74549a89468c88ddb91..9c760d94ad63197df938464e3891724723bc4768 100644
--- a/core/java/android/content/AbstractTableMerger.java
+++ b/core/java/android/content/AbstractTableMerger.java
@@ -61,8 +61,10 @@ public abstract class AbstractTableMerger
_SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=?";
private static final String SELECT_BY_ID = BaseColumns._ID +"=?";
- private static final String SELECT_UNSYNCED = ""
- + _SYNC_DIRTY + " > 0 and (" + _SYNC_ACCOUNT + "=? or " + _SYNC_ACCOUNT + " is null)";
+ private static final String SELECT_UNSYNCED =
+ "(" + _SYNC_ACCOUNT + " IS NULL OR " + _SYNC_ACCOUNT + "=?) AND "
+ + "(" + _SYNC_ID + " IS NULL OR (" + _SYNC_DIRTY + " > 0 AND "
+ + _SYNC_VERSION + " IS NOT NULL))";
public AbstractTableMerger(SQLiteDatabase database,
String table, Uri tableURL, String deletedTable,
@@ -365,26 +367,32 @@ public abstract class AbstractTableMerger
if (!TextUtils.isEmpty(localSyncID)) {
// An existing server item has changed
- boolean recordChanged = (localSyncVersion == null) ||
- !serverSyncVersion.equals(localSyncVersion);
- if (recordChanged) {
- if (localSyncDirty) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "remote record " + serverSyncId
- + " conflicts with local _sync_id " + localSyncID
- + ", local _id " + localRowId);
+ // If serverSyncVersion is null, there is no edit URL;
+ // server won't let this change be written.
+ // Just hold onto it, I guess, in case the server permissions
+ // change later.
+ if (serverSyncVersion != null) {
+ boolean recordChanged = (localSyncVersion == null) ||
+ !serverSyncVersion.equals(localSyncVersion);
+ if (recordChanged) {
+ if (localSyncDirty) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "remote record " + serverSyncId
+ + " conflicts with local _sync_id " + localSyncID
+ + ", local _id " + localRowId);
+ }
+ conflict = true;
+ } else {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ "remote record " +
+ serverSyncId +
+ " updates local _sync_id " +
+ localSyncID + ", local _id " +
+ localRowId);
+ }
+ update = true;
}
- conflict = true;
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG,
- "remote record " +
- serverSyncId +
- " updates local _sync_id " +
- localSyncID + ", local _id " +
- localRowId);
- }
- update = true;
}
}
} else {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f2ad2485d0e1ceb7df4f282c8b03bc42ce1c1b47..9e37ae448731cc82897c57eb5e26591f14ba6498 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -16,6 +16,7 @@
package android.content;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
@@ -233,6 +234,9 @@ public abstract class Context {
/** Return the name of this application's package. */
public abstract String getPackageName();
+ /** Return the full application info for this context's package. */
+ public abstract ApplicationInfo getApplicationInfo();
+
/**
* {@hide}
* Return the full path to this context's resource files. This is the ZIP files
@@ -254,11 +258,19 @@ public abstract class Context {
* Note: this is not generally useful for applications, since they should
* not be directly accessing the file system.
*
- *
* @return String Path to the code and assets.
*/
public abstract String getPackageCodePath();
+ /**
+ * {@hide}
+ * Return the full path to the shared prefs file for the given prefs group name.
+ *
+ *
Note: this is not generally useful for applications, since they should
+ * not be directly accessing the file system.
+ */
+ public abstract File getSharedPrefsFile(String name);
+
/**
* Retrieve and hold the contents of the preferences file 'name', returning
* a SharedPreferences through which you can retrieve and modify its
@@ -526,16 +538,6 @@ public abstract class Context {
*/
public abstract int getWallpaperDesiredMinimumHeight();
- /**
- * Returns the scale in which the application will be drawn on the
- * screen. This is usually 1.0f if the application supports the device's
- * resolution/density. This will be 1.5f, for example, if the application
- * that supports only 160 density runs on 240 density screen.
- *
- * @hide
- */
- public abstract float getApplicationScale();
-
/**
* Change the current system wallpaper to a bitmap. The given bitmap is
* converted to a PNG and stored as the wallpaper. On success, the intent
@@ -1133,6 +1135,15 @@ public abstract class Context {
* @see android.app.NotificationManager
*/
public static final String NOTIFICATION_SERVICE = "notification";
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.accessibility.AccessibilityManager} for giving the user
+ * feedback for UI events through the registered event listeners.
+ *
+ * @see #getSystemService
+ * @see android.view.accessibility.AccessibilityManager
+ */
+ public static final String ACCESSIBILITY_SERVICE = "accessibility";
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.NotificationManager} for controlling keyguard.
@@ -1643,6 +1654,13 @@ public abstract class Context {
* with extreme care!
*/
public static final int CONTEXT_IGNORE_SECURITY = 0x00000002;
+
+ /**
+ * Flag for use with {@link #createPackageContext}: a restricted context may
+ * disable specific features. For instance, a View associated with a restricted
+ * context would ignore particular XML attributes.
+ */
+ public static final int CONTEXT_RESTRICTED = 0x00000004;
/**
* Return a new Context object for the given application name. This
@@ -1671,4 +1689,15 @@ public abstract class Context {
*/
public abstract Context createPackageContext(String packageName,
int flags) throws PackageManager.NameNotFoundException;
+
+ /**
+ * Indicates whether this Context is restricted.
+ *
+ * @return True if this Context is restricted, false otherwise.
+ *
+ * @see #CONTEXT_RESTRICTED
+ */
+ public boolean isRestricted() {
+ return false;
+ }
}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 25b2caeb72790f3b490d52a21e466fc2930769e6..45a082a9520ee5c35c8804d9986b0357f1afd4ae 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -16,6 +16,7 @@
package android.content;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
@@ -119,6 +120,11 @@ public class ContextWrapper extends Context {
return mBase.getPackageName();
}
+ @Override
+ public ApplicationInfo getApplicationInfo() {
+ return mBase.getApplicationInfo();
+ }
+
@Override
public String getPackageResourcePath() {
return mBase.getPackageResourcePath();
@@ -129,6 +135,11 @@ public class ContextWrapper extends Context {
return mBase.getPackageCodePath();
}
+ @Override
+ public File getSharedPrefsFile(String name) {
+ return mBase.getSharedPrefsFile(name);
+ }
+
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
@@ -420,11 +431,8 @@ public class ContextWrapper extends Context {
return mBase.createPackageContext(packageName, flags);
}
- /**
- * @hide
- */
@Override
- public float getApplicationScale() {
- return mBase.getApplicationScale();
+ public boolean isRestricted() {
+ return mBase.isRestricted();
}
}
diff --git a/core/java/android/content/IIntentReceiver.aidl b/core/java/android/content/IIntentReceiver.aidl
new file mode 100755
index 0000000000000000000000000000000000000000..443db2d06d0f603e5a5a9490bd9187d439262a89
--- /dev/null
+++ b/core/java/android/content/IIntentReceiver.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2006 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.content;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * System private API for dispatching intent broadcasts. This is given to the
+ * activity manager as part of registering for an intent broadcasts, and is
+ * called when it receives intents.
+ *
+ * {@hide}
+ */
+oneway interface IIntentReceiver {
+ void performReceive(in Intent intent, int resultCode,
+ String data, in Bundle extras, boolean ordered);
+}
+
diff --git a/core/java/android/content/IIntentSender.aidl b/core/java/android/content/IIntentSender.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..b7da47219ce4725473eda23a51d1b57ecf91a960
--- /dev/null
+++ b/core/java/android/content/IIntentSender.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2006 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.content;
+
+import android.content.IIntentReceiver;
+import android.content.Intent;
+
+/** @hide */
+interface IIntentSender {
+ int send(int code, in Intent intent, String resolvedType,
+ IIntentReceiver finishedReceiver);
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 24262f51197d5f44b1b6c2797d64fcc5176a24c8..263f9279e69adc8abb77a5ed3903d69269901bbf 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -240,35 +240,35 @@ import java.util.Set;
*
* <activity class=".NotesList" android:label="@string/title_notes_list">
* <intent-filter>
- * <action android:value="android.intent.action.MAIN" />
- * <category android:value="android.intent.category.LAUNCHER" />
+ * <action android:name="android.intent.action.MAIN" />
+ * <category android:name="android.intent.category.LAUNCHER" />
* </intent-filter>
* <intent-filter>
- * <action android:value="android.intent.action.VIEW" />
- * <action android:value="android.intent.action.EDIT" />
- * <action android:value="android.intent.action.PICK" />
- * <category android:value="android.intent.category.DEFAULT" />
- * <type android:value="vnd.android.cursor.dir/vnd.google.note" />
+ * <action android:name="android.intent.action.VIEW" />
+ * <action android:name="android.intent.action.EDIT" />
+ * <action android:name="android.intent.action.PICK" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
* </intent-filter>
* <intent-filter>
- * <action android:value="android.intent.action.GET_CONTENT" />
- * <category android:value="android.intent.category.DEFAULT" />
- * <type android:value="vnd.android.cursor.item/vnd.google.note" />
+ * <action android:name="android.intent.action.GET_CONTENT" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
* </intent-filter>
* </activity>
*
* <activity class=".NoteEditor" android:label="@string/title_note">
* <intent-filter android:label="@string/resolve_edit">
- * <action android:value="android.intent.action.VIEW" />
- * <action android:value="android.intent.action.EDIT" />
- * <category android:value="android.intent.category.DEFAULT" />
- * <type android:value="vnd.android.cursor.item/vnd.google.note" />
+ * <action android:name="android.intent.action.VIEW" />
+ * <action android:name="android.intent.action.EDIT" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
* </intent-filter>
*
* <intent-filter>
- * <action android:value="android.intent.action.INSERT" />
- * <category android:value="android.intent.category.DEFAULT" />
- * <type android:value="vnd.android.cursor.dir/vnd.google.note" />
+ * <action android:name="android.intent.action.INSERT" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
* </intent-filter>
*
* </activity>
@@ -276,11 +276,11 @@ import java.util.Set;
* <activity class=".TitleEditor" android:label="@string/title_edit_title"
* android:theme="@android:style/Theme.Dialog">
* <intent-filter android:label="@string/resolve_title">
- * <action android:value="com.android.notepad.action.EDIT_TITLE" />
- * <category android:value="android.intent.category.DEFAULT" />
- * <category android:value="android.intent.category.ALTERNATIVE" />
- * <category android:value="android.intent.category.SELECTED_ALTERNATIVE" />
- * <type android:value="vnd.android.cursor.item/vnd.google.note" />
+ * <action android:name="com.android.notepad.action.EDIT_TITLE" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <category android:name="android.intent.category.ALTERNATIVE" />
+ * <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
+ * <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
* </intent-filter>
* </activity>
*
@@ -294,8 +294,8 @@ import java.util.Set;
*
*
* <intent-filter>
- * <action android:value="{@link #ACTION_MAIN android.intent.action.MAIN}" />
- * <category android:value="{@link #CATEGORY_LAUNCHER android.intent.category.LAUNCHER}" />
+ * <action android:name="{@link #ACTION_MAIN android.intent.action.MAIN}" />
+ * <category android:name="{@link #CATEGORY_LAUNCHER android.intent.category.LAUNCHER}" />
* </intent-filter>
* This provides a top-level entry into the NotePad application: the standard
* MAIN action is a main entry point (not requiring any other information in
@@ -303,11 +303,11 @@ import java.util.Set;
* listed in the application launcher.
*
* <intent-filter>
- * <action android:value="{@link #ACTION_VIEW android.intent.action.VIEW}" />
- * <action android:value="{@link #ACTION_EDIT android.intent.action.EDIT}" />
- * <action android:value="{@link #ACTION_PICK android.intent.action.PICK}" />
- * <category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
- * <type android:value="vnd.android.cursor.dir/vnd.google.note" />
+ * <action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" />
+ * <action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" />
+ * <action android:name="{@link #ACTION_PICK android.intent.action.PICK}" />
+ * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <data mimeType:name="vnd.android.cursor.dir/vnd.google.note" />
* </intent-filter>
* This declares the things that the activity can do on a directory of
* notes. The type being supported is given with the <type> tag, where
@@ -322,9 +322,9 @@ import java.util.Set;
* activity when its component name is not explicitly specified.
*
* <intent-filter>
- * <action android:value="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" />
- * <category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
- * <type android:value="vnd.android.cursor.item/vnd.google.note" />
+ * <action android:name="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" />
+ * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
* </intent-filter>
* This filter describes the ability return to the caller a note selected by
* the user without needing to know where it came from. The data type
@@ -371,10 +371,10 @@ import java.util.Set;
*
*
* <intent-filter android:label="@string/resolve_edit">
- * <action android:value="{@link #ACTION_VIEW android.intent.action.VIEW}" />
- * <action android:value="{@link #ACTION_EDIT android.intent.action.EDIT}" />
- * <category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
- * <type android:value="vnd.android.cursor.item/vnd.google.note" />
+ * <action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" />
+ * <action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" />
+ * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
* </intent-filter>
* The first, primary, purpose of this activity is to let the user interact
* with a single note, as decribed by the MIME type
@@ -384,9 +384,9 @@ import java.util.Set;
* specifying its component.
*
* <intent-filter>
- * <action android:value="{@link #ACTION_INSERT android.intent.action.INSERT}" />
- * <category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
- * <type android:value="vnd.android.cursor.dir/vnd.google.note" />
+ * <action android:name="{@link #ACTION_INSERT android.intent.action.INSERT}" />
+ * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
* </intent-filter>
* The secondary use of this activity is to insert a new note entry into
* an existing directory of notes. This is used when the user creates a new
@@ -422,11 +422,11 @@ import java.util.Set;
*
*
* <intent-filter android:label="@string/resolve_title">
- * <action android:value="com.android.notepad.action.EDIT_TITLE" />
- * <category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
- * <category android:value="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" />
- * <category android:value="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" />
- * <type android:value="vnd.android.cursor.item/vnd.google.note" />
+ * <action android:name="com.android.notepad.action.EDIT_TITLE" />
+ * <category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" />
+ * <category android:name="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" />
+ * <category android:name="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" />
+ * <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
* </intent-filter>
*
* In the single intent template here, we
@@ -509,8 +509,8 @@ import java.util.Set;
*
- {@link #ACTION_UID_REMOVED}
*
- {@link #ACTION_BATTERY_CHANGED}
*
- {@link #ACTION_POWER_CONNECTED}
- *
- {@link #ACTION_POWER_DISCONNECTED}
- *
- {@link #ACTION_SHUTDOWN}
+ *
- {@link #ACTION_POWER_DISCONNECTED}
+ *
- {@link #ACTION_SHUTDOWN}
*
*
*
Standard Categories
@@ -914,6 +914,23 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_SEND = "android.intent.action.SEND";
+ /**
+ * Activity Action: Deliver multiple data to someone else.
+ *
+ * Like ACTION_SEND, except the data is multiple.
+ *
+ * Input: {@link #getType} is the MIME type of the data being sent.
+ * get*ArrayListExtra can have either a {@link #EXTRA_TEXT} or {@link
+ * #EXTRA_STREAM} field, containing the data to be sent.
+ *
+ * Optional standard extras, which may be interpreted by some recipients as
+ * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
+ * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}.
+ *
+ * Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SEND_MULTIPLE = "android.intent.action.SEND_MULTIPLE";
/**
* Activity Action: Handle an incoming phone call.
*
Input: nothing.
@@ -1059,6 +1076,36 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_APP_ERROR = "android.intent.action.APP_ERROR";
+
+ /**
+ * Activity Action: Show power usage information to the user.
+ *
Input: Nothing.
+ *
Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
+
+ /**
+ * Activity Action: Setup wizard to launch after a platform update. This
+ * activity should have a string meta-data field associated with it,
+ * {@link #METADATA_SETUP_VERSION}, which defines the current version of
+ * the platform for setup. The activity will be launched only if
+ * {@link android.provider.Settings.Secure#LAST_SETUP_SHOWN} is not the
+ * same value.
+ *
Input: Nothing.
+ *
Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
+
+ /**
+ * A string associated with a {@link #ACTION_UPGRADE_SETUP} activity
+ * describing the last run version of the platform that was setup.
+ * @hide
+ */
+ public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent broadcast actions (see action variable).
@@ -1263,6 +1310,13 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
+ /**
+ * Broadcast Action: Indicates the battery is now okay after being low.
+ * This will be sent after {@link #ACTION_BATTERY_LOW} once the battery has
+ * gone back up to an okay state.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_BATTERY_OKAY = "android.intent.action.BATTERY_OKAY";
/**
* Broadcast Action: External power has been connected to the device.
* This is intended for applications that wish to register specifically to this notification.
@@ -1277,10 +1331,10 @@ public class Intent implements Parcelable {
* This is intended for applications that wish to register specifically to this notification.
* Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
* stay active to receive this notification. This action can be used to implement actions
- * that wait until power is available to trigger.
+ * that wait until power is available to trigger.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED";
+ public static final String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED";
/**
* Broadcast Action: Device is shutting down.
* This is broadcast when the device is being shut down (completely turned
@@ -1289,7 +1343,7 @@ public class Intent implements Parcelable {
* to handle this, since the forground activity will be paused as well.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN";
+ public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN";
/**
* Broadcast Action: Indicates low memory condition on the device
*/
@@ -1552,6 +1606,16 @@ public class Intent implements Parcelable {
public static final String ACTION_REBOOT =
"android.intent.action.REBOOT";
+ /**
+ * @hide
+ * TODO: This will be unhidden in a later CL.
+ * Broadcast Action: The TextToSpeech synthesizer has completed processing
+ * all of the text in the speech queue.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
+ "android.intent.action.TTS_QUEUE_PROCESSING_COMPLETED";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -1791,23 +1855,23 @@ public class Intent implements Parcelable {
* delivered.
*/
public static final String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
-
+
/**
* Used as a parcelable extra field in {@link #ACTION_APP_ERROR}, containing
* the bug report.
- *
+ *
* @hide
*/
public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
/**
- * Used as a string extra field when sending an intent to PackageInstaller to install a
+ * Used as a string extra field when sending an intent to PackageInstaller to install a
* package. Specifies the installer package name; this package will receive the
* {@link #ACTION_APP_ERROR} intent.
- *
+ *
* @hide
*/
- public static final String EXTRA_INSTALLER_PACKAGE_NAME
+ public static final String EXTRA_INSTALLER_PACKAGE_NAME
= "android.intent.extra.INSTALLER_PACKAGE_NAME";
// ---------------------------------------------------------------------
@@ -2039,11 +2103,26 @@ public class Intent implements Parcelable {
*/
public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x20000000;
+ // ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // toUri() and parseUri() options.
+
+ /**
+ * Flag for use with {@link #toUri} and {@link #parseUri}: the URI string
+ * always has the "intent:" scheme. This syntax can be used when you want
+ * to later disambiguate between URIs that are intended to describe an
+ * Intent vs. all others that should be treated as raw URIs. When used
+ * with {@link #parseUri}, any other scheme will result in a generic
+ * VIEW action for that raw URI.
+ */
+ public static final int URI_INTENT_SCHEME = 1<<0;
+
// ---------------------------------------------------------------------
private String mAction;
private Uri mData;
private String mType;
+ private String mPackage;
private ComponentName mComponent;
private int mFlags;
private HashSet mCategories;
@@ -2064,6 +2143,7 @@ public class Intent implements Parcelable {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
+ this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
this.mFlags = o.mFlags;
if (o.mCategories != null) {
@@ -2083,6 +2163,7 @@ public class Intent implements Parcelable {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
+ this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
if (o.mCategories != null) {
this.mCategories = new HashSet(o.mCategories);
@@ -2182,24 +2263,51 @@ public class Intent implements Parcelable {
mComponent = new ComponentName(packageContext, cls);
}
+ /**
+ * Call {@link #parseUri} with 0 flags.
+ * @deprecated Use {@link #parseUri} instead.
+ */
+ @Deprecated
+ public static Intent getIntent(String uri) throws URISyntaxException {
+ return parseUri(uri, 0);
+ }
+
/**
* Create an intent from a URI. This URI may encode the action,
- * category, and other intent fields, if it was returned by toURI(). If
- * the Intent was not generate by toURI(), its data will be the entire URI
- * and its action will be ACTION_VIEW.
+ * category, and other intent fields, if it was returned by
+ * {@link #toUri}.. If the Intent was not generate by toUri(), its data
+ * will be the entire URI and its action will be ACTION_VIEW.
*
* The URI given here must not be relative -- that is, it must include
* the scheme and full path.
*
* @param uri The URI to turn into an Intent.
+ * @param flags Additional processing flags. Either 0 or
*
* @return Intent The newly created Intent object.
*
- * @see #toURI
+ * @throws URISyntaxException Throws URISyntaxError if the basic URI syntax
+ * it bad (as parsed by the Uri class) or the Intent data within the
+ * URI is invalid.
+ *
+ * @see #toUri
*/
- public static Intent getIntent(String uri) throws URISyntaxException {
+ public static Intent parseUri(String uri, int flags) throws URISyntaxException {
int i = 0;
try {
+ // Validate intent scheme for if requested.
+ if ((flags&URI_INTENT_SCHEME) != 0) {
+ if (!uri.startsWith("intent:")) {
+ Intent intent = new Intent(ACTION_VIEW);
+ try {
+ intent.setData(Uri.parse(uri));
+ } catch (IllegalArgumentException e) {
+ throw new URISyntaxException(uri, e.getMessage());
+ }
+ return intent;
+ }
+ }
+
// simple case
i = uri.lastIndexOf("#");
if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri));
@@ -2211,16 +2319,15 @@ public class Intent implements Parcelable {
Intent intent = new Intent(ACTION_VIEW);
// fetch data part, if present
- if (i > 0) {
- intent.mData = Uri.parse(uri.substring(0, i));
- }
+ String data = i >= 0 ? uri.substring(0, i) : null;
+ String scheme = null;
i += "#Intent;".length();
// loop over contents of Intent, all name=value;
while (!uri.startsWith("end", i)) {
int eq = uri.indexOf('=', i);
int semi = uri.indexOf(';', eq);
- String value = uri.substring(eq + 1, semi);
+ String value = Uri.decode(uri.substring(eq + 1, semi));
// action
if (uri.startsWith("action=", i)) {
@@ -2242,15 +2349,24 @@ public class Intent implements Parcelable {
intent.mFlags = Integer.decode(value).intValue();
}
+ // package
+ else if (uri.startsWith("package=", i)) {
+ intent.mPackage = value;
+ }
+
// component
else if (uri.startsWith("component=", i)) {
intent.mComponent = ComponentName.unflattenFromString(value);
}
+ // scheme
+ else if (uri.startsWith("scheme=", i)) {
+ scheme = value;
+ }
+
// extra
else {
String key = Uri.decode(uri.substring(i + 2, eq));
- value = Uri.decode(value);
// create Bundle if it doesn't already exist
if (intent.mExtras == null) intent.mExtras = new Bundle();
Bundle b = intent.mExtras;
@@ -2271,6 +2387,23 @@ public class Intent implements Parcelable {
i = semi + 1;
}
+ if (data != null) {
+ if (data.startsWith("intent:")) {
+ data = data.substring(7);
+ if (scheme != null) {
+ data = scheme + ':' + data;
+ }
+ }
+
+ if (data.length() > 0) {
+ try {
+ intent.mData = Uri.parse(data);
+ } catch (IllegalArgumentException e) {
+ throw new URISyntaxException(uri, e.getMessage());
+ }
+ }
+ }
+
return intent;
} catch (IndexOutOfBoundsException e) {
@@ -3083,6 +3216,20 @@ public class Intent implements Parcelable {
return mFlags;
}
+ /**
+ * Retrieve the application package name this Intent is limited to. When
+ * resolving an Intent, if non-null this limits the resolution to only
+ * components in the given application package.
+ *
+ * @return The name of the application package for the Intent.
+ *
+ * @see #resolveActivity
+ * @see #setPackage
+ */
+ public String getPackage() {
+ return mPackage;
+ }
+
/**
* Retrieve the concrete component associated with the intent. When receiving
* an intent, this is the component that was found to best handle it (that is,
@@ -3118,6 +3265,9 @@ public class Intent implements Parcelable {
*
If {@link #addCategory} has added any categories, the activity must
* handle ALL of the categories specified.
*
+ *
If {@link #getPackage} is non-NULL, only activity components in
+ * that application package will be considered.
+ *
*
If there are no activities that satisfy all of these conditions, a
* null string is returned.
*
@@ -3239,7 +3389,7 @@ public class Intent implements Parcelable {
* only specify a type and not data, for example to indicate the type of
* data to return. This method automatically clears any data that was
* previously set by {@link #setData}.
- *
+ *
*
Note: MIME type matching in the Android framework is
* case-sensitive, unlike formal RFC MIME types. As a result,
* you should always write your MIME types with lower case letters,
@@ -4088,6 +4238,27 @@ public class Intent implements Parcelable {
return this;
}
+ /**
+ * (Usually optional) Set an explicit application package name that limits
+ * the components this Intent will resolve to. If left to the default
+ * value of null, all components in all applications will considered.
+ * If non-null, the Intent can only match the components in the given
+ * application package.
+ *
+ * @param packageName The name of the application package to handle the
+ * intent, or null to allow any application package.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getPackage
+ * @see #resolveActivity
+ */
+ public Intent setPackage(String packageName) {
+ mPackage = packageName;
+ return this;
+ }
+
/**
* (Usually optional) Explicitly set the component to handle the intent.
* If left with the default value of null, the system will determine the
@@ -4199,6 +4370,12 @@ public class Intent implements Parcelable {
*/
public static final int FILL_IN_COMPONENT = 1<<3;
+ /**
+ * Use with {@link #fillIn} to allow the current package value to be
+ * overwritten, even if it is already set.
+ */
+ public static final int FILL_IN_PACKAGE = 1<<4;
+
/**
* Copy the contents of other in to this object, but only
* where fields are not defined by this object. For purposes of a field
@@ -4210,14 +4387,15 @@ public class Intent implements Parcelable {
* - data URI and MIME type, as set by {@link #setData(Uri)},
* {@link #setType(String)}, or {@link #setDataAndType(Uri, String)}.
*
- categories, as set by {@link #addCategory}.
+ *
- package, as set by {@link #setPackage}.
*
- component, as set by {@link #setComponent(ComponentName)} or
* related methods.
*
- each top-level name in the associated extras.
*
*
*
In addition, you can use the {@link #FILL_IN_ACTION},
- * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, and
- * {@link #FILL_IN_COMPONENT} to override the restriction where the
+ * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
+ * and {@link #FILL_IN_COMPONENT} to override the restriction where the
* corresponding field will not be replaced if it is already set.
*
*
For example, consider Intent A with {data="foo", categories="bar"}
@@ -4233,32 +4411,39 @@ public class Intent implements Parcelable {
* @param flags Options to control which fields can be filled in.
*
* @return Returns a bit mask of {@link #FILL_IN_ACTION},
- * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, and
- * {@link #FILL_IN_COMPONENT} indicating which fields were changed.
+ * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
+ * and {@link #FILL_IN_COMPONENT} indicating which fields were changed.
*/
public int fillIn(Intent other, int flags) {
int changes = 0;
- if ((mAction == null && other.mAction == null)
- || (flags&FILL_IN_ACTION) != 0) {
+ if (other.mAction != null
+ && (mAction == null || (flags&FILL_IN_ACTION) != 0)) {
mAction = other.mAction;
changes |= FILL_IN_ACTION;
}
- if ((mData == null && mType == null &&
- (other.mData != null || other.mType != null))
- || (flags&FILL_IN_DATA) != 0) {
+ if ((other.mData != null || other.mType != null)
+ && ((mData == null && mType == null)
+ || (flags&FILL_IN_DATA) != 0)) {
mData = other.mData;
mType = other.mType;
changes |= FILL_IN_DATA;
}
- if ((mCategories == null && other.mCategories == null)
- || (flags&FILL_IN_CATEGORIES) != 0) {
+ if (other.mCategories != null
+ && (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) {
if (other.mCategories != null) {
mCategories = new HashSet(other.mCategories);
}
changes |= FILL_IN_CATEGORIES;
}
- if ((mComponent == null && other.mComponent == null)
- || (flags&FILL_IN_COMPONENT) != 0) {
+ if (other.mPackage != null
+ && (mPackage == null || (flags&FILL_IN_PACKAGE) != 0)) {
+ mPackage = other.mPackage;
+ changes |= FILL_IN_PACKAGE;
+ }
+ // Component is special: it can -only- be set if explicitly allowed,
+ // since otherwise the sender could force the intent somewhere the
+ // originator didn't intend.
+ if (other.mComponent != null && (flags&FILL_IN_COMPONENT) != 0) {
mComponent = other.mComponent;
changes |= FILL_IN_COMPONENT;
}
@@ -4373,6 +4558,17 @@ public class Intent implements Parcelable {
}
}
}
+ if (mPackage != other.mPackage) {
+ if (mPackage != null) {
+ if (!mPackage.equals(other.mPackage)) {
+ return false;
+ }
+ } else {
+ if (!other.mPackage.equals(mPackage)) {
+ return false;
+ }
+ }
+ }
if (mComponent != other.mComponent) {
if (mComponent != null) {
if (!mComponent.equals(other.mComponent)) {
@@ -4418,6 +4614,9 @@ public class Intent implements Parcelable {
if (mType != null) {
code += mType.hashCode();
}
+ if (mPackage != null) {
+ code += mPackage.hashCode();
+ }
if (mComponent != null) {
code += mComponent.hashCode();
}
@@ -4444,7 +4643,7 @@ public class Intent implements Parcelable {
toShortString(b, comp, extras);
return b.toString();
}
-
+
/** @hide */
public void toShortString(StringBuilder b, boolean comp, boolean extras) {
boolean first = true;
@@ -4488,6 +4687,13 @@ public class Intent implements Parcelable {
first = false;
b.append("flg=0x").append(Integer.toHexString(mFlags));
}
+ if (mPackage != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("pkg=").append(mPackage);
+ }
if (comp && mComponent != null) {
if (!first) {
b.append(' ');
@@ -4504,28 +4710,87 @@ public class Intent implements Parcelable {
}
}
+ /**
+ * Call {@link #toUri} with 0 flags.
+ * @deprecated Use {@link #toUri} instead.
+ */
+ @Deprecated
public String toURI() {
+ return toUri(0);
+ }
+
+ /**
+ * Convert this Intent into a String holding a URI representation of it.
+ * The returned URI string has been properly URI encoded, so it can be
+ * used with {@link Uri#parse Uri.parse(String)}. The URI contains the
+ * Intent's data as the base URI, with an additional fragment describing
+ * the action, categories, type, flags, package, component, and extras.
+ *
+ * You can convert the returned string back to an Intent with
+ * {@link #getIntent}.
+ *
+ * @param flags Additional operating flags. Either 0 or
+ * {@link #URI_INTENT_SCHEME}.
+ *
+ * @return Returns a URI encoding URI string describing the entire contents
+ * of the Intent.
+ */
+ public String toUri(int flags) {
StringBuilder uri = new StringBuilder(128);
- if (mData != null) uri.append(mData.toString());
+ String scheme = null;
+ if (mData != null) {
+ String data = mData.toString();
+ if ((flags&URI_INTENT_SCHEME) != 0) {
+ final int N = data.length();
+ for (int i=0; i= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+ || c == '.' || c == '-') {
+ continue;
+ }
+ if (c == ':' && i > 0) {
+ // Valid scheme.
+ scheme = data.substring(0, i);
+ uri.append("intent:");
+ data = data.substring(i+1);
+ break;
+ }
+
+ // No scheme.
+ break;
+ }
+ }
+ uri.append(data);
+
+ } else if ((flags&URI_INTENT_SCHEME) != 0) {
+ uri.append("intent:");
+ }
uri.append("#Intent;");
+ if (scheme != null) {
+ uri.append("scheme=").append(scheme).append(';');
+ }
if (mAction != null) {
- uri.append("action=").append(mAction).append(';');
+ uri.append("action=").append(Uri.encode(mAction)).append(';');
}
if (mCategories != null) {
for (String category : mCategories) {
- uri.append("category=").append(category).append(';');
+ uri.append("category=").append(Uri.encode(category)).append(';');
}
}
if (mType != null) {
- uri.append("type=").append(mType).append(';');
+ uri.append("type=").append(Uri.encode(mType, "/")).append(';');
}
if (mFlags != 0) {
uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';');
}
+ if (mPackage != null) {
+ uri.append("package=").append(Uri.encode(mPackage)).append(';');
+ }
if (mComponent != null) {
- uri.append("component=").append(mComponent.flattenToShortString()).append(';');
+ uri.append("component=").append(Uri.encode(
+ mComponent.flattenToShortString(), "/")).append(';');
}
if (mExtras != null) {
for (String key : mExtras.keySet()) {
@@ -4567,6 +4832,7 @@ public class Intent implements Parcelable {
Uri.writeToParcel(out, mData);
out.writeString(mType);
out.writeInt(mFlags);
+ out.writeString(mPackage);
ComponentName.writeToParcel(mComponent, out);
if (mCategories != null) {
@@ -4600,6 +4866,7 @@ public class Intent implements Parcelable {
mData = Uri.CREATOR.createFromParcel(in);
mType = in.readString();
mFlags = in.readInt();
+ mPackage = in.readString();
mComponent = ComponentName.readFromParcel(in);
int N = in.readInt();
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index e5c5dc8a5e0ccf70d52c9a7bb25fa2f421983cea..365f26983a33c356ed159988a851cb470d777bc1 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -366,6 +366,7 @@ public class IntentFilter implements Parcelable {
throws MalformedMimeTypeException {
mPriority = 0;
mActions = new ArrayList();
+ addAction(action);
addDataType(dataType);
}
diff --git a/core/java/android/content/IntentSender.aidl b/core/java/android/content/IntentSender.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..741bc8c953cb3b637aef7365a9f4de8568bcff90
--- /dev/null
+++ b/core/java/android/content/IntentSender.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+parcelable IntentSender;
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
new file mode 100644
index 0000000000000000000000000000000000000000..4da49d974fd53f72e38508a3df6233c9e241db96
--- /dev/null
+++ b/core/java/android/content/IntentSender.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2006 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.content;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IIntentSender;
+import android.content.IIntentReceiver;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AndroidException;
+
+
+/**
+ * A description of an Intent and target action to perform with it.
+ * The returned object can be
+ * handed to other applications so that they can perform the action you
+ * described on your behalf at a later time.
+ *
+ * By giving a IntentSender to another application,
+ * you are granting it the right to perform the operation you have specified
+ * as if the other application was yourself (with the same permissions and
+ * identity). As such, you should be careful about how you build the IntentSender:
+ * often, for example, the base Intent you supply will have the component
+ * name explicitly set to one of your own components, to ensure it is ultimately
+ * sent there and nowhere else.
+ *
+ *
A IntentSender itself is simply a reference to a token maintained by
+ * the system describing the original data used to retrieve it. This means
+ * that, even if its owning application's process is killed, the
+ * IntentSender itself will remain usable from other processes that
+ * have been given it. If the creating application later re-retrieves the
+ * same kind of IntentSender (same operation, same Intent action, data,
+ * categories, and components, and same flags), it will receive a IntentSender
+ * representing the same token if that is still valid.
+ *
+ */
+public class IntentSender implements Parcelable {
+ private final IIntentSender mTarget;
+
+ /**
+ * Exception thrown when trying to send through a PendingIntent that
+ * has been canceled or is otherwise no longer able to execute the request.
+ */
+ public static class SendIntentException extends AndroidException {
+ public SendIntentException() {
+ }
+
+ public SendIntentException(String name) {
+ super(name);
+ }
+
+ public SendIntentException(Exception cause) {
+ super(cause);
+ }
+ }
+
+ /**
+ * Callback interface for discovering when a send operation has
+ * completed. Primarily for use with a IntentSender that is
+ * performing a broadcast, this provides the same information as
+ * calling {@link Context#sendOrderedBroadcast(Intent, String,
+ * android.content.BroadcastReceiver, Handler, int, String, Bundle)
+ * Context.sendBroadcast()} with a final BroadcastReceiver.
+ */
+ public interface OnFinished {
+ /**
+ * Called when a send operation as completed.
+ *
+ * @param IntentSender The IntentSender this operation was sent through.
+ * @param intent The original Intent that was sent.
+ * @param resultCode The final result code determined by the send.
+ * @param resultData The final data collected by a broadcast.
+ * @param resultExtras The final extras collected by a broadcast.
+ */
+ void onSendFinished(IntentSender IntentSender, Intent intent,
+ int resultCode, String resultData, Bundle resultExtras);
+ }
+
+ private static class FinishedDispatcher extends IIntentReceiver.Stub
+ implements Runnable {
+ private final IntentSender mIntentSender;
+ private final OnFinished mWho;
+ private final Handler mHandler;
+ private Intent mIntent;
+ private int mResultCode;
+ private String mResultData;
+ private Bundle mResultExtras;
+ FinishedDispatcher(IntentSender pi, OnFinished who, Handler handler) {
+ mIntentSender = pi;
+ mWho = who;
+ mHandler = handler;
+ }
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean serialized) {
+ mIntent = intent;
+ mResultCode = resultCode;
+ mResultData = data;
+ mResultExtras = extras;
+ if (mHandler == null) {
+ run();
+ } else {
+ mHandler.post(this);
+ }
+ }
+ public void run() {
+ mWho.onSendFinished(mIntentSender, mIntent, mResultCode,
+ mResultData, mResultExtras);
+ }
+ }
+
+ /**
+ * Perform the operation associated with this IntentSender, allowing the
+ * caller to specify information about the Intent to use and be notified
+ * when the send has completed.
+ *
+ * @param context The Context of the caller. This may be null if
+ * intent is also null.
+ * @param code Result code to supply back to the IntentSender's target.
+ * @param intent Additional Intent data. See {@link Intent#fillIn
+ * Intent.fillIn()} for information on how this is applied to the
+ * original Intent. Use null to not modify the original Intent.
+ * @param onFinished The object to call back on when the send has
+ * completed, or null for no callback.
+ * @param handler Handler identifying the thread on which the callback
+ * should happen. If null, the callback will happen from the thread
+ * pool of the process.
+ *
+ *
+ * @throws SendIntentException Throws CanceledIntentException if the IntentSender
+ * is no longer allowing more intents to be sent through it.
+ */
+ public void sendIntent(Context context, int code, Intent intent,
+ OnFinished onFinished, Handler handler) throws SendIntentException {
+ try {
+ String resolvedType = intent != null ?
+ intent.resolveTypeIfNeeded(context.getContentResolver())
+ : null;
+ int res = mTarget.send(code, intent, resolvedType,
+ onFinished != null
+ ? new FinishedDispatcher(this, onFinished, handler)
+ : null);
+ if (res < 0) {
+ throw new SendIntentException();
+ }
+ } catch (RemoteException e) {
+ throw new SendIntentException();
+ }
+ }
+
+ /**
+ * Comparison operator on two IntentSender objects, such that true
+ * is returned then they both represent the same operation from the
+ * same package.
+ */
+ @Override
+ public boolean equals(Object otherObj) {
+ if (otherObj instanceof IntentSender) {
+ return mTarget.asBinder().equals(((IntentSender)otherObj)
+ .mTarget.asBinder());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mTarget.asBinder().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("IntentSender{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(": ");
+ sb.append(mTarget != null ? mTarget.asBinder() : null);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(mTarget.asBinder());
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public IntentSender createFromParcel(Parcel in) {
+ IBinder target = in.readStrongBinder();
+ return target != null ? new IntentSender(target) : null;
+ }
+
+ public IntentSender[] newArray(int size) {
+ return new IntentSender[size];
+ }
+ };
+
+ /**
+ * Convenience function for writing either a IntentSender or null pointer to
+ * a Parcel. You must use this with {@link #readIntentSenderOrNullFromParcel}
+ * for later reading it.
+ *
+ * @param sender The IntentSender to write, or null.
+ * @param out Where to write the IntentSender.
+ */
+ public static void writeIntentSenderOrNullToParcel(IntentSender sender,
+ Parcel out) {
+ out.writeStrongBinder(sender != null ? sender.mTarget.asBinder()
+ : null);
+ }
+
+ /**
+ * Convenience function for reading either a Messenger or null pointer from
+ * a Parcel. You must have previously written the Messenger with
+ * {@link #writeIntentSenderOrNullToParcel}.
+ *
+ * @param in The Parcel containing the written Messenger.
+ *
+ * @return Returns the Messenger read from the Parcel, or null if null had
+ * been written.
+ */
+ public static IntentSender readIntentSenderOrNullFromParcel(Parcel in) {
+ IBinder b = in.readStrongBinder();
+ return b != null ? new IntentSender(b) : null;
+ }
+
+ protected IntentSender(IIntentSender target) {
+ mTarget = target;
+ }
+
+ protected IntentSender(IBinder target) {
+ mTarget = IIntentSender.Stub.asInterface(target);
+ }
+}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 9c25e73b0cf3d10348d7f3a549ae624f5fa777c8..f781e0d0e12dff81a646cb9beeaf2b65a2d37d98 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.backup.IBackupManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
@@ -35,6 +36,7 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
@@ -351,8 +353,18 @@ public class SyncStorageEngine extends Handler {
}
}
}
+ // Inform the backup manager about a data change
+ IBackupManager ibm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ if (ibm != null) {
+ try {
+ ibm.dataChanged("com.android.providers.settings");
+ } catch (RemoteException e) {
+ // Try again later
+ }
+ }
}
-
+
public boolean getSyncProviderAutomatically(String account, String providerName) {
synchronized (mAuthorities) {
if (account != null) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 85d877a070170a5006465d77caaf2859fb3a098c..27783efea9dff42301e48885fc25b1a61cabf38d 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -233,6 +233,12 @@ public class ActivityInfo extends ComponentInfo
* {@link android.R.attr#configChanges} attribute.
*/
public static final int CONFIG_ORIENTATION = 0x0080;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the screen layout. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_SCREEN_LAYOUT = 0x0100;
/**
* Bit in {@link #configChanges} that indicates that the activity
* can itself handle changes to the font scaling factor. Set from the
@@ -248,8 +254,8 @@ public class ActivityInfo extends ComponentInfo
* Contains any combination of {@link #CONFIG_FONT_SCALE},
* {@link #CONFIG_MCC}, {@link #CONFIG_MNC},
* {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN},
- * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION}, and
- * {@link #CONFIG_ORIENTATION}. Set from the
+ * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
+ * {@link #CONFIG_ORIENTATION}, and {@link #CONFIG_SCREEN_LAYOUT}. Set from the
* {@link android.R.attr#configChanges} attribute.
*/
public int configChanges;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 88ac04c24e6df912e60ea73655c3ef8aa66eeaf2..bcf95b6f574eba477921bab51c6c153a1999137c 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -58,10 +58,21 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* Class implementing the Application's manage space
* functionality. From the "manageSpaceActivity"
* attribute. This is an optional attribute and will be null if
- * application's dont specify it in their manifest
+ * applications don't specify it in their manifest
*/
public String manageSpaceActivityName;
+ /**
+ * Class implementing the Application's backup functionality. From
+ * the "backupAgent" attribute. This is an optional attribute and
+ * will be null if the application does not specify it in its manifest.
+ *
+ * If android:allowBackup is set to false, this attribute is ignored.
+ *
+ * {@hide}
+ */
+ public String backupAgentName;
+
/**
* Value for {@link #flags}: if set, this application is installed in the
* device's system image.
@@ -93,7 +104,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_PERSISTENT = 1<<3;
/**
- * Value for {@link #flags}: set to true iif this application holds the
+ * Value for {@link #flags}: set to true if this application holds the
* {@link android.Manifest.permission#FACTORY_TEST} permission and the
* device is running in factory test mode.
*/
@@ -123,13 +134,46 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* Value for {@link #flags}: this is set of the application has set
* its android:targetSdkVersion to something >= the current SDK version.
*/
- public static final int FLAG_TARGETS_SDK = 1<<8;
+ public static final int FLAG_TEST_ONLY = 1<<8;
/**
- * Value for {@link #flags}: this is set of the application has set
- * its android:targetSdkVersion to something >= the current SDK version.
+ * Value for {@link #flags}: true when the application's window can be
+ * reduced in size for smaller screens. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_smallScreens
+ * android:smallScreens}.
*/
- public static final int FLAG_TEST_ONLY = 1<<9;
+ public static final int FLAG_SUPPORTS_SMALL_SCREENS = 1<<9;
+
+ /**
+ * Value for {@link #flags}: true when the application's window can be
+ * displayed on normal screens. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_normalScreens
+ * android:normalScreens}.
+ */
+ public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1<<10;
+
+ /**
+ * Value for {@link #flags}: true when the application's window can be
+ * increased in size for larger screens. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_largeScreens
+ * android:smallScreens}.
+ */
+ public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11;
+
+ /**
+ * Value for {@link #flags}: this is false if the application has set
+ * its android:allowBackup to false, true otherwise.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_ALLOW_BACKUP = 1<<12;
+
+ /**
+ * Indicates that the application supports any densities;
+ * {@hide}
+ */
+ public static final int ANY_DENSITY = -1;
+ private static final int[] ANY_DENSITIES_ARRAY = { ANY_DENSITY };
/**
* Flags associated with the application. Any combination of
@@ -137,7 +181,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
* {@link #FLAG_ALLOW_TASK_REPARENTING}
* {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP},
- * {@link #FLAG_TARGETS_SDK}.
+ * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS},
+ * {@link #FLAG_SUPPORTS_NORMAL_SCREENS},
+ * {@link #FLAG_SUPPORTS_LARGE_SCREENS}.
*/
public int flags = 0;
@@ -173,7 +219,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public int uid;
-
/**
* The list of densities in DPI that application supprots. This
* field is only set if the {@link PackageManager#GET_SUPPORTS_DENSITIES} flag was
@@ -181,6 +226,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public int[] supportsDensities;
+ /**
+ * The minimum SDK version this application targets. It may run on earilier
+ * versions, but it knows how to work with any new behavior added at this
+ * version. Will be {@link android.os.Build.VERSION_CODES#CUR_DEVELOPMENT}
+ * if this is a development build and the app is targeting that. You should
+ * compare that this number is >= the SDK version number at which your
+ * behavior was introduced.
+ */
+ public int targetSdkVersion;
+
/**
* When false, indicates that all components within this application are
* considered disabled, regardless of their individually set enabled status.
@@ -200,6 +255,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
pw.println(prefix + "publicSourceDir=" + publicSourceDir);
pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
pw.println(prefix + "dataDir=" + dataDir);
+ pw.println(prefix + "targetSdkVersion=" + targetSdkVersion);
pw.println(prefix + "enabled=" + enabled);
pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName);
pw.println(prefix + "description=0x"+Integer.toHexString(descriptionRes));
@@ -246,6 +302,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
sharedLibraryFiles = orig.sharedLibraryFiles;
dataDir = orig.dataDir;
uid = orig.uid;
+ targetSdkVersion = orig.targetSdkVersion;
enabled = orig.enabled;
manageSpaceActivityName = orig.manageSpaceActivityName;
descriptionRes = orig.descriptionRes;
@@ -276,8 +333,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeStringArray(sharedLibraryFiles);
dest.writeString(dataDir);
dest.writeInt(uid);
+ dest.writeInt(targetSdkVersion);
dest.writeInt(enabled ? 1 : 0);
dest.writeString(manageSpaceActivityName);
+ dest.writeString(backupAgentName);
dest.writeInt(descriptionRes);
dest.writeIntArray(supportsDensities);
}
@@ -305,8 +364,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
sharedLibraryFiles = source.readStringArray();
dataDir = source.readString();
uid = source.readInt();
+ targetSdkVersion = source.readInt();
enabled = source.readInt() != 0;
manageSpaceActivityName = source.readString();
+ backupAgentName = source.readString();
descriptionRes = source.readInt();
supportsDensities = source.createIntArray();
}
@@ -331,4 +392,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
return null;
}
+
+ /**
+ * Disable compatibility mode
+ *
+ * @hide
+ */
+ public void disableCompatibilityMode() {
+ flags |= FLAG_SUPPORTS_LARGE_SCREENS;
+ supportsDensities = ANY_DENSITIES_ARRAY;
+ }
}
diff --git a/core/java/android/content/pm/ConfigurationInfo.java b/core/java/android/content/pm/ConfigurationInfo.java
index dcc746331b0b89a7ea4aba3882c66ef929c455c5..fb7a47fbd1bd3438345df93f61fcc5c94ec91eb9 100755
--- a/core/java/android/content/pm/ConfigurationInfo.java
+++ b/core/java/android/content/pm/ConfigurationInfo.java
@@ -22,7 +22,7 @@ import android.os.Parcelable;
/**
* Information you can retrieve about hardware configuration preferences
* declared by an application. This corresponds to information collected from the
- * AndroidManifest.xml's <uses-configuration> tags.
+ * AndroidManifest.xml's <uses-configuration> and the <uses-feature>tags.
*/
public class ConfigurationInfo implements Parcelable {
/**
@@ -70,6 +70,16 @@ public class ConfigurationInfo implements Parcelable {
*/
public int reqInputFeatures = 0;
+ /**
+ * Default value for {@link #reqGlEsVersion};
+ */
+ public static final int GL_ES_VERSION_UNDEFINED = 0;
+ /**
+ * The GLES version used by an application. The upper order 16 bits represent the
+ * major version and the lower order 16 bits the minor version.
+ */
+ public int reqGlEsVersion;
+
public ConfigurationInfo() {
}
@@ -78,6 +88,7 @@ public class ConfigurationInfo implements Parcelable {
reqKeyboardType = orig.reqKeyboardType;
reqNavigation = orig.reqNavigation;
reqInputFeatures = orig.reqInputFeatures;
+ reqGlEsVersion = orig.reqGlEsVersion;
}
public String toString() {
@@ -86,7 +97,8 @@ public class ConfigurationInfo implements Parcelable {
+ ", touchscreen = " + reqTouchScreen + "}"
+ ", inputMethod = " + reqKeyboardType + "}"
+ ", navigation = " + reqNavigation + "}"
- + ", reqInputFeatures = " + reqInputFeatures + "}";
+ + ", reqInputFeatures = " + reqInputFeatures + "}"
+ + ", reqGlEsVersion = " + reqGlEsVersion + "}";
}
public int describeContents() {
@@ -98,6 +110,7 @@ public class ConfigurationInfo implements Parcelable {
dest.writeInt(reqKeyboardType);
dest.writeInt(reqNavigation);
dest.writeInt(reqInputFeatures);
+ dest.writeInt(reqGlEsVersion);
}
public static final Creator CREATOR =
@@ -115,5 +128,18 @@ public class ConfigurationInfo implements Parcelable {
reqKeyboardType = source.readInt();
reqNavigation = source.readInt();
reqInputFeatures = source.readInt();
+ reqGlEsVersion = source.readInt();
+ }
+
+ /**
+ * This method extracts the major and minor version of reqGLEsVersion attribute
+ * and returns it as a string. Say reqGlEsVersion value of 0x00010002 is returned
+ * as 1.2
+ * @return String representation of the reqGlEsVersion attribute
+ */
+ public String getGlEsVersion() {
+ int major = ((reqGlEsVersion & 0xffff0000) >> 16);
+ int minor = reqGlEsVersion & 0x0000ffff;
+ return String.valueOf(major)+"."+String.valueOf(minor);
}
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c199619c57a92ba6168c9e6d223b411fc008e44a..bf2a8959c7f4cb9f84789675d2d883becd44311c 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -34,7 +34,7 @@ import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
-import android.app.PendingIntent;
+import android.content.IntentSender;
/**
* See {@link PackageManager} for documentation on most of the APIs
@@ -164,7 +164,12 @@ interface IPackageManager {
void addPreferredActivity(in IntentFilter filter, int match,
in ComponentName[] set, in ComponentName activity);
+
+ void replacePreferredActivity(in IntentFilter filter, int match,
+ in ComponentName[] set, in ComponentName activity);
+
void clearPackagePreferredActivities(String packageName);
+
int getPreferredActivities(out List outFilters,
out List outActivities, String packageName);
@@ -229,12 +234,12 @@ interface IPackageManager {
* and the current free storage is YY,
* if XX is less than YY, just return. if not free XX-YY number
* of bytes if possible.
- * @param opFinishedIntent PendingIntent call back used to
+ * @param pi IntentSender call back used to
* notify when the operation is completed.May be null
* to indicate that no call back is desired.
*/
void freeStorage(in long freeStorageSize,
- in PendingIntent opFinishedIntent);
+ in IntentSender pi);
/**
* Delete all the cache files in an applications cache directory
@@ -271,4 +276,11 @@ interface IPackageManager {
boolean isSafeMode();
void systemReady();
boolean hasSystemUidErrors();
+
+ /**
+ * Ask the package manager to perform dex-opt (if needed) on the given
+ * package, if it already hasn't done mode. Only does this if running
+ * in the special development "no pre-dexopt" mode.
+ */
+ boolean performDexOpt(String packageName);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 3a192f7eb7493168da4d0e9ce42446032dac89df..941ca9e5bb74efc8e73e630f932ddaca217e4c20 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,12 +16,11 @@
package android.content.pm;
-
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
@@ -397,6 +396,15 @@ public abstract class PackageManager {
*/
public static final int INSTALL_FAILED_TEST_ONLY = -15;
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the package being installed contains native code, but none that is
+ * compatible with the the device's CPU_ABI.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16;
+
/**
* Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
@@ -563,9 +571,8 @@ public abstract class PackageManager {
* launch the main activity in the package, or null if the package does
* not contain such an activity.
*/
- public abstract Intent getLaunchIntentForPackage(String packageName)
- throws NameNotFoundException;
-
+ public abstract Intent getLaunchIntentForPackage(String packageName);
+
/**
* Return an array of all of the secondary group-ids that have been
* assigned to a package.
@@ -1491,7 +1498,7 @@ public abstract class PackageManager {
* @hide
*/
public abstract void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer);
-
+
/**
* Free storage by deleting LRU sorted list of cache files across
* all applications. If the currently available free storage
@@ -1509,13 +1516,13 @@ public abstract class PackageManager {
* and the current free storage is YY,
* if XX is less than YY, just return. if not free XX-YY number
* of bytes if possible.
- * @param opFinishedIntent PendingIntent call back used to
+ * @param pi IntentSender call back used to
* notify when the operation is completed.May be null
* to indicate that no call back is desired.
*
* @hide
*/
- public abstract void freeStorage(long freeStorageSize, PendingIntent opFinishedIntent);
+ public abstract void freeStorage(long freeStorageSize, IntentSender pi);
/**
* Retrieve the size information for a package.
@@ -1604,6 +1611,26 @@ public abstract class PackageManager {
public abstract void addPreferredActivity(IntentFilter filter, int match,
ComponentName[] set, ComponentName activity);
+ /**
+ * Replaces an existing preferred activity mapping to the system, and if that were not present
+ * adds a new preferred activity. This will be used
+ * to automatically select the given activity component when
+ * {@link Context#startActivity(Intent) Context.startActivity()} finds
+ * multiple matching activities and also matches the given filter.
+ *
+ * @param filter The set of intents under which this activity will be
+ * made preferred.
+ * @param match The IntentFilter match category that this preference
+ * applies to.
+ * @param set The set of activities that the user was picking from when
+ * this preference was made.
+ * @param activity The component name of the activity that is to be
+ * preferred.
+ * @hide
+ */
+ public abstract void replacePreferredActivity(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity);
+
/**
* Remove all preferred activity mappings, previously added with
* {@link #addPreferredActivity}, from the
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 88907c180117f15546c410151cabcbf2eb927890..558b0c3e369a109dd8d7dbc36a2a32b58eb29677 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -55,6 +55,32 @@ import java.util.jar.JarFile;
* {@hide}
*/
public class PackageParser {
+ /** @hide */
+ public static class NewPermissionInfo {
+ public final String name;
+ public final int sdkVersion;
+ public final int fileVersion;
+
+ public NewPermissionInfo(String name, int sdkVersion, int fileVersion) {
+ this.name = name;
+ this.sdkVersion = sdkVersion;
+ this.fileVersion = fileVersion;
+ }
+ }
+
+ /**
+ * List of new permissions that have been added since 1.0.
+ * NOTE: These must be declared in SDK version order, with permissions
+ * added to older SDKs appearing before those added to newer SDKs.
+ * @hide
+ */
+ public static final PackageParser.NewPermissionInfo NEW_PERMISSIONS[] =
+ new PackageParser.NewPermissionInfo[] {
+ new PackageParser.NewPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ android.os.Build.VERSION_CODES.DONUT, 0),
+ new PackageParser.NewPermissionInfo(android.Manifest.permission.READ_PHONE_STATE,
+ android.os.Build.VERSION_CODES.DONUT, 0)
+ };
private String mArchiveSourcePath;
private String[] mSeparateProcesses;
@@ -616,7 +642,6 @@ public class PackageParser {
final Package pkg = new Package(pkgName);
boolean foundApp = false;
- boolean targetsSdk = false;
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifest);
@@ -643,6 +668,11 @@ public class PackageParser {
}
sa.recycle();
+ // Resource boolean are -1, so 1 means we don't know the value.
+ int supportsSmallScreens = 1;
+ int supportsNormalScreens = 1;
+ int supportsLargeScreens = 1;
+
int outerDepth = parser.getDepth();
while ((type=parser.next()) != parser.END_DOCUMENT
&& (type != parser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -723,6 +753,18 @@ public class PackageParser {
XmlUtils.skipCurrentTag(parser);
+ } else if (tagName.equals("uses-feature")) {
+ ConfigurationInfo cPref = new ConfigurationInfo();
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestUsesFeature);
+ cPref.reqGlEsVersion = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesFeature_glEsVersion,
+ ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
+ sa.recycle();
+ pkg.configPreferences.add(cPref);
+
+ XmlUtils.skipCurrentTag(parser);
+
} else if (tagName.equals("uses-sdk")) {
if (mSdkVersion > 0) {
sa = res.obtainAttributes(attrs,
@@ -740,7 +782,7 @@ public class PackageParser {
targetCode = minCode = val.string.toString();
} else {
// If it's not a string, it's an integer.
- minVers = val.data;
+ targetVers = minVers = val.data;
}
}
@@ -761,6 +803,25 @@ public class PackageParser {
sa.recycle();
+ if (minCode != null) {
+ if (!minCode.equals(mSdkCodename)) {
+ if (mSdkCodename != null) {
+ outError[0] = "Requires development platform " + minCode
+ + " (current platform is " + mSdkCodename + ")";
+ } else {
+ outError[0] = "Requires development platform " + minCode
+ + " but this is a release platform.";
+ }
+ mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+ return null;
+ }
+ } else if (minVers > mSdkVersion) {
+ outError[0] = "Requires newer sdk version #" + minVers
+ + " (current version is #" + mSdkVersion + ")";
+ mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+ return null;
+ }
+
if (targetCode != null) {
if (!targetCode.equals(mSdkCodename)) {
if (mSdkCodename != null) {
@@ -774,18 +835,10 @@ public class PackageParser {
return null;
}
// If the code matches, it definitely targets this SDK.
- targetsSdk = true;
- } else if (targetVers >= mSdkVersion) {
- // If they have explicitly targeted our current version
- // or something after it, then note this.
- targetsSdk = true;
- }
-
- if (minVers > mSdkVersion) {
- outError[0] = "Requires newer sdk version #" + minVers
- + " (current version is #" + mSdkVersion + ")";
- mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
- return null;
+ pkg.applicationInfo.targetSdkVersion
+ = android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
+ } else {
+ pkg.applicationInfo.targetSdkVersion = targetVers;
}
if (maxVers < mSdkVersion) {
@@ -811,6 +864,42 @@ public class PackageParser {
+ parser.getName();
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
+
+
+ } else if (tagName.equals("supports-density")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestSupportsDensity);
+
+ int density = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsDensity_density, -1);
+
+ sa.recycle();
+
+ if (density != -1 && !pkg.supportsDensityList.contains(density)) {
+ pkg.supportsDensityList.add(density);
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("supports-screens")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens);
+
+ // This is a trick to get a boolean and still able to detect
+ // if a value was actually set.
+ supportsSmallScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_smallScreens,
+ supportsSmallScreens);
+ supportsNormalScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_normalScreens,
+ supportsNormalScreens);
+ supportsLargeScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_largeScreens,
+ supportsLargeScreens);
+
+ sa.recycle();
+
+ XmlUtils.skipCurrentTag(parser);
} else {
Log.w(TAG, "Bad element under : "
+ parser.getName());
@@ -824,15 +913,39 @@ public class PackageParser {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY;
}
- if (targetsSdk) {
- pkg.applicationInfo.flags |= ApplicationInfo.FLAG_TARGETS_SDK;
+ final int NP = PackageParser.NEW_PERMISSIONS.length;
+ for (int ip=0; ip= npi.sdkVersion) {
+ break;
+ }
+ if (!pkg.requestedPermissions.contains(npi.name)) {
+ Log.i(TAG, "Impliciting adding " + npi.name + " to old pkg "
+ + pkg.packageName);
+ pkg.requestedPermissions.add(npi.name);
+ }
}
if (pkg.usesLibraries.size() > 0) {
pkg.usesLibraryFiles = new String[pkg.usesLibraries.size()];
pkg.usesLibraries.toArray(pkg.usesLibraryFiles);
}
-
+
+ if (supportsSmallScreens < 0 || (supportsSmallScreens > 0
+ && pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS;
+ }
+ if (supportsNormalScreens != 0) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS;
+ }
+ if (supportsLargeScreens < 0 || (supportsLargeScreens > 0
+ && pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS;
+ }
+
int size = pkg.supportsDensityList.size();
if (size > 0) {
int densities[] = pkg.supportsDensities = new int[size];
@@ -1142,6 +1255,19 @@ public class PackageParser {
outError);
}
+ boolean allowBackup = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_allowBackup, true);
+ if (allowBackup) {
+ ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ String backupAgent = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_backupAgent);
+ if (backupAgent != null) {
+ ai.backupAgentName = buildClassName(pkgName, backupAgent, outError);
+ Log.v(TAG, "android:backupAgent = " + ai.backupAgentName
+ + " from " + pkgName + "+" + backupAgent);
+ }
+ }
+
TypedValue v = sa.peekValue(
com.android.internal.R.styleable.AndroidManifestApplication_label);
if (v != null && (ai.labelRes=v.resourceId) == 0) {
@@ -1298,21 +1424,6 @@ public class PackageParser {
XmlUtils.skipCurrentTag(parser);
- } else if (tagName.equals("supports-density")) {
- sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AndroidManifestSupportsDensity);
-
- int density = sa.getInteger(
- com.android.internal.R.styleable.AndroidManifestSupportsDensity_density, -1);
-
- sa.recycle();
-
- if (density != -1 && !owner.supportsDensityList.contains(density)) {
- owner.supportsDensityList.add(density);
- }
-
- XmlUtils.skipCurrentTag(parser);
-
} else {
if (!RIGID_PARSER) {
Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
@@ -2219,6 +2330,17 @@ public class PackageParser {
// preferred up order.
public int mPreferredOrder = 0;
+ // For use by package manager service to keep track of which apps
+ // have been installed with forward locking.
+ public boolean mForwardLocked;
+
+ // For use by the package manager to keep track of the path to the
+ // file an app came from.
+ public String mScanPath;
+
+ // For use by package manager to keep track of where it has done dexopt.
+ public boolean mDidDexOpt;
+
// Additional data supplied by callers.
public Object mExtras;
@@ -2368,7 +2490,7 @@ public class PackageParser {
return true;
}
if ((flags & PackageManager.GET_SUPPORTS_DENSITIES) != 0
- && p.supportsDensities != null) {
+ && p.supportsDensities != null) {
return true;
}
return false;
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index 231e3e24a27c8036c736b29fe48ad040f9aadce7..a37e4e8cc3bf31286675c97b02d6d62b47c1e4b3 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -16,6 +16,7 @@
package android.content.res;
+import android.os.MemoryFile;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -24,6 +25,8 @@ import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
/**
* File descriptor of an entry in the AssetManager. This provides your own
@@ -123,6 +126,13 @@ public class AssetFileDescriptor implements Parcelable {
mFd.close();
}
+ /**
+ * Checks whether this file descriptor is for a memory file.
+ */
+ private boolean isMemoryFile() throws IOException {
+ return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
+ }
+
/**
* Create and return a new auto-close input stream for this asset. This
* will either return a full asset {@link AutoCloseInputStream}, or
@@ -132,6 +142,12 @@ public class AssetFileDescriptor implements Parcelable {
* should only call this once for a particular asset.
*/
public FileInputStream createInputStream() throws IOException {
+ if (isMemoryFile()) {
+ if (mLength > Integer.MAX_VALUE) {
+ throw new IOException("File length too large for a memory file: " + mLength);
+ }
+ return new AutoCloseMemoryFileInputStream(mFd, (int)mLength);
+ }
if (mLength < 0) {
return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
}
@@ -261,6 +277,66 @@ public class AssetFileDescriptor implements Parcelable {
}
}
+ /**
+ * An input stream that reads from a MemoryFile and closes it when the stream is closed.
+ * This extends FileInputStream just because {@link #createInputStream} returns
+ * a FileInputStream. All the FileInputStream methods are
+ * overridden to use the MemoryFile instead.
+ */
+ private static class AutoCloseMemoryFileInputStream extends FileInputStream {
+ private ParcelFileDescriptor mParcelFd;
+ private MemoryFile mFile;
+ private InputStream mStream;
+
+ public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length)
+ throws IOException {
+ super(fd.getFileDescriptor());
+ mParcelFd = fd;
+ mFile = new MemoryFile(fd.getFileDescriptor(), length, "r");
+ mStream = mFile.getInputStream();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mStream.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mParcelFd.close(); // must close ParcelFileDescriptor, not just the file descriptor,
+ // since it could be a subclass of ParcelFileDescriptor.
+ // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases
+ // a content provider
+ mFile.close(); // to unmap the memory file from the address space.
+ mStream.close(); // doesn't actually do anything
+ }
+
+ @Override
+ public FileChannel getChannel() {
+ return null;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return mStream.read();
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int count) throws IOException {
+ return mStream.read(buffer, offset, count);
+ }
+
+ @Override
+ public int read(byte[] buffer) throws IOException {
+ return mStream.read(buffer);
+ }
+
+ @Override
+ public long skip(long count) throws IOException {
+ return mStream.skip(count);
+ }
+ }
+
/**
* An OutputStream you can create on a ParcelFileDescriptor, which will
* take care of calling {@link ParcelFileDescriptor#close
@@ -345,4 +421,16 @@ public class AssetFileDescriptor implements Parcelable {
return new AssetFileDescriptor[size];
}
};
+
+ /**
+ * Creates an AssetFileDescriptor from a memory file.
+ *
+ * @hide
+ */
+ public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile)
+ throws IOException {
+ ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor();
+ return new AssetFileDescriptor(fd, 0, memoryFile.length());
+ }
+
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 1c9173694d2bccb668b99d96e89df9ad2d6422c2..5c7b01fa0ccb0d5034f09dd24d984636a7385705 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -601,7 +601,7 @@ public final class AssetManager {
public native final void setConfiguration(int mcc, int mnc, String locale,
int orientation, int touchscreen, int density, int keyboard,
int keyboardHidden, int navigation, int screenWidth, int screenHeight,
- int majorVersion);
+ int screenLayout, int majorVersion);
/**
* Retrieve the resource identifier for the given resource name.
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..dfe304d1593d193d85d8936f8112e03d576a4e71
--- /dev/null
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2006 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.content.res;
+
+import android.content.pm.ApplicationInfo;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+/**
+ * CompatibilityInfo class keeps the information about compatibility mode that the application is
+ * running under.
+ *
+ * {@hide}
+ */
+public class CompatibilityInfo {
+ private static final boolean DBG = false;
+ private static final String TAG = "CompatibilityInfo";
+
+ /** default compatibility info object for compatible applications */
+ public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo();
+
+ /**
+ * The default width of the screen in portrait mode.
+ */
+ public static final int DEFAULT_PORTRAIT_WIDTH = 320;
+
+ /**
+ * The default height of the screen in portrait mode.
+ */
+ public static final int DEFAULT_PORTRAIT_HEIGHT = 480;
+
+ /**
+ * The x-shift mode that controls the position of the content or the window under
+ * compatibility mode.
+ * {@see getTranslator}
+ * {@see Translator#mShiftMode}
+ */
+ private static final int X_SHIFT_NONE = 0;
+ private static final int X_SHIFT_CONTENT = 1;
+ private static final int X_SHIFT_AND_CLIP_CONTENT = 2;
+ private static final int X_SHIFT_WINDOW = 3;
+
+
+ /**
+ * A compatibility flags
+ */
+ private int mCompatibilityFlags;
+
+ /**
+ * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f)
+ * {@see compatibilityFlag}
+ */
+ private static final int SCALING_REQUIRED = 1;
+
+ /**
+ * A flag mask to indicates that the application can expand over the original size.
+ * The flag is set to true if
+ * 1) Application declares its expandable in manifest file using or
+ * 2) The screen size is same as (320 x 480) * density.
+ * {@see compatibilityFlag}
+ */
+ private static final int EXPANDABLE = 2;
+
+ /**
+ * A flag mask to tell if the application is configured to be expandable. This differs
+ * from EXPANDABLE in that the application that is not expandable will be
+ * marked as expandable if it runs in (320x 480) * density screen size.
+ */
+ private static final int CONFIGURED_EXPANDABLE = 4;
+
+ private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE;
+
+ /**
+ * Application's scale.
+ */
+ public final float applicationScale;
+
+ /**
+ * Application's inverted scale.
+ */
+ public final float applicationInvertedScale;
+
+ /**
+ * The flags from ApplicationInfo.
+ */
+ public final int appFlags;
+
+ /**
+ * Window size in Compatibility Mode, in real pixels. This is updated by
+ * {@link DisplayMetrics#updateMetrics}.
+ */
+ private int mWidth;
+ private int mHeight;
+
+ /**
+ * The x offset to center the window content. In X_SHIFT_WINDOW mode, the offset is added
+ * to the window's layout. In X_SHIFT_CONTENT/X_SHIFT_AND_CLIP_CONTENT mode, the offset
+ * is used to translate the Canvas.
+ */
+ private int mXOffset;
+
+ public CompatibilityInfo(ApplicationInfo appInfo) {
+ appFlags = appInfo.flags;
+
+ if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
+ mCompatibilityFlags = EXPANDABLE | CONFIGURED_EXPANDABLE;
+ }
+
+ float packageDensityScale = -1.0f;
+ if (appInfo.supportsDensities != null) {
+ int minDiff = Integer.MAX_VALUE;
+ for (int density : appInfo.supportsDensities) {
+ if (density == ApplicationInfo.ANY_DENSITY) {
+ packageDensityScale = 1.0f;
+ break;
+ }
+ int tmpDiff = Math.abs(DisplayMetrics.DEVICE_DENSITY - density);
+ if (tmpDiff == 0) {
+ packageDensityScale = 1.0f;
+ break;
+ }
+ // prefer higher density (appScale>1.0), unless that's only option.
+ if (tmpDiff < minDiff && packageDensityScale < 1.0f) {
+ packageDensityScale = DisplayMetrics.DEVICE_DENSITY / (float) density;
+ minDiff = tmpDiff;
+ }
+ }
+ }
+ if (packageDensityScale > 0.0f) {
+ applicationScale = packageDensityScale;
+ } else {
+ applicationScale =
+ DisplayMetrics.DEVICE_DENSITY / (float) DisplayMetrics.DEFAULT_DENSITY;
+ }
+ applicationInvertedScale = 1.0f / applicationScale;
+ if (applicationScale != 1.0f) {
+ mCompatibilityFlags |= SCALING_REQUIRED;
+ }
+ }
+
+ private CompatibilityInfo(int appFlags, int compFlags, float scale, float invertedScale) {
+ this.appFlags = appFlags;
+ mCompatibilityFlags = compFlags;
+ applicationScale = scale;
+ applicationInvertedScale = invertedScale;
+ }
+
+ private CompatibilityInfo() {
+ this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS
+ | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS
+ | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS,
+ EXPANDABLE | CONFIGURED_EXPANDABLE,
+ 1.0f,
+ 1.0f);
+ }
+
+ /**
+ * Returns the copy of this instance.
+ */
+ public CompatibilityInfo copy() {
+ CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags,
+ applicationScale, applicationInvertedScale);
+ info.setVisibleRect(mXOffset, mWidth, mHeight);
+ return info;
+ }
+
+ /**
+ * Sets the application's visible rect in compatibility mode.
+ * @param xOffset the application's x offset that is added to center the content.
+ * @param widthPixels the application's width in real pixels on the screen.
+ * @param heightPixels the application's height in real pixels on the screen.
+ */
+ public void setVisibleRect(int xOffset, int widthPixels, int heightPixels) {
+ this.mXOffset = xOffset;
+ mWidth = widthPixels;
+ mHeight = heightPixels;
+ }
+
+ /**
+ * Sets expandable bit in the compatibility flag.
+ */
+ public void setExpandable(boolean expandable) {
+ if (expandable) {
+ mCompatibilityFlags |= CompatibilityInfo.EXPANDABLE;
+ } else {
+ mCompatibilityFlags &= ~CompatibilityInfo.EXPANDABLE;
+ }
+ }
+
+ /**
+ * @return true if the application is configured to be expandable.
+ */
+ public boolean isConfiguredExpandable() {
+ return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_EXPANDABLE) != 0;
+ }
+
+ /**
+ * @return true if the scaling is required
+ */
+ public boolean isScalingRequired() {
+ return (mCompatibilityFlags & SCALING_REQUIRED) != 0;
+ }
+
+ @Override
+ public String toString() {
+ return "CompatibilityInfo{scale=" + applicationScale +
+ ", compatibility flag=" + mCompatibilityFlags + "}";
+ }
+
+ /**
+ * Returns the translator which can translate the coordinates of the window.
+ * There are five different types of Translator.
+ *
+ * 1) {@link CompatibilityInfo#X_SHIFT_AND_CLIP_CONTENT}
+ * Shift and clip the content of the window at drawing time. Used for activities'
+ * main window (with no gravity).
+ * 2) {@link CompatibilityInfo#X_SHIFT_CONTENT}
+ * Shift the content of the window at drawing time. Used for windows that is created by
+ * an application and expected to be aligned with the application window.
+ * 3) {@link CompatibilityInfo#X_SHIFT_WINDOW}
+ * Create the window with adjusted x- coordinates. This is typically used
+ * in popup window, where it has to be placed relative to main window.
+ * 4) {@link CompatibilityInfo#X_SHIFT_NONE}
+ * No adjustment required, such as dialog.
+ * 5) Same as X_SHIFT_WINDOW, but no scaling. This is used by {@link SurfaceView}, which
+ * does not require scaling, but its window's location has to be adjusted.
+ *
+ * @param params the window's parameter
+ */
+ public Translator getTranslator(WindowManager.LayoutParams params) {
+ if ( (mCompatibilityFlags & CompatibilityInfo.SCALING_EXPANDABLE_MASK)
+ == CompatibilityInfo.EXPANDABLE) {
+ if (DBG) Log.d(TAG, "no translation required");
+ return null;
+ }
+
+ if ((mCompatibilityFlags & CompatibilityInfo.EXPANDABLE) == 0) {
+ if ((params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) {
+ if (DBG) Log.d(TAG, "translation for surface view selected");
+ return new Translator(X_SHIFT_WINDOW, false, 1.0f, 1.0f);
+ } else {
+ int shiftMode;
+ if (params.gravity == Gravity.NO_GRAVITY) {
+ // For Regular Application window
+ shiftMode = X_SHIFT_AND_CLIP_CONTENT;
+ if (DBG) Log.d(TAG, "shift and clip translator");
+ } else if (params.width == WindowManager.LayoutParams.FILL_PARENT) {
+ // For Regular Application window
+ shiftMode = X_SHIFT_CONTENT;
+ if (DBG) Log.d(TAG, "shift content translator");
+ } else if ((params.gravity & Gravity.LEFT) != 0 && params.x > 0) {
+ shiftMode = X_SHIFT_WINDOW;
+ if (DBG) Log.d(TAG, "shift window translator");
+ } else {
+ shiftMode = X_SHIFT_NONE;
+ if (DBG) Log.d(TAG, "no content/window translator");
+ }
+ return new Translator(shiftMode);
+ }
+ } else if (isScalingRequired()) {
+ return new Translator();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * A helper object to translate the screen and window coordinates back and forth.
+ * @hide
+ */
+ public class Translator {
+ final private int mShiftMode;
+ final public boolean scalingRequired;
+ final public float applicationScale;
+ final public float applicationInvertedScale;
+
+ private Rect mContentInsetsBuffer = null;
+ private Rect mVisibleInsets = null;
+
+ Translator(int shiftMode, boolean scalingRequired, float applicationScale,
+ float applicationInvertedScale) {
+ mShiftMode = shiftMode;
+ this.scalingRequired = scalingRequired;
+ this.applicationScale = applicationScale;
+ this.applicationInvertedScale = applicationInvertedScale;
+ }
+
+ Translator(int shiftMode) {
+ this(shiftMode,
+ isScalingRequired(),
+ CompatibilityInfo.this.applicationScale,
+ CompatibilityInfo.this.applicationInvertedScale);
+ }
+
+ Translator() {
+ this(X_SHIFT_NONE);
+ }
+
+ /**
+ * Translate the screen rect to the application frame.
+ */
+ public void translateRectInScreenToAppWinFrame(Rect rect) {
+ if (rect.isEmpty()) return; // skip if the window size is empty.
+ switch (mShiftMode) {
+ case X_SHIFT_AND_CLIP_CONTENT:
+ rect.intersect(0, 0, mWidth, mHeight);
+ break;
+ case X_SHIFT_CONTENT:
+ rect.intersect(0, 0, mWidth + mXOffset, mHeight);
+ break;
+ case X_SHIFT_WINDOW:
+ case X_SHIFT_NONE:
+ break;
+ }
+ if (scalingRequired) {
+ rect.scale(applicationInvertedScale);
+ }
+ }
+
+ /**
+ * Translate the region in window to screen.
+ */
+ public void translateRegionInWindowToScreen(Region transparentRegion) {
+ switch (mShiftMode) {
+ case X_SHIFT_AND_CLIP_CONTENT:
+ case X_SHIFT_CONTENT:
+ transparentRegion.scale(applicationScale);
+ transparentRegion.translate(mXOffset, 0);
+ break;
+ case X_SHIFT_WINDOW:
+ case X_SHIFT_NONE:
+ transparentRegion.scale(applicationScale);
+ }
+ }
+
+ /**
+ * Apply translation to the canvas that is necessary to draw the content.
+ */
+ public void translateCanvas(Canvas canvas) {
+ if (mShiftMode == X_SHIFT_CONTENT ||
+ mShiftMode == X_SHIFT_AND_CLIP_CONTENT) {
+ // TODO: clear outside when rotation is changed.
+
+ // Translate x-offset only when the content is shifted.
+ canvas.translate(mXOffset, 0);
+ }
+ if (scalingRequired) {
+ canvas.scale(applicationScale, applicationScale);
+ }
+ }
+
+ /**
+ * Translate the motion event captured on screen to the application's window.
+ */
+ public void translateEventInScreenToAppWindow(MotionEvent event) {
+ if (mShiftMode == X_SHIFT_CONTENT ||
+ mShiftMode == X_SHIFT_AND_CLIP_CONTENT) {
+ event.translate(-mXOffset, 0);
+ }
+ if (scalingRequired) {
+ event.scale(applicationInvertedScale);
+ }
+ }
+
+ /**
+ * Translate the window's layout parameter, from application's view to
+ * Screen's view.
+ */
+ public void translateWindowLayout(WindowManager.LayoutParams params) {
+ switch (mShiftMode) {
+ case X_SHIFT_NONE:
+ case X_SHIFT_AND_CLIP_CONTENT:
+ case X_SHIFT_CONTENT:
+ params.scale(applicationScale);
+ break;
+ case X_SHIFT_WINDOW:
+ params.scale(applicationScale);
+ params.x += mXOffset;
+ break;
+ }
+ }
+
+ /**
+ * Translate a Rect in application's window to screen.
+ */
+ public void translateRectInAppWindowToScreen(Rect rect) {
+ // TODO Auto-generated method stub
+ if (scalingRequired) {
+ rect.scale(applicationScale);
+ }
+ switch(mShiftMode) {
+ case X_SHIFT_NONE:
+ case X_SHIFT_WINDOW:
+ break;
+ case X_SHIFT_CONTENT:
+ case X_SHIFT_AND_CLIP_CONTENT:
+ rect.offset(mXOffset, 0);
+ break;
+ }
+ }
+
+ /**
+ * Translate a Rect in screen coordinates into the app window's coordinates.
+ */
+ public void translateRectInScreenToAppWindow(Rect rect) {
+ switch (mShiftMode) {
+ case X_SHIFT_NONE:
+ case X_SHIFT_WINDOW:
+ break;
+ case X_SHIFT_CONTENT: {
+ rect.intersects(mXOffset, 0, rect.right, rect.bottom);
+ int dx = Math.min(mXOffset, rect.left);
+ rect.offset(-dx, 0);
+ break;
+ }
+ case X_SHIFT_AND_CLIP_CONTENT: {
+ rect.intersects(mXOffset, 0, mWidth + mXOffset, mHeight);
+ int dx = Math.min(mXOffset, rect.left);
+ rect.offset(-dx, 0);
+ break;
+ }
+ }
+ if (scalingRequired) {
+ rect.scale(applicationInvertedScale);
+ }
+ }
+
+ /**
+ * Translate the location of the sub window.
+ * @param params
+ */
+ public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) {
+ if (scalingRequired) {
+ params.scale(applicationScale);
+ }
+ switch (mShiftMode) {
+ // the window location on these mode does not require adjustmenet.
+ case X_SHIFT_NONE:
+ case X_SHIFT_WINDOW:
+ break;
+ case X_SHIFT_CONTENT:
+ case X_SHIFT_AND_CLIP_CONTENT:
+ params.x += mXOffset;
+ break;
+ }
+ }
+
+ /**
+ * Translate the content insets in application window to Screen. This uses
+ * the internal buffer for content insets to avoid extra object allocation.
+ */
+ public Rect getTranslatedContentInsets(Rect contentInsets) {
+ if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect();
+ mContentInsetsBuffer.set(contentInsets);
+ translateRectInAppWindowToScreen(mContentInsetsBuffer);
+ return mContentInsetsBuffer;
+ }
+
+ /**
+ * Translate the visible insets in application window to Screen. This uses
+ * the internal buffer for content insets to avoid extra object allocation.
+ */
+ public Rect getTranslatedVisbileInsets(Rect visibleInsets) {
+ if (mVisibleInsets == null) mVisibleInsets = new Rect();
+ mVisibleInsets.set(visibleInsets);
+ translateRectInAppWindowToScreen(mVisibleInsets);
+ return mVisibleInsets;
+ }
+ }
+}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index bb3486c205545776e0c3bbbfe568384aa58c9f12..577aa600a19e1e576a004ee30f274bc589d75190 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -116,6 +116,18 @@ public final class Configuration implements Parcelable, Comparable SparseArray emptySparseArray() {
- return (SparseArray) EMPTY_ARRAY;
+ private static LongSparseArray emptySparseArray() {
+ return (LongSparseArray) EMPTY_ARRAY;
}
/**
@@ -126,26 +129,59 @@ public class Resources {
*/
public Resources(AssetManager assets, DisplayMetrics metrics,
Configuration config) {
- this(assets, metrics, config, true);
+ this(assets, metrics, config, (ApplicationInfo) null);
}
/**
- * Create a resource with an additional flag for preloaded
- * drawable cache. Used by {@link ActivityThread}.
- *
+ * Creates a new Resources object with ApplicationInfo.
+ *
+ * @param assets Previously created AssetManager.
+ * @param metrics Current display metrics to consider when
+ * selecting/computing resource values.
+ * @param config Desired device configuration to consider when
+ * selecting/computing resource values (optional).
+ * @param appInfo this resource's application info.
* @hide
*/
public Resources(AssetManager assets, DisplayMetrics metrics,
- Configuration config, boolean usePreloadedCache) {
+ Configuration config, ApplicationInfo appInfo) {
mAssets = assets;
mConfiguration.setToDefaults();
mMetrics.setToDefaults();
+ if (appInfo != null) {
+ mCompatibilityInfo = new CompatibilityInfo(appInfo);
+ if (DEBUG_CONFIG) {
+ Log.d(TAG, "compatibility for " + appInfo.packageName + " : " + mCompatibilityInfo);
+ }
+ } else {
+ mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ }
updateConfiguration(config, metrics);
assets.ensureStringBlocks();
- if (usePreloadedCache) {
- mPreloadedDrawables = sPreloadedDrawables;
+ if (mCompatibilityInfo.isScalingRequired()) {
+ mPreloadedDrawables = emptySparseArray();
} else {
+ mPreloadedDrawables = sPreloadedDrawables;
+ }
+ }
+
+ /**
+ * Creates a new resources that uses the given compatibility info. Used to create
+ * a context for widgets using the container's compatibility info.
+ * {@see ApplicationContext#createPackageCotnext}.
+ * @hide
+ */
+ public Resources(AssetManager assets, DisplayMetrics metrics,
+ Configuration config, CompatibilityInfo info) {
+ mAssets = assets;
+ mMetrics.setToDefaults();
+ mCompatibilityInfo = info;
+ updateConfiguration(config, metrics);
+ assets.ensureStringBlocks();
+ if (mCompatibilityInfo.isScalingRequired()) {
mPreloadedDrawables = emptySparseArray();
+ } else {
+ mPreloadedDrawables = sPreloadedDrawables;
}
}
@@ -1238,7 +1274,7 @@ public class Resources {
return array;
}
-
+
/**
* Store the newly updated configuration.
*/
@@ -1251,6 +1287,8 @@ public class Resources {
}
if (metrics != null) {
mMetrics.setTo(metrics);
+ mMetrics.updateMetrics(mCompatibilityInfo,
+ mConfiguration.orientation, mConfiguration.screenLayout);
}
mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
@@ -1282,7 +1320,7 @@ public class Resources {
mConfiguration.touchscreen,
(int)(mMetrics.density*160), mConfiguration.keyboard,
keyboardHidden, mConfiguration.navigation, width, height,
- sSdkVersion);
+ mConfiguration.screenLayout, sSdkVersion);
int N = mDrawableCache.size();
if (DEBUG_CONFIG) {
Log.d(TAG, "Cleaning up drawables config changes: 0x"
@@ -1297,14 +1335,14 @@ public class Resources {
configChanges, cs.getChangingConfigurations())) {
if (DEBUG_CONFIG) {
Log.d(TAG, "FLUSHING #0x"
- + Integer.toHexString(mDrawableCache.keyAt(i))
+ + Long.toHexString(mDrawableCache.keyAt(i))
+ " / " + cs + " with changes: 0x"
+ Integer.toHexString(cs.getChangingConfigurations()));
}
mDrawableCache.setValueAt(i, null);
} else if (DEBUG_CONFIG) {
Log.d(TAG, "(Keeping #0x"
- + Integer.toHexString(mDrawableCache.keyAt(i))
+ + Long.toHexString(mDrawableCache.keyAt(i))
+ " / " + cs + " with changes: 0x"
+ Integer.toHexString(cs.getChangingConfigurations())
+ ")");
@@ -1356,6 +1394,17 @@ public class Resources {
public Configuration getConfiguration() {
return mConfiguration;
}
+
+ /**
+ * Return the compatibility mode information for the application.
+ * The returned object should be treated as read-only.
+ *
+ * @return compatibility info. null if the app does not require compatibility mode.
+ * @hide
+ */
+ public CompatibilityInfo getCompatibilityInfo() {
+ return mCompatibilityInfo;
+ }
/**
* Return a resource identifier for the given resource name. A fully
@@ -1624,7 +1673,7 @@ public class Resources {
}
}
- final int key = (value.assetCookie << 24) | value.data;
+ final long key = (((long) value.assetCookie) << 32) | value.data;
Drawable dr = getCachedDrawable(key);
if (dr != null) {
@@ -1704,7 +1753,7 @@ public class Resources {
return dr;
}
- private Drawable getCachedDrawable(int key) {
+ private Drawable getCachedDrawable(long key) {
synchronized (mTmpValue) {
WeakReference wr = mDrawableCache.get(key);
if (wr != null) { // we have the key
@@ -1920,5 +1969,6 @@ public class Resources {
updateConfiguration(null, null);
mAssets.ensureStringBlocks();
mPreloadedDrawables = sPreloadedDrawables;
+ mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
}
}
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
index c26810a6248feaa11f4b771a2e082cb1789fa0e5..cf30dd9a9112dc4eb637cdc330990997d518a339 100644
--- a/core/java/android/database/BulkCursorToCursorAdaptor.java
+++ b/core/java/android/database/BulkCursorToCursorAdaptor.java
@@ -247,9 +247,11 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
try {
return mBulkCursor.respond(extras);
} catch (RemoteException e) {
- // This should never happen because the system kills processes that are using remote
- // cursors when the provider process is killed.
- throw new RuntimeException(e);
+ // the system kills processes that are using remote cursors when the provider process
+ // is killed, but this can still happen if this is being called from the system process,
+ // so, better to log and return an empty bundle.
+ Log.w(TAG, "respond() threw RemoteException, returning an empty bundle.", e);
+ return Bundle.EMPTY;
}
}
}
diff --git a/core/java/android/database/sqlite/SQLiteContentHelper.java b/core/java/android/database/sqlite/SQLiteContentHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..2800d86279b1ef352961c2c16b43844ee595dd70
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteContentHelper.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2009 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.database.sqlite;
+
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.os.MemoryFile;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Some helper functions for using SQLite database to implement content providers.
+ *
+ * @hide
+ */
+public class SQLiteContentHelper {
+
+ /**
+ * Runs an SQLite query and returns an AssetFileDescriptor for the
+ * blob in column 0 of the first row. If the first column does
+ * not contain a blob, an unspecified exception is thrown.
+ *
+ * @param db Handle to a readable database.
+ * @param sql SQL query, possibly with query arguments.
+ * @param selectionArgs Query argument values, or {@code null} for no argument.
+ * @return If no exception is thrown, a non-null AssetFileDescriptor is returned.
+ * @throws FileNotFoundException If the query returns no results or the
+ * value of column 0 is NULL, or if there is an error creating the
+ * asset file descriptor.
+ */
+ public static AssetFileDescriptor getBlobColumnAsAssetFile(SQLiteDatabase db, String sql,
+ String[] selectionArgs) throws FileNotFoundException {
+ try {
+ MemoryFile file = simpleQueryForBlobMemoryFile(db, sql, selectionArgs);
+ if (file == null) {
+ throw new FileNotFoundException("No results.");
+ }
+ return AssetFileDescriptor.fromMemoryFile(file);
+ } catch (IOException ex) {
+ throw new FileNotFoundException(ex.toString());
+ }
+ }
+
+ /**
+ * Runs an SQLite query and returns a MemoryFile for the
+ * blob in column 0 of the first row. If the first column does
+ * not contain a blob, an unspecified exception is thrown.
+ *
+ * @return A memory file, or {@code null} if the query returns no results
+ * or the value column 0 is NULL.
+ * @throws IOException If there is an error creating the memory file.
+ */
+ // TODO: make this native and use the SQLite blob API to reduce copying
+ private static MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql,
+ String[] selectionArgs) throws IOException {
+ Cursor cursor = db.rawQuery(sql, selectionArgs);
+ if (cursor == null) {
+ return null;
+ }
+ try {
+ if (!cursor.moveToFirst()) {
+ return null;
+ }
+ byte[] bytes = cursor.getBlob(0);
+ if (bytes == null) {
+ return null;
+ }
+ MemoryFile file = new MemoryFile(null, bytes.length);
+ file.writeBytes(bytes, 0, 0, bytes.length);
+ file.deactivate();
+ return file;
+ } finally {
+ cursor.close();
+ }
+ }
+
+}
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index ab7c827cc7c86e0853f0dba203b785e149a981f8..8a639196ee7a414ad93af7571cf8b6f0747ee5b1 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -18,16 +18,15 @@ package android.database.sqlite;
import android.database.Cursor;
import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
import android.provider.BaseColumns;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
+import java.util.regex.Pattern;
/**
* This is a convience class that helps build SQL queries to be sent to
@@ -36,10 +35,12 @@ import java.util.Map.Entry;
public class SQLiteQueryBuilder
{
private static final String TAG = "SQLiteQueryBuilder";
+ private static final Pattern sLimitPattern =
+ Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
private Map mProjectionMap = null;
private String mTables = "";
- private StringBuilder mWhereClause = new StringBuilder(64);
+ private final StringBuilder mWhereClause = new StringBuilder(64);
private boolean mDistinct;
private SQLiteDatabase.CursorFactory mFactory;
@@ -169,6 +170,9 @@ public class SQLiteQueryBuilder
throw new IllegalArgumentException(
"HAVING clauses are only permitted when using a groupBy clause");
}
+ if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
+ throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
+ }
StringBuilder query = new StringBuilder(120);
@@ -187,7 +191,7 @@ public class SQLiteQueryBuilder
appendClause(query, " GROUP BY ", groupBy);
appendClause(query, " HAVING ", having);
appendClause(query, " ORDER BY ", orderBy);
- appendClauseEscapeClause(query, " LIMIT ", limit);
+ appendClause(query, " LIMIT ", limit);
return query.toString();
}
diff --git a/core/java/android/gesture/Gesture.java b/core/java/android/gesture/Gesture.java
new file mode 100755
index 0000000000000000000000000000000000000000..2262477a93c4fcd9d88194cef80afa63bfd14293
--- /dev/null
+++ b/core/java/android/gesture/Gesture.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2008-2009 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.gesture;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.DataOutputStream;
+import java.io.DataInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+
+/**
+ * A gesture can have a single or multiple strokes
+ */
+
+public class Gesture implements Parcelable {
+ private static final long GESTURE_ID_BASE = System.currentTimeMillis();
+
+ private static final int BITMAP_RENDERING_WIDTH = 2;
+
+ private static final boolean BITMAP_RENDERING_ANTIALIAS = true;
+ private static final boolean BITMAP_RENDERING_DITHER = true;
+
+ private static int sGestureCount = 0;
+
+ private final RectF mBoundingBox = new RectF();
+
+ // the same as its instance ID
+ private long mGestureID;
+
+ private final ArrayList mStrokes = new ArrayList();
+
+ public Gesture() {
+ mGestureID = GESTURE_ID_BASE + sGestureCount++;
+ }
+
+ void recycle() {
+ mStrokes.clear();
+ mBoundingBox.setEmpty();
+ }
+
+ /**
+ * @return all the strokes of the gesture
+ */
+ public ArrayList getStrokes() {
+ return mStrokes;
+ }
+
+ /**
+ * @return the number of strokes included by this gesture
+ */
+ public int getStrokesCount() {
+ return mStrokes.size();
+ }
+
+ /**
+ * Add a stroke to the gesture
+ *
+ * @param stroke
+ */
+ public void addStroke(GestureStroke stroke) {
+ mStrokes.add(stroke);
+ mBoundingBox.union(stroke.boundingBox);
+ }
+
+ /**
+ * Get the total length of the gesture. When there are multiple strokes in
+ * the gesture, this returns the sum of the lengths of all the strokes
+ *
+ * @return the length of the gesture
+ */
+ public float getLength() {
+ int len = 0;
+ final ArrayList strokes = mStrokes;
+ final int count = strokes.size();
+
+ for (int i = 0; i < count; i++) {
+ len += strokes.get(i).length;
+ }
+
+ return len;
+ }
+
+ /**
+ * @return the bounding box of the gesture
+ */
+ public RectF getBoundingBox() {
+ return mBoundingBox;
+ }
+
+ public Path toPath() {
+ return toPath(null);
+ }
+
+ public Path toPath(Path path) {
+ if (path == null) path = new Path();
+
+ final ArrayList strokes = mStrokes;
+ final int count = strokes.size();
+
+ for (int i = 0; i < count; i++) {
+ path.addPath(strokes.get(i).getPath());
+ }
+
+ return path;
+ }
+
+ public Path toPath(int width, int height, int edge, int numSample) {
+ return toPath(null, width, height, edge, numSample);
+ }
+
+ public Path toPath(Path path, int width, int height, int edge, int numSample) {
+ if (path == null) path = new Path();
+
+ final ArrayList strokes = mStrokes;
+ final int count = strokes.size();
+
+ for (int i = 0; i < count; i++) {
+ path.addPath(strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample));
+ }
+
+ return path;
+ }
+
+ /**
+ * Set the id of the gesture
+ *
+ * @param id
+ */
+ void setID(long id) {
+ mGestureID = id;
+ }
+
+ /**
+ * @return the id of the gesture
+ */
+ public long getID() {
+ return mGestureID;
+ }
+
+ /**
+ * draw the gesture
+ *
+ * @param canvas
+ */
+ void draw(Canvas canvas, Paint paint) {
+ final ArrayList strokes = mStrokes;
+ final int count = strokes.size();
+
+ for (int i = 0; i < count; i++) {
+ strokes.get(i).draw(canvas, paint);
+ }
+ }
+
+ /**
+ * Create a bitmap of the gesture with a transparent background
+ *
+ * @param width width of the target bitmap
+ * @param height height of the target bitmap
+ * @param edge the edge
+ * @param numSample
+ * @param color
+ * @return the bitmap
+ */
+ public Bitmap toBitmap(int width, int height, int edge, int numSample, int color) {
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+
+ canvas.translate(edge, edge);
+
+ final Paint paint = new Paint();
+ paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
+ paint.setDither(BITMAP_RENDERING_DITHER);
+ paint.setColor(color);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);
+
+ final ArrayList strokes = mStrokes;
+ final int count = strokes.size();
+
+ for (int i = 0; i < count; i++) {
+ Path path = strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample);
+ canvas.drawPath(path, paint);
+ }
+
+ return bitmap;
+ }
+
+ /**
+ * Create a bitmap of the gesture with a transparent background
+ *
+ * @param width
+ * @param height
+ * @param inset
+ * @param color
+ * @return the bitmap
+ */
+ public Bitmap toBitmap(int width, int height, int inset, int color) {
+ final Bitmap bitmap = Bitmap.createBitmap(width, height,
+ Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+
+ final Paint paint = new Paint();
+ paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
+ paint.setDither(BITMAP_RENDERING_DITHER);
+ paint.setColor(color);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);
+
+ final Path path = toPath();
+ final RectF bounds = new RectF();
+ path.computeBounds(bounds, true);
+
+ final float sx = (width - 2 * inset) / bounds.width();
+ final float sy = (height - 2 * inset) / bounds.height();
+ final float scale = sx > sy ? sy : sx;
+ paint.setStrokeWidth(2.0f / scale);
+
+ path.offset(-bounds.left + (width - bounds.width() * scale) / 2.0f,
+ -bounds.top + (height - bounds.height() * scale) / 2.0f);
+
+ canvas.translate(inset, inset);
+ canvas.scale(scale, scale);
+
+ canvas.drawPath(path, paint);
+
+ return bitmap;
+ }
+
+ void serialize(DataOutputStream out) throws IOException {
+ final ArrayList strokes = mStrokes;
+ final int count = strokes.size();
+
+ // Write gesture ID
+ out.writeLong(mGestureID);
+ // Write number of strokes
+ out.writeInt(count);
+
+ for (int i = 0; i < count; i++) {
+ strokes.get(i).serialize(out);
+ }
+ }
+
+ static Gesture deserialize(DataInputStream in) throws IOException {
+ final Gesture gesture = new Gesture();
+
+ // Gesture ID
+ gesture.mGestureID = in.readLong();
+ // Number of strokes
+ final int count = in.readInt();
+
+ for (int i = 0; i < count; i++) {
+ gesture.addStroke(GestureStroke.deserialize(in));
+ }
+
+ return gesture;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public Gesture createFromParcel(Parcel in) {
+ Gesture gesture = null;
+ final long gestureID = in.readLong();
+
+ final DataInputStream inStream = new DataInputStream(
+ new ByteArrayInputStream(in.createByteArray()));
+
+ try {
+ gesture = deserialize(inStream);
+ } catch (IOException e) {
+ Log.e(GestureConstants.LOG_TAG, "Error reading Gesture from parcel:", e);
+ } finally {
+ GestureUtilities.closeStream(inStream);
+ }
+
+ if (gesture != null) {
+ gesture.mGestureID = gestureID;
+ }
+
+ return gesture;
+ }
+
+ public Gesture[] newArray(int size) {
+ return new Gesture[size];
+ }
+ };
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mGestureID);
+
+ boolean result = false;
+ final ByteArrayOutputStream byteStream =
+ new ByteArrayOutputStream(GestureConstants.IO_BUFFER_SIZE);
+ final DataOutputStream outStream = new DataOutputStream(byteStream);
+
+ try {
+ serialize(outStream);
+ result = true;
+ } catch (IOException e) {
+ Log.e(GestureConstants.LOG_TAG, "Error writing Gesture to parcel:", e);
+ } finally {
+ GestureUtilities.closeStream(outStream);
+ GestureUtilities.closeStream(byteStream);
+ }
+
+ if (result) {
+ out.writeByteArray(byteStream.toByteArray());
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+}
+
diff --git a/core/java/android/gesture/GestureConstants.java b/core/java/android/gesture/GestureConstants.java
new file mode 100644
index 0000000000000000000000000000000000000000..230db0c00c50162cc7081213bc5bdb4b7a61ec2a
--- /dev/null
+++ b/core/java/android/gesture/GestureConstants.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2009 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.gesture;
+
+interface GestureConstants {
+ static final int STROKE_STRING_BUFFER_SIZE = 1024;
+ static final int STROKE_POINT_BUFFER_SIZE = 100; // number of points
+
+ static final int IO_BUFFER_SIZE = 32 * 1024; // 32K
+
+ static final String LOG_TAG = "Gestures";
+}
diff --git a/core/java/android/gesture/GestureLibraries.java b/core/java/android/gesture/GestureLibraries.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d6c156def9a432f58b99fa4d301e28625c77095
--- /dev/null
+++ b/core/java/android/gesture/GestureLibraries.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2009 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.gesture;
+
+import android.util.Log;
+import static android.gesture.GestureConstants.*;
+import android.content.Context;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+
+public final class GestureLibraries {
+ private GestureLibraries() {
+ }
+
+ public static GestureLibrary fromFile(String path) {
+ return fromFile(new File(path));
+ }
+
+ public static GestureLibrary fromFile(File path) {
+ return new FileGestureLibrary(path);
+ }
+
+ public static GestureLibrary fromPrivateFile(Context context, String name) {
+ return fromFile(context.getFileStreamPath(name));
+ }
+
+ public static GestureLibrary fromRawResource(Context context, int resourceId) {
+ return new ResourceGestureLibrary(context, resourceId);
+ }
+
+ private static class FileGestureLibrary extends GestureLibrary {
+ private final File mPath;
+
+ public FileGestureLibrary(File path) {
+ mPath = path;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return !mPath.canWrite();
+ }
+
+ public boolean save() {
+ if (!mStore.hasChanged()) return true;
+
+ final File file = mPath;
+
+ final File parentFile = file.getParentFile();
+ if (!parentFile.exists()) {
+ if (!parentFile.mkdirs()) {
+ return false;
+ }
+ }
+
+ boolean result = false;
+ try {
+ //noinspection ResultOfMethodCallIgnored
+ file.createNewFile();
+ mStore.save(new FileOutputStream(file), true);
+ result = true;
+ } catch (FileNotFoundException e) {
+ Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
+ }
+
+ return result;
+ }
+
+ public boolean load() {
+ boolean result = false;
+ final File file = mPath;
+ if (file.exists() && file.canRead()) {
+ try {
+ mStore.load(new FileInputStream(file), true);
+ result = true;
+ } catch (FileNotFoundException e) {
+ Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
+ }
+ }
+
+ return result;
+ }
+ }
+
+ private static class ResourceGestureLibrary extends GestureLibrary {
+ private final WeakReference mContext;
+ private final int mResourceId;
+
+ public ResourceGestureLibrary(Context context, int resourceId) {
+ mContext = new WeakReference(context);
+ mResourceId = resourceId;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return true;
+ }
+
+ public boolean save() {
+ return false;
+ }
+
+ public boolean load() {
+ boolean result = false;
+ final Context context = mContext.get();
+ if (context != null) {
+ final InputStream in = context.getResources().openRawResource(mResourceId);
+ try {
+ mStore.load(in, true);
+ result = true;
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Could not load the gesture library from raw resource " +
+ context.getResources().getResourceName(mResourceId), e);
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/core/java/android/gesture/GestureLibrary.java b/core/java/android/gesture/GestureLibrary.java
new file mode 100644
index 0000000000000000000000000000000000000000..a29c2c83cfbf030279eb62ff2572e0d283607b49
--- /dev/null
+++ b/core/java/android/gesture/GestureLibrary.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2009 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.gesture;
+
+import java.util.Set;
+import java.util.ArrayList;
+
+public abstract class GestureLibrary {
+ protected final GestureStore mStore;
+
+ protected GestureLibrary() {
+ mStore = new GestureStore();
+ }
+
+ public abstract boolean save();
+
+ public abstract boolean load();
+
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ public Learner getLearner() {
+ return mStore.getLearner();
+ }
+
+ public void setOrientationStyle(int style) {
+ mStore.setOrientationStyle(style);
+ }
+
+ public int getOrientationStyle() {
+ return mStore.getOrientationStyle();
+ }
+
+ public void setSequenceType(int type) {
+ mStore.setSequenceType(type);
+ }
+
+ public int getSequenceType() {
+ return mStore.getSequenceType();
+ }
+
+ public Set getGestureEntries() {
+ return mStore.getGestureEntries();
+ }
+
+ public ArrayList recognize(Gesture gesture) {
+ return mStore.recognize(gesture);
+ }
+
+ public void addGesture(String entryName, Gesture gesture) {
+ mStore.addGesture(entryName, gesture);
+ }
+
+ public void removeGesture(String entryName, Gesture gesture) {
+ mStore.removeGesture(entryName, gesture);
+ }
+
+ public void removeEntry(String entryName) {
+ mStore.removeEntry(entryName);
+ }
+
+ public ArrayList getGestures(String entryName) {
+ return mStore.getGestures(entryName);
+ }
+}
diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java
new file mode 100755
index 0000000000000000000000000000000000000000..5bfdcc4a5578c6abe4f409838aa937578ddc3a0c
--- /dev/null
+++ b/core/java/android/gesture/GestureOverlayView.java
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2009 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.gesture;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.animation.AnimationUtils;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.os.SystemClock;
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/**
+ * A transparent overlay for gesture input that can be placed on top of other
+ * widgets or contain other widgets.
+ *
+ * @attr ref android.R.styleable#GestureOverlayView_eventsInterceptionEnabled
+ * @attr ref android.R.styleable#GestureOverlayView_fadeDuration
+ * @attr ref android.R.styleable#GestureOverlayView_fadeOffset
+ * @attr ref android.R.styleable#GestureOverlayView_fadeEnabled
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeAngleThreshold
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeLengthThreshold
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeSquarenessThreshold
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeType
+ * @attr ref android.R.styleable#GestureOverlayView_gestureColor
+ * @attr ref android.R.styleable#GestureOverlayView_orientation
+ * @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
+ */
+public class GestureOverlayView extends FrameLayout {
+ public static final int GESTURE_STROKE_TYPE_SINGLE = 0;
+ public static final int GESTURE_STROKE_TYPE_MULTIPLE = 1;
+
+ public static final int ORIENTATION_HORIZONTAL = 0;
+ public static final int ORIENTATION_VERTICAL = 1;
+
+ private static final int FADE_ANIMATION_RATE = 16;
+ private static final boolean GESTURE_RENDERING_ANTIALIAS = true;
+ private static final boolean DITHER_FLAG = true;
+
+ private final Paint mGesturePaint = new Paint();
+
+ private long mFadeDuration = 150;
+ private long mFadeOffset = 420;
+ private long mFadingStart;
+ private boolean mFadingHasStarted;
+ private boolean mFadeEnabled = true;
+
+ private int mCurrentColor;
+ private int mCertainGestureColor = 0xFFFFFF00;
+ private int mUncertainGestureColor = 0x48FFFF00;
+ private float mGestureStrokeWidth = 12.0f;
+ private int mInvalidateExtraBorder = 10;
+
+ private int mGestureStrokeType = GESTURE_STROKE_TYPE_SINGLE;
+ private float mGestureStrokeLengthThreshold = 50.0f;
+ private float mGestureStrokeSquarenessTreshold = 0.275f;
+ private float mGestureStrokeAngleThreshold = 40.0f;
+
+ private int mOrientation = ORIENTATION_VERTICAL;
+
+ private final Rect mInvalidRect = new Rect();
+ private final Path mPath = new Path();
+ private boolean mGestureVisible = true;
+
+ private float mX;
+ private float mY;
+
+ private float mCurveEndX;
+ private float mCurveEndY;
+
+ private float mTotalLength;
+ private boolean mIsGesturing = false;
+ private boolean mPreviousWasGesturing = false;
+ private boolean mInterceptEvents = true;
+ private boolean mIsListeningForGestures;
+ private boolean mResetGesture;
+
+ // current gesture
+ private Gesture mCurrentGesture;
+ private final ArrayList mStrokeBuffer = new ArrayList(100);
+
+ // TODO: Make this a list of WeakReferences
+ private final ArrayList mOnGestureListeners =
+ new ArrayList();
+ // TODO: Make this a list of WeakReferences
+ private final ArrayList mOnGesturePerformedListeners =
+ new ArrayList();
+ // TODO: Make this a list of WeakReferences
+ private final ArrayList mOnGesturingListeners =
+ new ArrayList();
+
+ private boolean mHandleGestureActions;
+
+ // fading out effect
+ private boolean mIsFadingOut = false;
+ private float mFadingAlpha = 1.0f;
+ private final AccelerateDecelerateInterpolator mInterpolator =
+ new AccelerateDecelerateInterpolator();
+
+ private final FadeOutRunnable mFadingOut = new FadeOutRunnable();
+
+ public GestureOverlayView(Context context) {
+ super(context);
+ init();
+ }
+
+ public GestureOverlayView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle);
+ }
+
+ public GestureOverlayView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.GestureOverlayView, defStyle, 0);
+
+ mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
+ mGestureStrokeWidth);
+ mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
+ mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor,
+ mCertainGestureColor);
+ mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor,
+ mUncertainGestureColor);
+ mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration);
+ mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset);
+ mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType,
+ mGestureStrokeType);
+ mGestureStrokeLengthThreshold = a.getFloat(
+ R.styleable.GestureOverlayView_gestureStrokeLengthThreshold,
+ mGestureStrokeLengthThreshold);
+ mGestureStrokeAngleThreshold = a.getFloat(
+ R.styleable.GestureOverlayView_gestureStrokeAngleThreshold,
+ mGestureStrokeAngleThreshold);
+ mGestureStrokeSquarenessTreshold = a.getFloat(
+ R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold,
+ mGestureStrokeSquarenessTreshold);
+ mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled,
+ mInterceptEvents);
+ mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled,
+ mFadeEnabled);
+ mOrientation = a.getInt(R.styleable.GestureOverlayView_orientation, mOrientation);
+
+ a.recycle();
+
+ init();
+ }
+
+ private void init() {
+ setWillNotDraw(false);
+
+ final Paint gesturePaint = mGesturePaint;
+ gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
+ gesturePaint.setColor(mCertainGestureColor);
+ gesturePaint.setStyle(Paint.Style.STROKE);
+ gesturePaint.setStrokeJoin(Paint.Join.ROUND);
+ gesturePaint.setStrokeCap(Paint.Cap.ROUND);
+ gesturePaint.setStrokeWidth(mGestureStrokeWidth);
+ gesturePaint.setDither(DITHER_FLAG);
+
+ mCurrentColor = mCertainGestureColor;
+ setPaintAlpha(255);
+ }
+
+ public ArrayList getCurrentStroke() {
+ return mStrokeBuffer;
+ }
+
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ public void setOrientation(int orientation) {
+ mOrientation = orientation;
+ }
+
+ public void setGestureColor(int color) {
+ mCertainGestureColor = color;
+ }
+
+ public void setUncertainGestureColor(int color) {
+ mUncertainGestureColor = color;
+ }
+
+ public int getUncertainGestureColor() {
+ return mUncertainGestureColor;
+ }
+
+ public int getGestureColor() {
+ return mCertainGestureColor;
+ }
+
+ public float getGestureStrokeWidth() {
+ return mGestureStrokeWidth;
+ }
+
+ public void setGestureStrokeWidth(float gestureStrokeWidth) {
+ mGestureStrokeWidth = gestureStrokeWidth;
+ mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
+ mGesturePaint.setStrokeWidth(gestureStrokeWidth);
+ }
+
+ public int getGestureStrokeType() {
+ return mGestureStrokeType;
+ }
+
+ public void setGestureStrokeType(int gestureStrokeType) {
+ mGestureStrokeType = gestureStrokeType;
+ }
+
+ public float getGestureStrokeLengthThreshold() {
+ return mGestureStrokeLengthThreshold;
+ }
+
+ public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
+ mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
+ }
+
+ public float getGestureStrokeSquarenessTreshold() {
+ return mGestureStrokeSquarenessTreshold;
+ }
+
+ public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
+ mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
+ }
+
+ public float getGestureStrokeAngleThreshold() {
+ return mGestureStrokeAngleThreshold;
+ }
+
+ public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
+ mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
+ }
+
+ public boolean isEventsInterceptionEnabled() {
+ return mInterceptEvents;
+ }
+
+ public void setEventsInterceptionEnabled(boolean enabled) {
+ mInterceptEvents = enabled;
+ }
+
+ public boolean isFadeEnabled() {
+ return mFadeEnabled;
+ }
+
+ public void setFadeEnabled(boolean fadeEnabled) {
+ mFadeEnabled = fadeEnabled;
+ }
+
+ public Gesture getGesture() {
+ return mCurrentGesture;
+ }
+
+ public void setGesture(Gesture gesture) {
+ if (mCurrentGesture != null) {
+ clear(false);
+ }
+
+ setCurrentColor(mCertainGestureColor);
+ mCurrentGesture = gesture;
+
+ final Path path = mCurrentGesture.toPath();
+ final RectF bounds = new RectF();
+ path.computeBounds(bounds, true);
+
+ // TODO: The path should also be scaled to fit inside this view
+ mPath.rewind();
+ mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f,
+ -bounds.top + (getHeight() - bounds.height()) / 2.0f);
+
+ mResetGesture = true;
+
+ invalidate();
+ }
+
+ public Path getGesturePath() {
+ return mPath;
+ }
+
+ public Path getGesturePath(Path path) {
+ path.set(mPath);
+ return path;
+ }
+
+ public boolean isGestureVisible() {
+ return mGestureVisible;
+ }
+
+ public void setGestureVisible(boolean visible) {
+ mGestureVisible = visible;
+ }
+
+ public long getFadeOffset() {
+ return mFadeOffset;
+ }
+
+ public void setFadeOffset(long fadeOffset) {
+ mFadeOffset = fadeOffset;
+ }
+
+ public void addOnGestureListener(OnGestureListener listener) {
+ mOnGestureListeners.add(listener);
+ }
+
+ public void removeOnGestureListener(OnGestureListener listener) {
+ mOnGestureListeners.remove(listener);
+ }
+
+ public void removeAllOnGestureListeners() {
+ mOnGestureListeners.clear();
+ }
+
+ public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
+ mOnGesturePerformedListeners.add(listener);
+ if (mOnGesturePerformedListeners.size() > 0) {
+ mHandleGestureActions = true;
+ }
+ }
+
+ public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
+ mOnGesturePerformedListeners.remove(listener);
+ if (mOnGesturePerformedListeners.size() <= 0) {
+ mHandleGestureActions = false;
+ }
+ }
+
+ public void removeAllOnGesturePerformedListeners() {
+ mOnGesturePerformedListeners.clear();
+ mHandleGestureActions = false;
+ }
+
+ public void addOnGesturingListener(OnGesturingListener listener) {
+ mOnGesturingListeners.add(listener);
+ }
+
+ public void removeOnGesturingListener(OnGesturingListener listener) {
+ mOnGesturingListeners.remove(listener);
+ }
+
+ public void removeAllOnGesturingListeners() {
+ mOnGesturingListeners.clear();
+ }
+
+ public boolean isGesturing() {
+ return mIsGesturing;
+ }
+
+ private void setCurrentColor(int color) {
+ mCurrentColor = color;
+ if (mFadingHasStarted) {
+ setPaintAlpha((int) (255 * mFadingAlpha));
+ } else {
+ setPaintAlpha(255);
+ }
+ invalidate();
+ }
+
+ /**
+ * @hide
+ */
+ public Paint getGesturePaint() {
+ return mGesturePaint;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mCurrentGesture != null && mGestureVisible) {
+ canvas.drawPath(mPath, mGesturePaint);
+ }
+ }
+
+ private void setPaintAlpha(int alpha) {
+ alpha += alpha >> 7;
+ final int baseAlpha = mCurrentColor >>> 24;
+ final int useAlpha = baseAlpha * alpha >> 8;
+ mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24));
+ }
+
+ public void clear(boolean animated) {
+ clear(animated, false, true);
+ }
+
+ private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {
+ setPaintAlpha(255);
+ removeCallbacks(mFadingOut);
+ mResetGesture = false;
+ mFadingOut.fireActionPerformed = fireActionPerformed;
+ mFadingOut.resetMultipleStrokes = false;
+
+ if (animated && mCurrentGesture != null) {
+ mFadingAlpha = 1.0f;
+ mIsFadingOut = true;
+ mFadingHasStarted = false;
+ mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
+
+ postDelayed(mFadingOut, mFadeOffset);
+ } else {
+ mFadingAlpha = 1.0f;
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+
+ if (immediate) {
+ mCurrentGesture = null;
+ mPath.rewind();
+ invalidate();
+ } else if (fireActionPerformed) {
+ postDelayed(mFadingOut, mFadeOffset);
+ } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {
+ mFadingOut.resetMultipleStrokes = true;
+ postDelayed(mFadingOut, mFadeOffset);
+ } else {
+ mCurrentGesture = null;
+ mPath.rewind();
+ invalidate();
+ }
+ }
+ }
+
+ public void cancelClearAnimation() {
+ setPaintAlpha(255);
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+ removeCallbacks(mFadingOut);
+ mPath.rewind();
+ mCurrentGesture = null;
+ }
+
+ public void cancelGesture() {
+ mIsListeningForGestures = false;
+
+ // add the stroke to the current gesture
+ mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
+
+ // pass the event to handlers
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+
+ final ArrayList listeners = mOnGestureListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureCancelled(this, event);
+ }
+
+ event.recycle();
+
+ clear(false);
+ mIsGesturing = false;
+ mPreviousWasGesturing = false;
+ mStrokeBuffer.clear();
+
+ final ArrayList otherListeners = mOnGesturingListeners;
+ count = otherListeners.size();
+ for (int i = 0; i < count; i++) {
+ otherListeners.get(i).onGesturingEnded(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ cancelClearAnimation();
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (isEnabled()) {
+ final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
+ mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
+ mInterceptEvents;
+
+ processEvent(event);
+
+ if (cancelDispatch) {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ }
+
+ super.dispatchTouchEvent(event);
+
+ return true;
+ }
+
+ return super.dispatchTouchEvent(event);
+ }
+
+ private boolean processEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ touchDown(event);
+ invalidate();
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ if (mIsListeningForGestures) {
+ Rect rect = touchMove(event);
+ if (rect != null) {
+ invalidate(rect);
+ }
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mIsListeningForGestures) {
+ touchUp(event, false);
+ invalidate();
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsListeningForGestures) {
+ touchUp(event, true);
+ invalidate();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void touchDown(MotionEvent event) {
+ mIsListeningForGestures = true;
+
+ float x = event.getX();
+ float y = event.getY();
+
+ mX = x;
+ mY = y;
+
+ mTotalLength = 0;
+ mIsGesturing = false;
+
+ if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {
+ if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
+ mResetGesture = false;
+ mCurrentGesture = null;
+ mPath.rewind();
+ } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
+ if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
+ }
+
+ // if there is fading out going on, stop it.
+ if (mFadingHasStarted) {
+ cancelClearAnimation();
+ } else if (mIsFadingOut) {
+ setPaintAlpha(255);
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+ removeCallbacks(mFadingOut);
+ }
+
+ if (mCurrentGesture == null) {
+ mCurrentGesture = new Gesture();
+ }
+
+ mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+ mPath.moveTo(x, y);
+
+ final int border = mInvalidateExtraBorder;
+ mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
+
+ mCurveEndX = x;
+ mCurveEndY = y;
+
+ // pass the event to handlers
+ final ArrayList listeners = mOnGestureListeners;
+ final int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureStarted(this, event);
+ }
+ }
+
+ private Rect touchMove(MotionEvent event) {
+ Rect areaToRefresh = null;
+
+ final float x = event.getX();
+ final float y = event.getY();
+
+ final float previousX = mX;
+ final float previousY = mY;
+
+ final float dx = Math.abs(x - previousX);
+ final float dy = Math.abs(y - previousY);
+
+ if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {
+ areaToRefresh = mInvalidRect;
+
+ // start with the curve end
+ final int border = mInvalidateExtraBorder;
+ areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
+ (int) mCurveEndX + border, (int) mCurveEndY + border);
+
+ float cX = mCurveEndX = (x + previousX) / 2;
+ float cY = mCurveEndY = (y + previousY) / 2;
+
+ mPath.quadTo(previousX, previousY, cX, cY);
+
+ // union with the control point of the new curve
+ areaToRefresh.union((int) previousX - border, (int) previousY - border,
+ (int) previousX + border, (int) previousY + border);
+
+ // union with the end point of the new curve
+ areaToRefresh.union((int) cX - border, (int) cY - border,
+ (int) cX + border, (int) cY + border);
+
+ mX = x;
+ mY = y;
+
+ mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+
+ if (mHandleGestureActions && !mIsGesturing) {
+ mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
+
+ if (mTotalLength > mGestureStrokeLengthThreshold) {
+ final OrientedBoundingBox box =
+ GestureUtilities.computeOrientedBoundingBox(mStrokeBuffer);
+
+ float angle = Math.abs(box.orientation);
+ if (angle > 90) {
+ angle = 180 - angle;
+ }
+
+ if (box.squareness > mGestureStrokeSquarenessTreshold ||
+ (mOrientation == ORIENTATION_VERTICAL ?
+ angle < mGestureStrokeAngleThreshold :
+ angle > mGestureStrokeAngleThreshold)) {
+
+ mIsGesturing = true;
+ setCurrentColor(mCertainGestureColor);
+
+ final ArrayList listeners = mOnGesturingListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesturingStarted(this);
+ }
+ }
+ }
+ }
+
+ // pass the event to handlers
+ final ArrayList listeners = mOnGestureListeners;
+ final int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesture(this, event);
+ }
+ }
+
+ return areaToRefresh;
+ }
+
+ private void touchUp(MotionEvent event, boolean cancel) {
+ mIsListeningForGestures = false;
+
+ // A gesture wasn't started or was cancelled
+ if (mCurrentGesture != null) {
+ // add the stroke to the current gesture
+ mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
+
+ if (!cancel) {
+ // pass the event to handlers
+ final ArrayList listeners = mOnGestureListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureEnded(this, event);
+ }
+
+ clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
+ false);
+ } else {
+ cancelGesture(event);
+
+ }
+ } else {
+ cancelGesture(event);
+ }
+
+ mStrokeBuffer.clear();
+ mPreviousWasGesturing = mIsGesturing;
+ mIsGesturing = false;
+
+ final ArrayList listeners = mOnGesturingListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesturingEnded(this);
+ }
+ }
+
+ private void cancelGesture(MotionEvent event) {
+ // pass the event to handlers
+ final ArrayList listeners = mOnGestureListeners;
+ final int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureCancelled(this, event);
+ }
+
+ clear(false);
+ }
+
+ private void fireOnGesturePerformed() {
+ final ArrayList actionListeners = mOnGesturePerformedListeners;
+ final int count = actionListeners.size();
+ for (int i = 0; i < count; i++) {
+ actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
+ }
+ }
+
+ private class FadeOutRunnable implements Runnable {
+ boolean fireActionPerformed;
+ boolean resetMultipleStrokes;
+
+ public void run() {
+ if (mIsFadingOut) {
+ final long now = AnimationUtils.currentAnimationTimeMillis();
+ final long duration = now - mFadingStart;
+
+ if (duration > mFadeDuration) {
+ if (fireActionPerformed) {
+ fireOnGesturePerformed();
+ }
+
+ mPreviousWasGesturing = false;
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+ mPath.rewind();
+ mCurrentGesture = null;
+ setPaintAlpha(255);
+ } else {
+ mFadingHasStarted = true;
+ float interpolatedTime = Math.max(0.0f,
+ Math.min(1.0f, duration / (float) mFadeDuration));
+ mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
+ setPaintAlpha((int) (255 * mFadingAlpha));
+ postDelayed(this, FADE_ANIMATION_RATE);
+ }
+ } else if (resetMultipleStrokes) {
+ mResetGesture = true;
+ } else {
+ fireOnGesturePerformed();
+
+ mFadingHasStarted = false;
+ mPath.rewind();
+ mCurrentGesture = null;
+ mPreviousWasGesturing = false;
+ setPaintAlpha(255);
+ }
+
+ invalidate();
+ }
+ }
+
+ public static interface OnGesturingListener {
+ void onGesturingStarted(GestureOverlayView overlay);
+
+ void onGesturingEnded(GestureOverlayView overlay);
+ }
+
+ public static interface OnGestureListener {
+ void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
+
+ void onGesture(GestureOverlayView overlay, MotionEvent event);
+
+ void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
+
+ void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
+ }
+
+ public static interface OnGesturePerformedListener {
+ void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
+ }
+}
diff --git a/core/java/android/gesture/GesturePoint.java b/core/java/android/gesture/GesturePoint.java
new file mode 100644
index 0000000000000000000000000000000000000000..3698011fc7fcd578eb433f9046e6ae142dd78a0c
--- /dev/null
+++ b/core/java/android/gesture/GesturePoint.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2008-2009 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.gesture;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * A timed point of a gesture stroke
+ */
+
+public class GesturePoint {
+ public final float x;
+ public final float y;
+
+ public final long timestamp;
+
+ public GesturePoint(float x, float y, long t) {
+ this.x = x;
+ this.y = y;
+ timestamp = t;
+ }
+
+ static GesturePoint deserialize(DataInputStream in) throws IOException {
+ // Read X and Y
+ final float x = in.readFloat();
+ final float y = in.readFloat();
+ // Read timestamp
+ final long timeStamp = in.readLong();
+ return new GesturePoint(x, y, timeStamp);
+ }
+}
diff --git a/core/java/android/gesture/GestureStore.java b/core/java/android/gesture/GestureStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f1a44503604e436183daa6c22c4da50a1c9fc1e
--- /dev/null
+++ b/core/java/android/gesture/GestureStore.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2008-2009 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.gesture;
+
+import android.util.Log;
+import android.os.SystemClock;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.DataOutputStream;
+import java.io.DataInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.Map;
+
+import static android.gesture.GestureConstants.LOG_TAG;
+
+/**
+ * GestureLibrary maintains gesture examples and makes predictions on a new
+ * gesture
+ */
+//
+// File format for GestureStore:
+//
+// Nb. bytes Java type Description
+// -----------------------------------
+// Header
+// 2 bytes short File format version number
+// 4 bytes int Number of entries
+// Entry
+// X bytes UTF String Entry name
+// 4 bytes int Number of gestures
+// Gesture
+// 8 bytes long Gesture ID
+// 4 bytes int Number of strokes
+// Stroke
+// 4 bytes int Number of points
+// Point
+// 4 bytes float X coordinate of the point
+// 4 bytes float Y coordinate of the point
+// 8 bytes long Time stamp
+//
+public class GestureStore {
+ public static final int SEQUENCE_INVARIANT = 1;
+ // when SEQUENCE_SENSITIVE is used, only single stroke gestures are currently allowed
+ public static final int SEQUENCE_SENSITIVE = 2;
+
+ // ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures
+ public static final int ORIENTATION_INVARIANT = 1;
+ public static final int ORIENTATION_SENSITIVE = 2;
+
+ private static final short FILE_FORMAT_VERSION = 1;
+
+ private static final boolean PROFILE_LOADING_SAVING = false;
+
+ private int mSequenceType = SEQUENCE_SENSITIVE;
+ private int mOrientationStyle = ORIENTATION_SENSITIVE;
+
+ private final HashMap> mNamedGestures =
+ new HashMap>();
+
+ private Learner mClassifier;
+
+ private boolean mChanged = false;
+
+ public GestureStore() {
+ mClassifier = new InstanceLearner();
+ }
+
+ /**
+ * Specify how the gesture library will handle orientation.
+ * Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE
+ *
+ * @param style
+ */
+ public void setOrientationStyle(int style) {
+ mOrientationStyle = style;
+ }
+
+ public int getOrientationStyle() {
+ return mOrientationStyle;
+ }
+
+ /**
+ * @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
+ */
+ public void setSequenceType(int type) {
+ mSequenceType = type;
+ }
+
+ /**
+ * @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
+ */
+ public int getSequenceType() {
+ return mSequenceType;
+ }
+
+ /**
+ * Get all the gesture entry names in the library
+ *
+ * @return a set of strings
+ */
+ public Set getGestureEntries() {
+ return mNamedGestures.keySet();
+ }
+
+ /**
+ * Recognize a gesture
+ *
+ * @param gesture the query
+ * @return a list of predictions of possible entries for a given gesture
+ */
+ public ArrayList recognize(Gesture gesture) {
+ Instance instance = Instance.createInstance(mSequenceType,
+ mOrientationStyle, gesture, null);
+ return mClassifier.classify(mSequenceType, instance.vector);
+ }
+
+ /**
+ * Add a gesture for the entry
+ *
+ * @param entryName entry name
+ * @param gesture
+ */
+ public void addGesture(String entryName, Gesture gesture) {
+ if (entryName == null || entryName.length() == 0) {
+ return;
+ }
+ ArrayList gestures = mNamedGestures.get(entryName);
+ if (gestures == null) {
+ gestures = new ArrayList();
+ mNamedGestures.put(entryName, gestures);
+ }
+ gestures.add(gesture);
+ mClassifier.addInstance(
+ Instance.createInstance(mSequenceType, mOrientationStyle, gesture, entryName));
+ mChanged = true;
+ }
+
+ /**
+ * Remove a gesture from the library. If there are no more gestures for the
+ * given entry, the gesture entry will be removed.
+ *
+ * @param entryName entry name
+ * @param gesture
+ */
+ public void removeGesture(String entryName, Gesture gesture) {
+ ArrayList gestures = mNamedGestures.get(entryName);
+ if (gestures == null) {
+ return;
+ }
+
+ gestures.remove(gesture);
+
+ // if there are no more samples, remove the entry automatically
+ if (gestures.isEmpty()) {
+ mNamedGestures.remove(entryName);
+ }
+
+ mClassifier.removeInstance(gesture.getID());
+
+ mChanged = true;
+ }
+
+ /**
+ * Remove a entry of gestures
+ *
+ * @param entryName the entry name
+ */
+ public void removeEntry(String entryName) {
+ mNamedGestures.remove(entryName);
+ mClassifier.removeInstances(entryName);
+ mChanged = true;
+ }
+
+ /**
+ * Get all the gestures of an entry
+ *
+ * @param entryName
+ * @return the list of gestures that is under this name
+ */
+ public ArrayList getGestures(String entryName) {
+ ArrayList gestures = mNamedGestures.get(entryName);
+ if (gestures != null) {
+ return new ArrayList(gestures);
+ } else {
+ return null;
+ }
+ }
+
+ public boolean hasChanged() {
+ return mChanged;
+ }
+
+ /**
+ * Save the gesture library
+ */
+ public void save(OutputStream stream) throws IOException {
+ save(stream, false);
+ }
+
+ public void save(OutputStream stream, boolean closeStream) throws IOException {
+ DataOutputStream out = null;
+
+ try {
+ long start;
+ if (PROFILE_LOADING_SAVING) {
+ start = SystemClock.elapsedRealtime();
+ }
+
+ final HashMap> maps = mNamedGestures;
+
+ out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream :
+ new BufferedOutputStream(stream, GestureConstants.IO_BUFFER_SIZE));
+ // Write version number
+ out.writeShort(FILE_FORMAT_VERSION);
+ // Write number of entries
+ out.writeInt(maps.size());
+
+ for (Map.Entry> entry : maps.entrySet()) {
+ final String key = entry.getKey();
+ final ArrayList examples = entry.getValue();
+ final int count = examples.size();
+
+ // Write entry name
+ out.writeUTF(key);
+ // Write number of examples for this entry
+ out.writeInt(count);
+
+ for (int i = 0; i < count; i++) {
+ examples.get(i).serialize(out);
+ }
+ }
+
+ out.flush();
+
+ if (PROFILE_LOADING_SAVING) {
+ long end = SystemClock.elapsedRealtime();
+ Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms");
+ }
+
+ mChanged = false;
+ } finally {
+ if (closeStream) GestureUtilities.closeStream(out);
+ }
+ }
+
+ /**
+ * Load the gesture library
+ */
+ public void load(InputStream stream) throws IOException {
+ load(stream, false);
+ }
+
+ public void load(InputStream stream, boolean closeStream) throws IOException {
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream((stream instanceof BufferedInputStream) ? stream :
+ new BufferedInputStream(stream, GestureConstants.IO_BUFFER_SIZE));
+
+ long start;
+ if (PROFILE_LOADING_SAVING) {
+ start = SystemClock.elapsedRealtime();
+ }
+
+ // Read file format version number
+ final short versionNumber = in.readShort();
+ switch (versionNumber) {
+ case 1:
+ readFormatV1(in);
+ break;
+ }
+
+ if (PROFILE_LOADING_SAVING) {
+ long end = SystemClock.elapsedRealtime();
+ Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms");
+ }
+ } finally {
+ if (closeStream) GestureUtilities.closeStream(in);
+ }
+ }
+
+ private void readFormatV1(DataInputStream in) throws IOException {
+ final Learner classifier = mClassifier;
+ final HashMap> namedGestures = mNamedGestures;
+ namedGestures.clear();
+
+ // Number of entries in the library
+ final int entriesCount = in.readInt();
+
+ for (int i = 0; i < entriesCount; i++) {
+ // Entry name
+ final String name = in.readUTF();
+ // Number of gestures
+ final int gestureCount = in.readInt();
+
+ final ArrayList gestures = new ArrayList(gestureCount);
+ for (int j = 0; j < gestureCount; j++) {
+ final Gesture gesture = Gesture.deserialize(in);
+ gestures.add(gesture);
+ classifier.addInstance(
+ Instance.createInstance(mSequenceType, mOrientationStyle, gesture, name));
+ }
+
+ namedGestures.put(name, gestures);
+ }
+ }
+
+ Learner getLearner() {
+ return mClassifier;
+ }
+}
diff --git a/core/java/android/gesture/GestureStroke.java b/core/java/android/gesture/GestureStroke.java
new file mode 100644
index 0000000000000000000000000000000000000000..598eb8534ffc56b3e7a1a1649b553d057328c61a
--- /dev/null
+++ b/core/java/android/gesture/GestureStroke.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2009 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.gesture;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+
+import java.io.IOException;
+import java.io.DataOutputStream;
+import java.io.DataInputStream;
+import java.util.ArrayList;
+
+/**
+ * A gesture stroke started on a touch down and ended on a touch up.
+ */
+public class GestureStroke {
+ static final float TOUCH_TOLERANCE = 8;
+
+ public final RectF boundingBox;
+
+ public final float length;
+ public final float[] points;
+
+ private final long[] timestamps;
+ private Path mCachedPath;
+
+ /**
+ * Construct a gesture stroke from a list of gesture points
+ *
+ * @param points
+ */
+ public GestureStroke(ArrayList points) {
+ final int count = points.size();
+ final float[] tmpPoints = new float[count * 2];
+ final long[] times = new long[count];
+
+ RectF bx = null;
+ float len = 0;
+ int index = 0;
+
+ for (int i = 0; i < count; i++) {
+ final GesturePoint p = points.get(i);
+ tmpPoints[i * 2] = p.x;
+ tmpPoints[i * 2 + 1] = p.y;
+ times[index] = p.timestamp;
+
+ if (bx == null) {
+ bx = new RectF();
+ bx.top = p.y;
+ bx.left = p.x;
+ bx.right = p.x;
+ bx.bottom = p.y;
+ len = 0;
+ } else {
+ len += Math.sqrt(Math.pow(p.x - tmpPoints[(i - 1) * 2], 2)
+ + Math.pow(p.y - tmpPoints[(i -1 ) * 2 + 1], 2));
+ bx.union(p.x, p.y);
+ }
+ index++;
+ }
+
+ timestamps = times;
+ this.points = tmpPoints;
+ boundingBox = bx;
+ length = len;
+ }
+
+ /**
+ * Draw the gesture with a given canvas and paint
+ *
+ * @param canvas
+ */
+ void draw(Canvas canvas, Paint paint) {
+ if (mCachedPath == null) {
+ makePath();
+ }
+
+ canvas.drawPath(mCachedPath, paint);
+ }
+
+ public Path getPath() {
+ if (mCachedPath == null) {
+ makePath();
+ }
+
+ return mCachedPath;
+ }
+
+ private void makePath() {
+ final float[] localPoints = points;
+ final int count = localPoints.length;
+
+ Path path = null;
+
+ float mX = 0;
+ float mY = 0;
+
+ for (int i = 0; i < count; i += 2) {
+ float x = localPoints[i];
+ float y = localPoints[i + 1];
+ if (path == null) {
+ path = new Path();
+ path.moveTo(x, y);
+ mX = x;
+ mY = y;
+ } else {
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
+ path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
+ mX = x;
+ mY = y;
+ }
+ }
+ }
+
+ mCachedPath = path;
+ }
+
+ /**
+ * Convert the stroke to a Path based on the number of points
+ *
+ * @param width the width of the bounding box of the target path
+ * @param height the height of the bounding box of the target path
+ * @param numSample the number of points needed
+ *
+ * @return the path
+ */
+ public Path toPath(float width, float height, int numSample) {
+ final float[] pts = GestureUtilities.temporalSampling(this, numSample);
+ final RectF rect = boundingBox;
+
+ GestureUtilities.translate(pts, -rect.left, -rect.top);
+
+ float sx = width / rect.width();
+ float sy = height / rect.height();
+ float scale = sx > sy ? sy : sx;
+ GestureUtilities.scale(pts, scale, scale);
+
+ float mX = 0;
+ float mY = 0;
+
+ Path path = null;
+
+ final int count = pts.length;
+
+ for (int i = 0; i < count; i += 2) {
+ float x = pts[i];
+ float y = pts[i + 1];
+ if (path == null) {
+ path = new Path();
+ path.moveTo(x, y);
+ mX = x;
+ mY = y;
+ } else {
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
+ path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
+ mX = x;
+ mY = y;
+ }
+ }
+ }
+
+ return path;
+ }
+
+ void serialize(DataOutputStream out) throws IOException {
+ final float[] pts = points;
+ final long[] times = timestamps;
+ final int count = points.length;
+
+ // Write number of points
+ out.writeInt(count / 2);
+
+ for (int i = 0; i < count; i += 2) {
+ // Write X
+ out.writeFloat(pts[i]);
+ // Write Y
+ out.writeFloat(pts[i + 1]);
+ // Write timestamp
+ out.writeLong(times[i / 2]);
+ }
+ }
+
+ static GestureStroke deserialize(DataInputStream in) throws IOException {
+ // Number of points
+ final int count = in.readInt();
+
+ final ArrayList points = new ArrayList(count);
+ for (int i = 0; i < count; i++) {
+ points.add(GesturePoint.deserialize(in));
+ }
+
+ return new GestureStroke(points);
+ }
+
+ /**
+ * Invalidate the cached path that is used to render the stroke
+ */
+ public void clearPath() {
+ if (mCachedPath != null) mCachedPath.rewind();
+ }
+
+ /**
+ * Compute an oriented bounding box of the stroke
+ * @return OrientedBoundingBox
+ */
+ public OrientedBoundingBox computeOrientedBoundingBox() {
+ return GestureUtilities.computeOrientedBoundingBox(points);
+ }
+}
diff --git a/core/java/android/gesture/GestureUtilities.java b/core/java/android/gesture/GestureUtilities.java
new file mode 100755
index 0000000000000000000000000000000000000000..40d70295fc9b220567cb38bdc535d33c45f9043e
--- /dev/null
+++ b/core/java/android/gesture/GestureUtilities.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2008-2009 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.gesture;
+
+import android.graphics.RectF;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.io.Closeable;
+import java.io.IOException;
+
+import static android.gesture.GestureConstants.*;
+
+final class GestureUtilities {
+ private static final int TEMPORAL_SAMPLING_RATE = 16;
+
+ private GestureUtilities() {
+ }
+
+ /**
+ * Closes the specified stream.
+ *
+ * @param stream The stream to close.
+ */
+ static void closeStream(Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Could not close stream", e);
+ }
+ }
+ }
+
+ static float[] spatialSampling(Gesture gesture, int sampleMatrixDimension) {
+ final float targetPatchSize = sampleMatrixDimension - 1; // edge inclusive
+ float[] sample = new float[sampleMatrixDimension * sampleMatrixDimension];
+ Arrays.fill(sample, 0);
+
+ RectF rect = gesture.getBoundingBox();
+ float sx = targetPatchSize / rect.width();
+ float sy = targetPatchSize / rect.height();
+ float scale = sx < sy ? sx : sy;
+
+ float preDx = -rect.centerX();
+ float preDy = -rect.centerY();
+ float postDx = targetPatchSize / 2;
+ float postDy = targetPatchSize / 2;
+
+ final ArrayList strokes = gesture.getStrokes();
+ final int count = strokes.size();
+
+ int size;
+ float xpos;
+ float ypos;
+
+ for (int index = 0; index < count; index++) {
+ final GestureStroke stroke = strokes.get(index);
+ float[] strokepoints = stroke.points;
+ size = strokepoints.length;
+
+ final float[] pts = new float[size];
+
+ for (int i = 0; i < size; i += 2) {
+ pts[i] = (strokepoints[i] + preDx) * scale + postDx;
+ pts[i + 1] = (strokepoints[i + 1] + preDy) * scale + postDy;
+ }
+
+ float segmentEndX = -1;
+ float segmentEndY = -1;
+
+ for (int i = 0; i < size; i += 2) {
+
+ float segmentStartX = pts[i] < 0 ? 0 : pts[i];
+ float segmentStartY = pts[i + 1] < 0 ? 0 : pts[i + 1];
+
+ if (segmentStartX > targetPatchSize) {
+ segmentStartX = targetPatchSize;
+ }
+
+ if (segmentStartY > targetPatchSize) {
+ segmentStartY = targetPatchSize;
+ }
+
+ plot(segmentStartX, segmentStartY, sample, sampleMatrixDimension);
+
+ if (segmentEndX != -1) {
+ // evaluate horizontally
+ if (segmentEndX > segmentStartX) {
+ xpos = (float) Math.ceil(segmentStartX);
+ float slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX);
+ while (xpos < segmentEndX) {
+ ypos = slope * (xpos - segmentStartX) + segmentStartY;
+ plot(xpos, ypos, sample, sampleMatrixDimension);
+ xpos++;
+ }
+ } else if (segmentEndX < segmentStartX){
+ xpos = (float) Math.ceil(segmentEndX);
+ float slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX);
+ while (xpos < segmentStartX) {
+ ypos = slope * (xpos - segmentStartX) + segmentStartY;
+ plot(xpos, ypos, sample, sampleMatrixDimension);
+ xpos++;
+ }
+ }
+
+ // evaluating vertically
+ if (segmentEndY > segmentStartY) {
+ ypos = (float) Math.ceil(segmentStartY);
+ float invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY);
+ while (ypos < segmentEndY) {
+ xpos = invertSlope * (ypos - segmentStartY) + segmentStartX;
+ plot(xpos, ypos, sample, sampleMatrixDimension);
+ ypos++;
+ }
+ } else if (segmentEndY < segmentStartY) {
+ ypos = (float) Math.ceil(segmentEndY);
+ float invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY);
+ while (ypos < segmentStartY) {
+ xpos = invertSlope * (ypos - segmentStartY) + segmentStartX;
+ plot(xpos, ypos, sample, sampleMatrixDimension);
+ ypos++;
+ }
+ }
+ }
+
+ segmentEndX = segmentStartX;
+ segmentEndY = segmentStartY;
+ }
+ }
+
+
+ return sample;
+ }
+
+ private static void plot(float x, float y, float[] sample, int sampleSize) {
+ x = x < 0 ? 0 : x;
+ y = y < 0 ? 0 : y;
+ int xFloor = (int) Math.floor(x);
+ int xCeiling = (int) Math.ceil(x);
+ int yFloor = (int) Math.floor(y);
+ int yCeiling = (int) Math.ceil(y);
+
+ // if it's an integer
+ if (x == xFloor && y == yFloor) {
+ int index = yCeiling * sampleSize + xCeiling;
+ if (sample[index] < 1){
+ sample[index] = 1;
+ }
+ } else {
+ double topLeft = Math.sqrt(Math.pow(xFloor - x, 2) + Math.pow(yFloor - y, 2));
+ double topRight = Math.sqrt(Math.pow(xCeiling - x, 2) + Math.pow(yFloor - y, 2));
+ double btmLeft = Math.sqrt(Math.pow(xFloor - x, 2) + Math.pow(yCeiling - y, 2));
+ double btmRight = Math.sqrt(Math.pow(xCeiling - x, 2) + Math.pow(yCeiling - y, 2));
+ double sum = topLeft + topRight + btmLeft + btmRight;
+
+ double value = topLeft / sum;
+ int index = yFloor * sampleSize + xFloor;
+ if (value > sample[index]){
+ sample[index] = (float) value;
+ }
+
+ value = topRight / sum;
+ index = yFloor * sampleSize + xCeiling;
+ if (value > sample[index]){
+ sample[index] = (float) value;
+ }
+
+ value = btmLeft / sum;
+ index = yCeiling * sampleSize + xFloor;
+ if (value > sample[index]){
+ sample[index] = (float) value;
+ }
+
+ value = btmRight / sum;
+ index = yCeiling * sampleSize + xCeiling;
+ if (value > sample[index]){
+ sample[index] = (float) value;
+ }
+ }
+ }
+
+ /**
+ * Featurize a stroke into a vector of a given number of elements
+ *
+ * @param stroke
+ * @param sampleSize
+ * @return a float array
+ */
+ static float[] temporalSampling(GestureStroke stroke, int sampleSize) {
+ final float increment = stroke.length / (sampleSize - 1);
+ int vectorLength = sampleSize * 2;
+ float[] vector = new float[vectorLength];
+ float distanceSoFar = 0;
+ float[] pts = stroke.points;
+ float lstPointX = pts[0];
+ float lstPointY = pts[1];
+ int index = 0;
+ float currentPointX = Float.MIN_VALUE;
+ float currentPointY = Float.MIN_VALUE;
+ vector[index] = lstPointX;
+ index++;
+ vector[index] = lstPointY;
+ index++;
+ int i = 0;
+ int count = pts.length / 2;
+ while (i < count) {
+ if (currentPointX == Float.MIN_VALUE) {
+ i++;
+ if (i >= count) {
+ break;
+ }
+ currentPointX = pts[i * 2];
+ currentPointY = pts[i * 2 + 1];
+ }
+ float deltaX = currentPointX - lstPointX;
+ float deltaY = currentPointY - lstPointY;
+ float distance = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ if (distanceSoFar + distance >= increment) {
+ float ratio = (increment - distanceSoFar) / distance;
+ float nx = lstPointX + ratio * deltaX;
+ float ny = lstPointY + ratio * deltaY;
+ vector[index] = nx;
+ index++;
+ vector[index] = ny;
+ index++;
+ lstPointX = nx;
+ lstPointY = ny;
+ distanceSoFar = 0;
+ } else {
+ lstPointX = currentPointX;
+ lstPointY = currentPointY;
+ currentPointX = Float.MIN_VALUE;
+ currentPointY = Float.MIN_VALUE;
+ distanceSoFar += distance;
+ }
+ }
+
+ for (i = index; i < vectorLength; i += 2) {
+ vector[i] = lstPointX;
+ vector[i + 1] = lstPointY;
+ }
+ return vector;
+ }
+
+ /**
+ * Calculate the centroid
+ *
+ * @param points
+ * @return the centroid
+ */
+ static float[] computeCentroid(float[] points) {
+ float centerX = 0;
+ float centerY = 0;
+ int count = points.length;
+ for (int i = 0; i < count; i++) {
+ centerX += points[i];
+ i++;
+ centerY += points[i];
+ }
+ float[] center = new float[2];
+ center[0] = 2 * centerX / count;
+ center[1] = 2 * centerY / count;
+
+ return center;
+ }
+
+ /**
+ * calculate the variance-covariance matrix, treat each point as a sample
+ *
+ * @param points
+ * @return the covariance matrix
+ */
+ private static double[][] computeCoVariance(float[] points) {
+ double[][] array = new double[2][2];
+ array[0][0] = 0;
+ array[0][1] = 0;
+ array[1][0] = 0;
+ array[1][1] = 0;
+ int count = points.length;
+ for (int i = 0; i < count; i++) {
+ float x = points[i];
+ i++;
+ float y = points[i];
+ array[0][0] += x * x;
+ array[0][1] += x * y;
+ array[1][0] = array[0][1];
+ array[1][1] += y * y;
+ }
+ array[0][0] /= (count / 2);
+ array[0][1] /= (count / 2);
+ array[1][0] /= (count / 2);
+ array[1][1] /= (count / 2);
+
+ return array;
+ }
+
+ static float computeTotalLength(float[] points) {
+ float sum = 0;
+ int count = points.length - 4;
+ for (int i = 0; i < count; i += 2) {
+ float dx = points[i + 2] - points[i];
+ float dy = points[i + 3] - points[i + 1];
+ sum += Math.sqrt(dx * dx + dy * dy);
+ }
+ return sum;
+ }
+
+ static double computeStraightness(float[] points) {
+ float totalLen = computeTotalLength(points);
+ float dx = points[2] - points[0];
+ float dy = points[3] - points[1];
+ return Math.sqrt(dx * dx + dy * dy) / totalLen;
+ }
+
+ static double computeStraightness(float[] points, float totalLen) {
+ float dx = points[2] - points[0];
+ float dy = points[3] - points[1];
+ return Math.sqrt(dx * dx + dy * dy) / totalLen;
+ }
+
+ /**
+ * Calculate the squared Euclidean distance between two vectors
+ *
+ * @param vector1
+ * @param vector2
+ * @return the distance
+ */
+ static double squaredEuclideanDistance(float[] vector1, float[] vector2) {
+ double squaredDistance = 0;
+ int size = vector1.length;
+ for (int i = 0; i < size; i++) {
+ float difference = vector1[i] - vector2[i];
+ squaredDistance += difference * difference;
+ }
+ return squaredDistance / size;
+ }
+
+ /**
+ * Calculate the cosine distance between two instances
+ *
+ * @param vector1
+ * @param vector2
+ * @return the distance between 0 and Math.PI
+ */
+ static double cosineDistance(float[] vector1, float[] vector2) {
+ float sum = 0;
+ int len = vector1.length;
+ for (int i = 0; i < len; i++) {
+ sum += vector1[i] * vector2[i];
+ }
+ return Math.acos(sum);
+ }
+
+ static OrientedBoundingBox computeOrientedBoundingBox(ArrayList pts) {
+ GestureStroke stroke = new GestureStroke(pts);
+ float[] points = temporalSampling(stroke, TEMPORAL_SAMPLING_RATE);
+ return computeOrientedBoundingBox(points);
+ }
+
+ static OrientedBoundingBox computeOrientedBoundingBox(float[] points) {
+ float[] meanVector = computeCentroid(points);
+ return computeOrientedBoundingBox(points, meanVector);
+ }
+
+ static OrientedBoundingBox computeOrientedBoundingBox(float[] points, float[] centroid) {
+ translate(points, -centroid[0], -centroid[1]);
+
+ double[][] array = computeCoVariance(points);
+ double[] targetVector = computeOrientation(array);
+
+ float angle;
+ if (targetVector[0] == 0 && targetVector[1] == 0) {
+ angle = (float) -Math.PI/2;
+ } else { // -PI maxx) {
+ maxx = points[i];
+ }
+ i++;
+ if (points[i] < miny) {
+ miny = points[i];
+ }
+ if (points[i] > maxy) {
+ maxy = points[i];
+ }
+ }
+
+ return new OrientedBoundingBox((float) (angle * 180 / Math.PI), centroid[0], centroid[1], maxx - minx, maxy - miny);
+ }
+
+ private static double[] computeOrientation(double[][] covarianceMatrix) {
+ double[] targetVector = new double[2];
+ if (covarianceMatrix[0][1] == 0 || covarianceMatrix[1][0] == 0) {
+ targetVector[0] = 1;
+ targetVector[1] = 0;
+ }
+
+ double a = -covarianceMatrix[0][0] - covarianceMatrix[1][1];
+ double b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1]
+ * covarianceMatrix[1][0];
+ double value = a / 2;
+ double rightside = Math.sqrt(Math.pow(value, 2) - b);
+ double lambda1 = -value + rightside;
+ double lambda2 = -value - rightside;
+ if (lambda1 == lambda2) {
+ targetVector[0] = 0;
+ targetVector[1] = 0;
+ } else {
+ double lambda = lambda1 > lambda2 ? lambda1 : lambda2;
+ targetVector[0] = 1;
+ targetVector[1] = (lambda - covarianceMatrix[0][0]) / covarianceMatrix[0][1];
+ }
+ return targetVector;
+ }
+
+
+ static float[] rotate(float[] points, double angle) {
+ double cos = Math.cos(angle);
+ double sin = Math.sin(angle);
+ int size = points.length;
+ for (int i = 0; i < size; i += 2) {
+ float x = (float) (points[i] * cos - points[i + 1] * sin);
+ float y = (float) (points[i] * sin + points[i + 1] * cos);
+ points[i] = x;
+ points[i + 1] = y;
+ }
+ return points;
+ }
+
+ static float[] translate(float[] points, float dx, float dy) {
+ int size = points.length;
+ for (int i = 0; i < size; i += 2) {
+ points[i] += dx;
+ points[i + 1] += dy;
+ }
+ return points;
+ }
+
+ static float[] scale(float[] points, float sx, float sy) {
+ int size = points.length;
+ for (int i = 0; i < size; i += 2) {
+ points[i] *= sx;
+ points[i + 1] *= sy;
+ }
+ return points;
+ }
+}
diff --git a/core/java/android/gesture/Instance.java b/core/java/android/gesture/Instance.java
new file mode 100755
index 0000000000000000000000000000000000000000..ef208acf4ec793167bc43dde35fa7973f57fc47f
--- /dev/null
+++ b/core/java/android/gesture/Instance.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2008-2009 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.gesture;
+
+
+/**
+ * An instance represents a sample if the label is available or a query if the
+ * label is null.
+ */
+class Instance {
+ private static final int SEQUENCE_SAMPLE_SIZE = 16;
+
+ private static final int PATCH_SAMPLE_SIZE = 16;
+
+ private final static float[] ORIENTATIONS = {
+ 0, (float) (Math.PI / 4), (float) (Math.PI / 2), (float) (Math.PI * 3 / 4),
+ (float) Math.PI, -0, (float) (-Math.PI / 4), (float) (-Math.PI / 2),
+ (float) (-Math.PI * 3 / 4), (float) -Math.PI
+ };
+
+ // the feature vector
+ final float[] vector;
+
+ // the label can be null
+ final String label;
+
+ // the id of the instance
+ final long id;
+
+ private Instance(long id, float[] sample, String sampleName) {
+ this.id = id;
+ vector = sample;
+ label = sampleName;
+ }
+
+ private void normalize() {
+ float[] sample = vector;
+ float sum = 0;
+
+ int size = sample.length;
+ for (int i = 0; i < size; i++) {
+ sum += sample[i] * sample[i];
+ }
+
+ float magnitude = (float)Math.sqrt(sum);
+ for (int i = 0; i < size; i++) {
+ sample[i] /= magnitude;
+ }
+ }
+
+ /**
+ * create a learning instance for a single stroke gesture
+ *
+ * @param gesture
+ * @param label
+ * @return the instance
+ */
+ static Instance createInstance(int sequenceType, int orientationType, Gesture gesture, String label) {
+ float[] pts;
+ Instance instance;
+ if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {
+ pts = temporalSampler(orientationType, gesture);
+ instance = new Instance(gesture.getID(), pts, label);
+ instance.normalize();
+ } else {
+ pts = spatialSampler(gesture);
+ instance = new Instance(gesture.getID(), pts, label);
+ }
+ return instance;
+ }
+
+ private static float[] spatialSampler(Gesture gesture) {
+ return GestureUtilities.spatialSampling(gesture, PATCH_SAMPLE_SIZE);
+ }
+
+ private static float[] temporalSampler(int orientationType, Gesture gesture) {
+ float[] pts = GestureUtilities.temporalSampling(gesture.getStrokes().get(0),
+ SEQUENCE_SAMPLE_SIZE);
+ float[] center = GestureUtilities.computeCentroid(pts);
+ float orientation = (float)Math.atan2(pts[1] - center[1], pts[0] - center[0]);
+
+ float adjustment = -orientation;
+ if (orientationType == GestureStore.ORIENTATION_SENSITIVE) {
+ int count = ORIENTATIONS.length;
+ for (int i = 0; i < count; i++) {
+ float delta = ORIENTATIONS[i] - orientation;
+ if (Math.abs(delta) < Math.abs(adjustment)) {
+ adjustment = delta;
+ }
+ }
+ }
+
+ GestureUtilities.translate(pts, -center[0], -center[1]);
+ GestureUtilities.rotate(pts, adjustment);
+
+ return pts;
+ }
+
+}
diff --git a/core/java/android/gesture/InstanceLearner.java b/core/java/android/gesture/InstanceLearner.java
new file mode 100644
index 0000000000000000000000000000000000000000..b93b76fa9a87692cde56f292b2b9958347f2b449
--- /dev/null
+++ b/core/java/android/gesture/InstanceLearner.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008-2009 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.gesture;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.TreeMap;
+
+/**
+ * An implementation of an instance-based learner
+ */
+
+class InstanceLearner extends Learner {
+ private static final Comparator