summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt25
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java19
-rw-r--r--core/java/android/app/INotificationManager.aidl3
-rw-r--r--core/java/android/app/Notification.java95
-rw-r--r--core/java/android/hardware/Camera.java76
-rw-r--r--core/java/android/net/nsd/INsdManager.aidl1
-rw-r--r--core/java/android/net/nsd/NsdManager.java53
-rw-r--r--core/java/android/provider/MediaStore.java2
-rw-r--r--core/java/android/provider/Settings.java6
-rw-r--r--core/java/android/view/KeyCharacterMap.java221
-rw-r--r--core/java/android/view/View.java2
-rw-r--r--core/java/android/widget/AbsSeekBar.java2
-rw-r--r--core/java/android/widget/ExpandableListConnector.java28
-rw-r--r--core/java/android/widget/ExpandableListPosition.java4
-rw-r--r--core/java/android/widget/ExpandableListView.java17
-rw-r--r--core/java/com/android/internal/util/AsyncChannel.java2
-rw-r--r--core/jni/android_hardware_Camera.cpp18
-rw-r--r--core/res/res/layout/notification_template_base.xml2
-rw-r--r--docs/html/resources/resources_toc.cs29
-rw-r--r--docs/html/training/basics/location/currentlocation.jd (renamed from docs/html/training/location/currentlocation.jd)0
-rw-r--r--docs/html/training/basics/location/geocoding.jd (renamed from docs/html/training/location/geocoding.jd)0
-rw-r--r--docs/html/training/basics/location/index.jd (renamed from docs/html/training/location/index.jd)0
-rw-r--r--docs/html/training/basics/location/locationmanager.jd (renamed from docs/html/training/location/locationmanager.jd)0
-rw-r--r--docs/html/training/cloudsync/aesync.jd432
-rw-r--r--docs/html/training/cloudsync/backupapi.jd193
-rw-r--r--docs/html/training/cloudsync/index.jd34
-rw-r--r--media/java/android/media/AudioRecord.java1
-rw-r--r--media/java/android/media/audiofx/AcousticEchoCanceler.java4
-rw-r--r--media/java/android/media/audiofx/AutomaticGainControl.java4
-rw-r--r--media/java/android/media/audiofx/NoiseSuppressor.java2
-rwxr-xr-xmedia/java/android/media/audiofx/Visualizer.java56
-rw-r--r--media/jni/audioeffect/android_media_Visualizer.cpp23
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVisualizerTest.java116
-rw-r--r--packages/SystemUI/res/menu/notification_popup_menu.xml22
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java6
-rwxr-xr-xservices/java/com/android/server/NotificationManagerService.java231
-rw-r--r--services/java/com/android/server/NsdService.java468
-rwxr-xr-x[-rw-r--r--]services/java/com/android/server/wm/WindowManagerService.java26
-rw-r--r--telephony/java/com/android/internal/telephony/DataConnection.java2
-rw-r--r--telephony/java/com/android/internal/telephony/DataConnectionAc.java2
-rw-r--r--tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java12
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pService.java31
47 files changed, 1851 insertions, 586 deletions
diff --git a/api/current.txt b/api/current.txt
index d8387c1..d95868f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1993,6 +1993,7 @@ package android.accessibilityservice {
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
+ method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
@@ -3767,7 +3768,6 @@ package android.app {
method public android.app.Notification.Builder setDefaults(int);
method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent);
method public android.app.Notification.Builder setFullScreenIntent(android.app.PendingIntent, boolean);
- method public android.app.Notification.Builder setIntruderActionsShowText(boolean);
method public android.app.Notification.Builder setLargeIcon(android.graphics.Bitmap);
method public android.app.Notification.Builder setLights(int, int, int);
method public android.app.Notification.Builder setNumber(int);
@@ -3783,7 +3783,6 @@ package android.app {
method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
method public android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
method public android.app.Notification.Builder setUsesChronometer(boolean);
- method public android.app.Notification.Builder setUsesIntruderAlert(boolean);
method public android.app.Notification.Builder setVibrate(long[]);
method public android.app.Notification.Builder setWhen(long);
}
@@ -10725,6 +10724,7 @@ package android.media {
public class AudioRecord {
ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
method public int getAudioFormat();
+ method public int getAudioSessionId();
method public int getAudioSource();
method public int getChannelConfiguration();
method public int getChannelCount();
@@ -11516,6 +11516,11 @@ package android.media {
package android.media.audiofx {
+ public class AcousticEchoCanceler extends android.media.audiofx.AudioEffect {
+ method public static android.media.audiofx.AcousticEchoCanceler create(int);
+ method public static boolean isAvailable();
+ }
+
public class AudioEffect {
method public android.media.audiofx.AudioEffect.Descriptor getDescriptor() throws java.lang.IllegalStateException;
method public boolean getEnabled() throws java.lang.IllegalStateException;
@@ -11566,6 +11571,11 @@ package android.media.audiofx {
method public abstract void onEnableStatusChange(android.media.audiofx.AudioEffect, boolean);
}
+ public class AutomaticGainControl extends android.media.audiofx.AudioEffect {
+ method public static android.media.audiofx.AutomaticGainControl create(int);
+ method public static boolean isAvailable();
+ }
+
public class BassBoost extends android.media.audiofx.AudioEffect {
ctor public BassBoost(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
method public android.media.audiofx.BassBoost.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
@@ -11684,6 +11694,11 @@ package android.media.audiofx {
field public short numBands;
}
+ public class NoiseSuppressor extends android.media.audiofx.AudioEffect {
+ method public static android.media.audiofx.NoiseSuppressor create(int);
+ method public static boolean isAvailable();
+ }
+
public class PresetReverb extends android.media.audiofx.AudioEffect {
ctor public PresetReverb(int, int) throws java.lang.IllegalArgumentException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
method public short getPreset() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
@@ -12470,10 +12485,14 @@ package android.net.nsd {
method public void resolveService(android.net.nsd.NsdManager.Channel, java.lang.String, java.lang.String, android.net.nsd.NsdManager.DnsSdResolveListener);
method public void stopServiceDiscovery(android.net.nsd.NsdManager.Channel, android.net.nsd.NsdManager.ActionListener);
method public void unregisterService(android.net.nsd.NsdManager.Channel, int, android.net.nsd.NsdManager.ActionListener);
+ field public static final java.lang.String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
field public static final int ALREADY_ACTIVE = 3; // 0x3
field public static final int BUSY = 2; // 0x2
field public static final int ERROR = 0; // 0x0
+ field public static final java.lang.String EXTRA_NSD_STATE = "nsd_state";
field public static final int MAX_REGS_REACHED = 4; // 0x4
+ field public static final int NSD_STATE_DISABLED = 1; // 0x1
+ field public static final int NSD_STATE_ENABLED = 2; // 0x2
field public static final int UNSUPPORTED = 1; // 0x1
}
@@ -17844,9 +17863,11 @@ package android.provider {
field public static final java.lang.String DATE_ADDED = "date_added";
field public static final java.lang.String DATE_MODIFIED = "date_modified";
field public static final java.lang.String DISPLAY_NAME = "_display_name";
+ field public static final java.lang.String HEIGHT = "height";
field public static final java.lang.String MIME_TYPE = "mime_type";
field public static final java.lang.String SIZE = "_size";
field public static final java.lang.String TITLE = "title";
+ field public static final java.lang.String WIDTH = "width";
}
public static final class MediaStore.Video {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index eed8aa6..4e340c0 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -426,13 +426,11 @@ public abstract class AccessibilityService extends Service {
throw new IllegalStateException("AccessibilityService not connected."
+ " Did you receive a call of onServiceConnected()?");
}
- AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByAccessibilityId(connectionId,
- AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
- AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
+ AccessibilityNodeInfo root = getRootInActiveWindow();
if (root == null) {
return;
}
+
AccessibilityNodeInfo current = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
if (current == null) {
current = root;
@@ -480,6 +478,19 @@ public abstract class AccessibilityService extends Service {
}
/**
+ * Gets the root node in the currently active window if this service
+ * can retrieve window content.
+ *
+ * @return The root node if this service can retrieve window content.
+ */
+ public AccessibilityNodeInfo getRootInActiveWindow() {
+ return AccessibilityInteractionClient.getInstance()
+ .findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
+ AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
+ }
+
+ /**
* Performs a global action. Such an action can be performed
* at any moment regardless of the current application or user
* location in that application. For example going back, going
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 4d5238c..6f95e26 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -34,5 +34,8 @@ interface INotificationManager
void cancelToast(String pkg, ITransientNotification callback);
void enqueueNotificationWithTag(String pkg, String tag, int id, in Notification notification, inout int[] idReceived);
void cancelNotificationWithTag(String pkg, String tag, int id);
+
+ void setNotificationsEnabledForPackage(String pkg, boolean enabled);
+ boolean areNotificationsEnabledForPackage(String pkg);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 22d84f0..b581f99 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -190,12 +190,6 @@ public class Notification implements Parcelable
public RemoteViews contentView;
/**
- * The view that will represent this notification in the pop-up "intruder alert" dialog.
- * @hide
- */
- public RemoteViews intruderView;
-
- /**
* A large-format version of {@link #contentView}, giving the Notification an
* opportunity to show more detail. The system UI may choose to show this
* instead of the normal content view at its discretion.
@@ -590,9 +584,6 @@ public class Notification implements Parcelable
actions = parcel.createTypedArray(Action.CREATOR);
if (parcel.readInt() != 0) {
- intruderView = RemoteViews.CREATOR.createFromParcel(parcel);
- }
- if (parcel.readInt() != 0) {
bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
}
}
@@ -658,9 +649,6 @@ public class Notification implements Parcelable
for(int i=0; i<this.actions.length; i++) {
that.actions[i] = this.actions[i].clone();
}
- if (this.intruderView != null) {
- that.intruderView = this.intruderView.clone();
- }
if (this.bigContentView != null) {
that.bigContentView = this.bigContentView.clone();
}
@@ -755,13 +743,6 @@ public class Notification implements Parcelable
parcel.writeTypedArray(actions, 0);
- if (intruderView != null) {
- parcel.writeInt(1);
- intruderView.writeToParcel(parcel, 0);
- } else {
- parcel.writeInt(0);
- }
-
if (bigContentView != null) {
parcel.writeInt(1);
bigContentView.writeToParcel(parcel, 0);
@@ -942,8 +923,6 @@ public class Notification implements Parcelable
private Bundle mExtras;
private int mPriority;
private ArrayList<Action> mActions = new ArrayList<Action>(3);
- private boolean mCanHasIntruder;
- private boolean mIntruderActionsShowText;
private boolean mUseChronometer;
/**
@@ -1349,38 +1328,6 @@ public class Notification implements Parcelable
return this;
}
- /**
- * Specify whether this notification should pop up as an
- * "intruder alert" (a small window that shares the screen with the
- * current activity). This sort of notification is (as the name implies)
- * very intrusive, so use it sparingly for notifications that require
- * the user's attention.
- *
- * Notes:
- * <ul>
- * <li>Intruder alerts only show when the screen is on.</li>
- * <li>Intruder alerts take precedence over fullScreenIntents.</li>
- * </ul>
- *
- * @param intrude Whether to pop up an intruder alert (default false).
- */
- public Builder setUsesIntruderAlert(boolean intrude) {
- mCanHasIntruder = intrude;
- return this;
- }
-
- /**
- * Control text on intruder alert action buttons. By default, action
- * buttons in intruders do not show textual labels.
- *
- * @param showActionText Whether to show text labels beneath action
- * icons (default false).
- */
- public Builder setIntruderActionsShowText(boolean showActionText) {
- mIntruderActionsShowText = showActionText;
- return this;
- }
-
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
@@ -1506,45 +1453,6 @@ public class Notification implements Parcelable
return applyStandardTemplateWithActions(R.layout.notification_template_base);
}
- private RemoteViews makeIntruderView(boolean showLabels) {
- RemoteViews intruderView = new RemoteViews(mContext.getPackageName(),
- R.layout.notification_intruder_content);
- if (mLargeIcon != null) {
- intruderView.setImageViewBitmap(R.id.icon, mLargeIcon);
- intruderView.setViewVisibility(R.id.icon, View.VISIBLE);
- } else if (mSmallIcon != 0) {
- intruderView.setImageViewResource(R.id.icon, mSmallIcon);
- intruderView.setViewVisibility(R.id.icon, View.VISIBLE);
- } else {
- intruderView.setViewVisibility(R.id.icon, View.GONE);
- }
- if (mContentTitle != null) {
- intruderView.setTextViewText(R.id.title, mContentTitle);
- }
- if (mContentText != null) {
- intruderView.setTextViewText(R.id.text, mContentText);
- }
- if (mActions.size() > 0) {
- intruderView.setViewVisibility(R.id.actions, View.VISIBLE);
- int N = mActions.size();
- if (N>3) N=3;
- final int[] BUTTONS = { R.id.action0, R.id.action1, R.id.action2 };
- for (int i=0; i<N; i++) {
- final Action action = mActions.get(i);
- final int buttonId = BUTTONS[i];
-
- intruderView.setViewVisibility(buttonId, View.VISIBLE);
- intruderView.setTextViewText(buttonId, showLabels ? action.title : null);
- intruderView.setTextViewCompoundDrawables(buttonId, 0, action.icon, 0, 0);
- intruderView.setContentDescription(buttonId, action.title);
- intruderView.setOnClickPendingIntent(buttonId, action.actionIntent);
- }
- } else {
- intruderView.setViewVisibility(R.id.actions, View.GONE);
- }
- return intruderView;
- }
-
private RemoteViews generateActionButton(Action action) {
RemoteViews button = new RemoteViews(mContext.getPackageName(), R.layout.notification_action);
button.setTextViewCompoundDrawables(R.id.action0, action.icon, 0, 0, 0);
@@ -1579,9 +1487,6 @@ public class Notification implements Parcelable
n.ledOffMS = mLedOffMs;
n.defaults = mDefaults;
n.flags = mFlags;
- if (mCanHasIntruder) {
- n.intruderView = makeIntruderView(mIntruderActionsShowText);
- }
n.bigContentView = makeBigContentView();
if (mLedOnMs != 0 && mLedOffMs != 0) {
n.flags |= FLAG_SHOW_LIGHTS;
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 640b47b..4fb710e 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -234,48 +234,6 @@ public class Camera {
};
/**
- * Creates a new Camera object to access a particular hardware camera.
- *
- * <p>When <code>force</code> is set to false, this will throw an exception
- * if the same camera is already opened by other clients. If true, the other
- * client will be disconnected from the camera they opened. If the device
- * can only support one camera running at a time, all camera-using clients
- * will be disconnected from their cameras.
- *
- * <p>A camera being held by an application can be taken away by other
- * applications at any time. Before the camera is taken, applications will
- * get {@link #CAMERA_ERROR_RELEASED} and have some time to clean up. Apps
- * receiving this callback must immediately stop video recording and then
- * call {@link #release()} on their camera object. Otherwise, it will be
- * released by the frameworks in a short time. After receiving
- * CAMERA_ERROR_RELEASED, apps should not call any method except <code>
- * release</code> and {@link #isReleased()}. After a camera is taken away,
- * all methods will throw exceptions except <code>isReleased</code> and
- * <code>release</code>. Apps can use <code>isReleased</code> to see if the
- * camera has been taken away. If the camera is taken away, the apps can
- * silently finish themselves or show a dialog.
- *
- * <p>Applications with android.permission.KEEP_CAMERA can request to keep
- * the camera. That is, the camera will not be taken by other applications
- * while it is opened. The permission can only be obtained by trusted
- * platform applications, such as those implementing lock screen security
- * features.
- *
- * @param cameraId the hardware camera to access, between 0 and
- * {@link #getNumberOfCameras()}-1.
- * @param force true to take the ownership from the existing client if the
- * camera has been opened by other clients.
- * @param keep true if the applications do not want other apps to take the
- * camera. Only the apps with android.permission.KEEP_CAMERA can keep
- * the camera.
- * @return a new Camera object, connected, locked and ready for use.
- * @hide
- */
- public static Camera open(int cameraId, boolean force, boolean keep) {
- return new Camera(cameraId, force, keep);
- }
-
- /**
* Creates a new Camera object to access a particular hardware camera. If
* the same camera is opened by other applications, this will throw a
* RuntimeException.
@@ -305,7 +263,7 @@ public class Camera {
* @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
*/
public static Camera open(int cameraId) {
- return new Camera(cameraId, false, false);
+ return new Camera(cameraId);
}
/**
@@ -320,13 +278,13 @@ public class Camera {
for (int i = 0; i < numberOfCameras; i++) {
getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
- return new Camera(i, false, false);
+ return new Camera(i);
}
}
return null;
}
- Camera(int cameraId, boolean force, boolean keep) {
+ Camera(int cameraId) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
@@ -343,7 +301,7 @@ public class Camera {
mEventHandler = null;
}
- native_setup(new WeakReference<Camera>(this), cameraId, force, keep);
+ native_setup(new WeakReference<Camera>(this), cameraId);
}
/**
@@ -356,8 +314,7 @@ public class Camera {
release();
}
- private native final void native_setup(Object camera_this, int cameraId,
- boolean force, boolean keep);
+ private native final void native_setup(Object camera_this, int cameraId);
private native final void native_release();
@@ -372,18 +329,6 @@ public class Camera {
}
/**
- * Whether the camera is released. When any camera method throws an
- * exception, applications can use this to check whether the camera has been
- * taken by other clients. If true, it means other clients have taken the
- * camera. The applications can silently finish themselves or show a dialog.
- *
- * @return whether the camera is released.
- * @see #open(int, boolean, boolean)
- * @hide
- */
- public native final boolean isReleased();
-
- /**
* Unlocks the camera to allow another process to access it.
* Normally, the camera is locked to the process with an active Camera
* object until {@link #release()} is called. To allow rapid handoff
@@ -1377,17 +1322,6 @@ public class Camera {
public static final int CAMERA_ERROR_UNKNOWN = 1;
/**
- * Camera was released because another client has opened the camera. The
- * application should call {@link #release()} after getting this. The apps
- * should not call any method except <code>release</code> and {@link #isReleased()}
- * after this.
- *
- * @see Camera.ErrorCallback
- * @hide
- */
- public static final int CAMERA_ERROR_RELEASED = 2;
-
- /**
* Media server died. In this case, the application must release the
* Camera object and instantiate a new one.
* @see Camera.ErrorCallback
diff --git a/core/java/android/net/nsd/INsdManager.aidl b/core/java/android/net/nsd/INsdManager.aidl
index 077a675..3361a7b 100644
--- a/core/java/android/net/nsd/INsdManager.aidl
+++ b/core/java/android/net/nsd/INsdManager.aidl
@@ -26,4 +26,5 @@ import android.os.Messenger;
interface INsdManager
{
Messenger getMessenger();
+ void setEnabled(boolean enable);
}
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index dac8d20..ecf5e20 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -16,6 +16,8 @@
package android.net.nsd;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -133,6 +135,40 @@ public class NsdManager {
private static final String TAG = "NsdManager";
INsdManager mService;
+ /**
+ * Broadcast intent action to indicate whether network service discovery is
+ * enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
+ * information as int.
+ *
+ * @see #EXTRA_NSD_STATE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NSD_STATE_CHANGED =
+ "android.net.nsd.STATE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether network service discovery is enabled
+ * or disabled. Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}.
+ *
+ * @see #NSD_STATE_DISABLED
+ * @see #NSD_STATE_ENABLED
+ */
+ public static final String EXTRA_NSD_STATE = "nsd_state";
+
+ /**
+ * Network service discovery is disabled
+ *
+ * @see #NSD_STATE_CHANGED_ACTION
+ */
+ public static final int NSD_STATE_DISABLED = 1;
+
+ /**
+ * Network service discovery is enabled
+ *
+ * @see #NSD_STATE_CHANGED_ACTION
+ */
+ public static final int NSD_STATE_ENABLED = 2;
+
private static final int BASE = Protocol.BASE_NSD_MANAGER;
/** @hide */
@@ -188,6 +224,12 @@ public class NsdManager {
/** @hide */
public static final int STOP_RESOLVE_SUCCEEDED = BASE + 23;
+ /** @hide */
+ public static final int ENABLE = BASE + 24;
+ /** @hide */
+ public static final int DISABLE = BASE + 25;
+
+
/**
* Create a new Nsd instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -312,8 +354,8 @@ public class NsdManager {
private DnsSdResolveListener mDnsSdResolveListener;
private ActionListener mDnsSdStopResolveListener;
- AsyncChannel mAsyncChannel;
- ServiceHandler mHandler;
+ private AsyncChannel mAsyncChannel;
+ private ServiceHandler mHandler;
class ServiceHandler extends Handler {
ServiceHandler(Looper looper) {
super(looper);
@@ -594,6 +636,13 @@ public class NsdManager {
c.mAsyncChannel.sendMessage(STOP_RESOLVE);
}
+ /** Internal use only @hide */
+ public void setEnabled(boolean enabled) {
+ try {
+ mService.setEnabled(enabled);
+ } catch (RemoteException e) { }
+ }
+
/**
* Get a reference to NetworkService handler. This is used to establish
* an AsyncChannel communication with the service
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 38945c2..79d0144 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -306,13 +306,11 @@ public final class MediaStore {
/**
* The width of the image/video in pixels.
- * @hide
*/
public static final String WIDTH = "width";
/**
* The height of the image/video in pixels.
- * @hide
*/
public static final String HEIGHT = "height";
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6dfbb2f..3a5fdd1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3277,6 +3277,12 @@ public final class Settings {
"wifi_mobile_data_transition_wakelock_timeout_ms";
/**
+ * Whether network service discovery is enabled.
+ * @hide
+ */
+ public static final String NSD_ON = "nsd_on";
+
+ /**
* Whether background data usage is allowed by the user. See
* ConnectivityManager for more info.
*/
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index d8d5185..1c61c6c 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -24,6 +24,7 @@ import android.util.SparseIntArray;
import android.hardware.input.InputManager;
import java.lang.Character;
+import java.text.Normalizer;
/**
* Describes the keys provided by a keyboard device and their associated labels.
@@ -149,9 +150,22 @@ public class KeyCharacterMap implements Parcelable {
/* Characters used to display placeholders for dead keys. */
private static final int ACCENT_ACUTE = '\u00B4';
+ private static final int ACCENT_BREVE = '\u02D8';
+ private static final int ACCENT_CARON = '\u02C7';
+ private static final int ACCENT_CEDILLA = '\u00B8';
+ private static final int ACCENT_COMMA_ABOVE = '\u1FBD';
+ private static final int ACCENT_COMMA_ABOVE_RIGHT = '\u02BC';
+ private static final int ACCENT_DOT_ABOVE = '\u02D9';
+ private static final int ACCENT_DOUBLE_ACUTE = '\u02DD';
private static final int ACCENT_GRAVE = '\u02CB';
private static final int ACCENT_CIRCUMFLEX = '\u02C6';
+ private static final int ACCENT_MACRON = '\u00AF';
+ private static final int ACCENT_MACRON_BELOW = '\u02CD';
+ private static final int ACCENT_OGONEK = '\u02DB';
+ private static final int ACCENT_REVERSED_COMMA_ABOVE = '\u02BD';
+ private static final int ACCENT_RING_ABOVE = '\u02DA';
private static final int ACCENT_TILDE = '\u02DC';
+ private static final int ACCENT_TURNED_COMMA_ABOVE = '\u02BB';
private static final int ACCENT_UMLAUT = '\u00A8';
/* Legacy dead key display characters used in previous versions of the API.
@@ -161,136 +175,66 @@ public class KeyCharacterMap implements Parcelable {
private static final int ACCENT_TILDE_LEGACY = '~';
/**
- * Maps Unicode combining diacritical to display-form dead key
- * (display character shifted left 16 bits).
+ * Maps Unicode combining diacritical to display-form dead key.
*/
- private static final SparseIntArray COMBINING = new SparseIntArray();
+ private static final SparseIntArray sCombiningToAccent = new SparseIntArray();
+ private static final SparseIntArray sAccentToCombining = new SparseIntArray();
static {
- COMBINING.put('\u0300', ACCENT_GRAVE);
- COMBINING.put('\u0301', ACCENT_ACUTE);
- COMBINING.put('\u0302', ACCENT_CIRCUMFLEX);
- COMBINING.put('\u0303', ACCENT_TILDE);
- COMBINING.put('\u0308', ACCENT_UMLAUT);
+ addCombining('\u0300', ACCENT_GRAVE);
+ addCombining('\u0301', ACCENT_ACUTE);
+ addCombining('\u0302', ACCENT_CIRCUMFLEX);
+ addCombining('\u0303', ACCENT_TILDE);
+ addCombining('\u0304', ACCENT_MACRON);
+ addCombining('\u0306', ACCENT_BREVE);
+ addCombining('\u0307', ACCENT_DOT_ABOVE);
+ addCombining('\u0308', ACCENT_UMLAUT);
+ //addCombining('\u0309', ACCENT_HOOK_ABOVE);
+ addCombining('\u030A', ACCENT_RING_ABOVE);
+ addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
+ addCombining('\u030C', ACCENT_CARON);
+ //addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE);
+ //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE);
+ //addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
+ //addCombining('\u0310', ACCENT_CANDRABINDU);
+ //addCombining('\u0311', ACCENT_INVERTED_BREVE);
+ addCombining('\u0312', ACCENT_TURNED_COMMA_ABOVE);
+ addCombining('\u0313', ACCENT_COMMA_ABOVE);
+ addCombining('\u0314', ACCENT_REVERSED_COMMA_ABOVE);
+ addCombining('\u0315', ACCENT_COMMA_ABOVE_RIGHT);
+ //addCombining('\u031B', ACCENT_HORN);
+ //addCombining('\u0323', ACCENT_DOT_BELOW);
+ //addCombining('\u0326', ACCENT_COMMA_BELOW);
+ addCombining('\u0327', ACCENT_CEDILLA);
+ addCombining('\u0328', ACCENT_OGONEK);
+ //addCombining('\u0329', ACCENT_VERTICAL_LINE_BELOW);
+ addCombining('\u0331', ACCENT_MACRON_BELOW);
+ //addCombining('\u0342', ACCENT_PERISPOMENI);
+ //addCombining('\u0344', ACCENT_DIALYTIKA_TONOS);
+ //addCombining('\u0345', ACCENT_YPOGEGRAMMENI);
+
+ // One-way mappings to equivalent preferred accents.
+ sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
+ sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
+ sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
+
+ // One-way legacy mappings to preserve compatibility with older applications.
+ sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
+ sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
+ sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
+ }
+
+ private static void addCombining(int combining, int accent) {
+ sCombiningToAccent.append(combining, accent);
+ sAccentToCombining.append(accent, combining);
}
/**
- * Maps combinations of (display-form) dead key and second character
+ * Maps combinations of (display-form) combining key and second character
* to combined output character.
+ * These mappings are derived from the Unicode NFC tables as needed.
*/
- private static final SparseIntArray DEAD = new SparseIntArray();
- static {
- addDeadChar(ACCENT_ACUTE, 'A', '\u00C1');
- addDeadChar(ACCENT_ACUTE, 'C', '\u0106');
- addDeadChar(ACCENT_ACUTE, 'E', '\u00C9');
- addDeadChar(ACCENT_ACUTE, 'G', '\u01F4');
- addDeadChar(ACCENT_ACUTE, 'I', '\u00CD');
- addDeadChar(ACCENT_ACUTE, 'K', '\u1E30');
- addDeadChar(ACCENT_ACUTE, 'L', '\u0139');
- addDeadChar(ACCENT_ACUTE, 'M', '\u1E3E');
- addDeadChar(ACCENT_ACUTE, 'N', '\u0143');
- addDeadChar(ACCENT_ACUTE, 'O', '\u00D3');
- addDeadChar(ACCENT_ACUTE, 'P', '\u1E54');
- addDeadChar(ACCENT_ACUTE, 'R', '\u0154');
- addDeadChar(ACCENT_ACUTE, 'S', '\u015A');
- addDeadChar(ACCENT_ACUTE, 'U', '\u00DA');
- addDeadChar(ACCENT_ACUTE, 'W', '\u1E82');
- addDeadChar(ACCENT_ACUTE, 'Y', '\u00DD');
- addDeadChar(ACCENT_ACUTE, 'Z', '\u0179');
- addDeadChar(ACCENT_ACUTE, 'a', '\u00E1');
- addDeadChar(ACCENT_ACUTE, 'c', '\u0107');
- addDeadChar(ACCENT_ACUTE, 'e', '\u00E9');
- addDeadChar(ACCENT_ACUTE, 'g', '\u01F5');
- addDeadChar(ACCENT_ACUTE, 'i', '\u00ED');
- addDeadChar(ACCENT_ACUTE, 'k', '\u1E31');
- addDeadChar(ACCENT_ACUTE, 'l', '\u013A');
- addDeadChar(ACCENT_ACUTE, 'm', '\u1E3F');
- addDeadChar(ACCENT_ACUTE, 'n', '\u0144');
- addDeadChar(ACCENT_ACUTE, 'o', '\u00F3');
- addDeadChar(ACCENT_ACUTE, 'p', '\u1E55');
- addDeadChar(ACCENT_ACUTE, 'r', '\u0155');
- addDeadChar(ACCENT_ACUTE, 's', '\u015B');
- addDeadChar(ACCENT_ACUTE, 'u', '\u00FA');
- addDeadChar(ACCENT_ACUTE, 'w', '\u1E83');
- addDeadChar(ACCENT_ACUTE, 'y', '\u00FD');
- addDeadChar(ACCENT_ACUTE, 'z', '\u017A');
- addDeadChar(ACCENT_CIRCUMFLEX, 'A', '\u00C2');
- addDeadChar(ACCENT_CIRCUMFLEX, 'C', '\u0108');
- addDeadChar(ACCENT_CIRCUMFLEX, 'E', '\u00CA');
- addDeadChar(ACCENT_CIRCUMFLEX, 'G', '\u011C');
- addDeadChar(ACCENT_CIRCUMFLEX, 'H', '\u0124');
- addDeadChar(ACCENT_CIRCUMFLEX, 'I', '\u00CE');
- addDeadChar(ACCENT_CIRCUMFLEX, 'J', '\u0134');
- addDeadChar(ACCENT_CIRCUMFLEX, 'O', '\u00D4');
- addDeadChar(ACCENT_CIRCUMFLEX, 'S', '\u015C');
- addDeadChar(ACCENT_CIRCUMFLEX, 'U', '\u00DB');
- addDeadChar(ACCENT_CIRCUMFLEX, 'W', '\u0174');
- addDeadChar(ACCENT_CIRCUMFLEX, 'Y', '\u0176');
- addDeadChar(ACCENT_CIRCUMFLEX, 'Z', '\u1E90');
- addDeadChar(ACCENT_CIRCUMFLEX, 'a', '\u00E2');
- addDeadChar(ACCENT_CIRCUMFLEX, 'c', '\u0109');
- addDeadChar(ACCENT_CIRCUMFLEX, 'e', '\u00EA');
- addDeadChar(ACCENT_CIRCUMFLEX, 'g', '\u011D');
- addDeadChar(ACCENT_CIRCUMFLEX, 'h', '\u0125');
- addDeadChar(ACCENT_CIRCUMFLEX, 'i', '\u00EE');
- addDeadChar(ACCENT_CIRCUMFLEX, 'j', '\u0135');
- addDeadChar(ACCENT_CIRCUMFLEX, 'o', '\u00F4');
- addDeadChar(ACCENT_CIRCUMFLEX, 's', '\u015D');
- addDeadChar(ACCENT_CIRCUMFLEX, 'u', '\u00FB');
- addDeadChar(ACCENT_CIRCUMFLEX, 'w', '\u0175');
- addDeadChar(ACCENT_CIRCUMFLEX, 'y', '\u0177');
- addDeadChar(ACCENT_CIRCUMFLEX, 'z', '\u1E91');
- addDeadChar(ACCENT_GRAVE, 'A', '\u00C0');
- addDeadChar(ACCENT_GRAVE, 'E', '\u00C8');
- addDeadChar(ACCENT_GRAVE, 'I', '\u00CC');
- addDeadChar(ACCENT_GRAVE, 'N', '\u01F8');
- addDeadChar(ACCENT_GRAVE, 'O', '\u00D2');
- addDeadChar(ACCENT_GRAVE, 'U', '\u00D9');
- addDeadChar(ACCENT_GRAVE, 'W', '\u1E80');
- addDeadChar(ACCENT_GRAVE, 'Y', '\u1EF2');
- addDeadChar(ACCENT_GRAVE, 'a', '\u00E0');
- addDeadChar(ACCENT_GRAVE, 'e', '\u00E8');
- addDeadChar(ACCENT_GRAVE, 'i', '\u00EC');
- addDeadChar(ACCENT_GRAVE, 'n', '\u01F9');
- addDeadChar(ACCENT_GRAVE, 'o', '\u00F2');
- addDeadChar(ACCENT_GRAVE, 'u', '\u00F9');
- addDeadChar(ACCENT_GRAVE, 'w', '\u1E81');
- addDeadChar(ACCENT_GRAVE, 'y', '\u1EF3');
- addDeadChar(ACCENT_TILDE, 'A', '\u00C3');
- addDeadChar(ACCENT_TILDE, 'E', '\u1EBC');
- addDeadChar(ACCENT_TILDE, 'I', '\u0128');
- addDeadChar(ACCENT_TILDE, 'N', '\u00D1');
- addDeadChar(ACCENT_TILDE, 'O', '\u00D5');
- addDeadChar(ACCENT_TILDE, 'U', '\u0168');
- addDeadChar(ACCENT_TILDE, 'V', '\u1E7C');
- addDeadChar(ACCENT_TILDE, 'Y', '\u1EF8');
- addDeadChar(ACCENT_TILDE, 'a', '\u00E3');
- addDeadChar(ACCENT_TILDE, 'e', '\u1EBD');
- addDeadChar(ACCENT_TILDE, 'i', '\u0129');
- addDeadChar(ACCENT_TILDE, 'n', '\u00F1');
- addDeadChar(ACCENT_TILDE, 'o', '\u00F5');
- addDeadChar(ACCENT_TILDE, 'u', '\u0169');
- addDeadChar(ACCENT_TILDE, 'v', '\u1E7D');
- addDeadChar(ACCENT_TILDE, 'y', '\u1EF9');
- addDeadChar(ACCENT_UMLAUT, 'A', '\u00C4');
- addDeadChar(ACCENT_UMLAUT, 'E', '\u00CB');
- addDeadChar(ACCENT_UMLAUT, 'H', '\u1E26');
- addDeadChar(ACCENT_UMLAUT, 'I', '\u00CF');
- addDeadChar(ACCENT_UMLAUT, 'O', '\u00D6');
- addDeadChar(ACCENT_UMLAUT, 'U', '\u00DC');
- addDeadChar(ACCENT_UMLAUT, 'W', '\u1E84');
- addDeadChar(ACCENT_UMLAUT, 'X', '\u1E8C');
- addDeadChar(ACCENT_UMLAUT, 'Y', '\u0178');
- addDeadChar(ACCENT_UMLAUT, 'a', '\u00E4');
- addDeadChar(ACCENT_UMLAUT, 'e', '\u00EB');
- addDeadChar(ACCENT_UMLAUT, 'h', '\u1E27');
- addDeadChar(ACCENT_UMLAUT, 'i', '\u00EF');
- addDeadChar(ACCENT_UMLAUT, 'o', '\u00F6');
- addDeadChar(ACCENT_UMLAUT, 't', '\u1E97');
- addDeadChar(ACCENT_UMLAUT, 'u', '\u00FC');
- addDeadChar(ACCENT_UMLAUT, 'w', '\u1E85');
- addDeadChar(ACCENT_UMLAUT, 'x', '\u1E8D');
- addDeadChar(ACCENT_UMLAUT, 'y', '\u00FF');
- }
+ private static final SparseIntArray sDeadKeyCache = new SparseIntArray();
+ private static final StringBuilder sDeadKeyBuilder = new StringBuilder();
public static final Parcelable.Creator<KeyCharacterMap> CREATOR =
new Parcelable.Creator<KeyCharacterMap>() {
@@ -387,7 +331,7 @@ public class KeyCharacterMap implements Parcelable {
metaState = KeyEvent.normalizeMetaState(metaState);
char ch = nativeGetCharacter(mPtr, keyCode, metaState);
- int map = COMBINING.get(ch);
+ int map = sCombiningToAccent.get(ch);
if (map != 0) {
return map | COMBINING_ACCENT;
} else {
@@ -503,14 +447,25 @@ public class KeyCharacterMap implements Parcelable {
* @return The combined character, or 0 if the characters cannot be combined.
*/
public static int getDeadChar(int accent, int c) {
- if (accent == ACCENT_CIRCUMFLEX_LEGACY) {
- accent = ACCENT_CIRCUMFLEX;
- } else if (accent == ACCENT_GRAVE_LEGACY) {
- accent = ACCENT_GRAVE;
- } else if (accent == ACCENT_TILDE_LEGACY) {
- accent = ACCENT_TILDE;
+ int combining = sAccentToCombining.get(accent);
+ if (combining == 0) {
+ return 0;
}
- return DEAD.get((accent << 16) | c);
+
+ final int combination = (combining << 16) | c;
+ int combined;
+ synchronized (sDeadKeyCache) {
+ combined = sDeadKeyCache.get(combination, -1);
+ if (combined == -1) {
+ sDeadKeyBuilder.setLength(0);
+ sDeadKeyBuilder.append((char)c);
+ sDeadKeyBuilder.append((char)combining);
+ String result = Normalizer.normalize(sDeadKeyBuilder, Normalizer.Form.NFC);
+ combined = result.length() == 1 ? result.charAt(0) : 0;
+ sDeadKeyCache.put(combination, combined);
+ }
+ }
+ return combined;
}
/**
@@ -723,10 +678,6 @@ public class KeyCharacterMap implements Parcelable {
return 0;
}
- private static void addDeadChar(int accent, int c, char combinedResult) {
- DEAD.put((accent << 16) | c, combinedResult);
- }
-
/**
* Thrown by {@link KeyCharacterMap#load} when a key character map could not be loaded.
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bd054bc..7e5fe63 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12297,7 +12297,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
if (!initialized) {
- a.initialize(mRight - mLeft, mBottom - mTop, getWidth(), getHeight());
+ a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
onAnimationStart();
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index ca5648a..ae68794 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -123,7 +123,7 @@ public abstract class AbsSeekBar extends ProgressBar {
invalidate();
if (needUpdate) {
updateThumbPos(getWidth(), getHeight());
- if (thumb.isStateful()) {
+ if (thumb != null && thumb.isStateful()) {
// Note that if the states are different this won't work.
// For now, let's consider that an app bug.
int[] state = getDrawableState();
diff --git a/core/java/android/widget/ExpandableListConnector.java b/core/java/android/widget/ExpandableListConnector.java
index 2ff6b70..bda64ba 100644
--- a/core/java/android/widget/ExpandableListConnector.java
+++ b/core/java/android/widget/ExpandableListConnector.java
@@ -372,7 +372,8 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
@Override
public boolean isEnabled(int flatListPos) {
- final ExpandableListPosition pos = getUnflattenedPos(flatListPos).position;
+ final PositionMetadata metadata = getUnflattenedPos(flatListPos);
+ final ExpandableListPosition pos = metadata.position;
boolean retValue;
if (pos.type == ExpandableListPosition.CHILD) {
@@ -382,7 +383,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
retValue = true;
}
- pos.recycle();
+ metadata.recycle();
return retValue;
}
@@ -461,7 +462,8 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
@Override
public int getItemViewType(int flatListPos) {
- final ExpandableListPosition pos = getUnflattenedPos(flatListPos).position;
+ final PositionMetadata metadata = getUnflattenedPos(flatListPos);
+ final ExpandableListPosition pos = metadata.position;
int retValue;
if (mExpandableListAdapter instanceof HeterogeneousExpandableList) {
@@ -481,7 +483,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
}
}
- pos.recycle();
+ metadata.recycle();
return retValue;
}
@@ -590,8 +592,10 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* @param groupPos position of the group to collapse
*/
boolean collapseGroup(int groupPos) {
- PositionMetadata pm = getFlattenedPos(ExpandableListPosition.obtain(
- ExpandableListPosition.GROUP, groupPos, -1, -1));
+ ExpandableListPosition elGroupPos = ExpandableListPosition.obtain(
+ ExpandableListPosition.GROUP, groupPos, -1, -1);
+ PositionMetadata pm = getFlattenedPos(elGroupPos);
+ elGroupPos.recycle();
if (pm == null) return false;
boolean retValue = collapseGroup(pm);
@@ -631,8 +635,10 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* @param groupPos the group to be expanded
*/
boolean expandGroup(int groupPos) {
- PositionMetadata pm = getFlattenedPos(ExpandableListPosition.obtain(
- ExpandableListPosition.GROUP, groupPos, -1, -1));
+ ExpandableListPosition elGroupPos = ExpandableListPosition.obtain(
+ ExpandableListPosition.GROUP, groupPos, -1, -1);
+ PositionMetadata pm = getFlattenedPos(elGroupPos);
+ elGroupPos.recycle();
boolean retValue = expandGroup(pm);
pm.recycle();
return retValue;
@@ -971,7 +977,10 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
public int groupInsertIndex;
private void resetState() {
- position = null;
+ if (position != null) {
+ position.recycle();
+ position = null;
+ }
groupMetadata = null;
groupInsertIndex = 0;
}
@@ -1005,6 +1014,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
}
public void recycle() {
+ resetState();
synchronized (sPool) {
if (sPool.size() < MAX_POOL_SIZE) {
sPool.add(this);
diff --git a/core/java/android/widget/ExpandableListPosition.java b/core/java/android/widget/ExpandableListPosition.java
index e8d6113..bb68da6 100644
--- a/core/java/android/widget/ExpandableListPosition.java
+++ b/core/java/android/widget/ExpandableListPosition.java
@@ -125,6 +125,10 @@ class ExpandableListPosition {
return elp;
}
+ /**
+ * Do not call this unless you obtained this via ExpandableListPosition.obtain().
+ * PositionMetadata will handle recycling its own children.
+ */
public void recycle() {
synchronized (sPool) {
if (sPool.size() < MAX_POOL_SIZE) {
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index c2d8bda..a746370 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -326,7 +326,6 @@ public class ExpandableListView extends ListView {
indicator.draw(canvas);
}
}
-
pos.recycle();
}
@@ -613,8 +612,10 @@ public class ExpandableListView extends ListView {
* was already expanded, this will return false)
*/
public boolean expandGroup(int groupPos, boolean animate) {
- PositionMetadata pm = mConnector.getFlattenedPos(ExpandableListPosition.obtain(
- ExpandableListPosition.GROUP, groupPos, -1, -1));
+ ExpandableListPosition elGroupPos = ExpandableListPosition.obtain(
+ ExpandableListPosition.GROUP, groupPos, -1, -1);
+ PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
+ elGroupPos.recycle();
boolean retValue = mConnector.expandGroup(pm);
if (mOnGroupExpandListener != null) {
@@ -776,8 +777,10 @@ public class ExpandableListView extends ListView {
* @return The flat list position for the given child or group.
*/
public int getFlatListPosition(long packedPosition) {
- PositionMetadata pm = mConnector.getFlattenedPos(ExpandableListPosition
- .obtainPosition(packedPosition));
+ ExpandableListPosition elPackedPos = ExpandableListPosition
+ .obtainPosition(packedPosition);
+ PositionMetadata pm = mConnector.getFlattenedPos(elPackedPos);
+ elPackedPos.recycle();
final int flatListPosition = pm.position.flatListPos;
pm.recycle();
return getAbsoluteFlatPosition(flatListPosition);
@@ -988,11 +991,11 @@ public class ExpandableListView extends ListView {
final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
ExpandableListPosition pos = pm.position;
- pm.recycle();
id = getChildOrGroupId(pos);
long packedPosition = pos.getPackedPosition();
- pos.recycle();
+
+ pm.recycle();
return new ExpandableListContextMenuInfo(view, packedPosition, id);
}
diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java
index 0c5d5ef..d1c2d2e 100644
--- a/core/java/com/android/internal/util/AsyncChannel.java
+++ b/core/java/com/android/internal/util/AsyncChannel.java
@@ -150,7 +150,7 @@ public class AsyncChannel {
*/
public static final int CMD_CHANNEL_DISCONNECTED = BASE + 4;
- private static final int CMD_TO_STRING_COUNT = CMD_CHANNEL_DISCONNECTED + 1;
+ private static final int CMD_TO_STRING_COUNT = CMD_CHANNEL_DISCONNECTED - BASE + 1;
private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
static {
sCmdToString[CMD_CHANNEL_HALF_CONNECTED - BASE] = "CMD_CHANNEL_HALF_CONNECTED";
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 36b4b45..6cd8955 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -457,9 +457,9 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
// connect to camera service
static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
- jobject weak_this, jint cameraId, jboolean force, jboolean keep)
+ jobject weak_this, jint cameraId)
{
- sp<Camera> camera = Camera::connect(cameraId, force, keep);
+ sp<Camera> camera = Camera::connect(cameraId);
if (camera == NULL) {
jniThrowRuntimeException(env, "Fail to connect to camera service");
@@ -821,15 +821,6 @@ static void android_hardware_Camera_enableFocusMoveCallback(JNIEnv *env, jobject
}
}
-static bool android_hardware_Camera_isReleased(JNIEnv *env, jobject thiz)
-{
- ALOGV("isReleased");
- sp<Camera> camera = get_native_camera(env, thiz, NULL);
- if (camera == 0) return true;
-
- return (camera->sendCommand(CAMERA_CMD_PING, 0, 0) != NO_ERROR);
-}
-
//-------------------------------------------------
static JNINativeMethod camMethods[] = {
@@ -840,7 +831,7 @@ static JNINativeMethod camMethods[] = {
"(ILandroid/hardware/Camera$CameraInfo;)V",
(void*)android_hardware_Camera_getCameraInfo },
{ "native_setup",
- "(Ljava/lang/Object;IZZ)V",
+ "(Ljava/lang/Object;I)V",
(void*)android_hardware_Camera_native_setup },
{ "native_release",
"()V",
@@ -908,9 +899,6 @@ static JNINativeMethod camMethods[] = {
{ "enableFocusMoveCallback",
"(I)V",
(void *)android_hardware_Camera_enableFocusMoveCallback},
- { "isReleased",
- "()Z",
- (void *)android_hardware_Camera_isReleased},
};
struct field {
diff --git a/core/res/res/layout/notification_template_base.xml b/core/res/res/layout/notification_template_base.xml
index 74b3fa9..bd26232 100644
--- a/core/res/res/layout/notification_template_base.xml
+++ b/core/res/res/layout/notification_template_base.xml
@@ -19,7 +19,7 @@
android:background="@android:color/background_dark"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="64dp"
internal:layout_minHeight="64dp"
internal:layout_maxHeight="64dp"
>
diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs
index 686bde3..a21708c 100644
--- a/docs/html/resources/resources_toc.cs
+++ b/docs/html/resources/resources_toc.cs
@@ -124,6 +124,23 @@ class="new">&nbsp;new!</span></span>
</li>
<li class="toggle-list">
+ <div><a href="<?cs var:toroot ?>training/cloudsync/index.html">
+ <span class="en">Syncing to the Cloud<span class="new">&nbsp;new!</span></span>
+ </a></div>
+ <ul>
+ <li><a href="<?cs var:toroot ?>training/cloudsync/aesync.html">
+ <span class="en">Syncing with App Engine</span>
+ </a>
+ </li>
+ <li><a href="<?cs var:toroot ?>training/cloudsync/backupapi.html">
+ <span class="en">Using the Backup API</span>
+ </a>
+ </li>
+ </ul>
+ </li>
+
+
+ <li class="toggle-list">
<div><a href="<?cs var:toroot ?>training/search/index.html">
<span class="en">Adding Search Functionality<span class="new">&nbsp;new!</span></span>
</a>
@@ -369,11 +386,11 @@ class="new">&nbsp;new!</span></span>
</li>
<li><a href="<?cs var:toroot ?>training/displaying-bitmaps/display-bitmap.html">
<span class="en">Displaying Bitmaps in Your UI</span>
- </a>
</li>
- </ul>
+ <ul>
</li>
+
<li class="toggle-list">
<div><a href="<?cs var:toroot ?>training/accessibility/index.html">
<span class="en">Implementing Accessibility<span class="new">&nbsp;new!</span></span>
@@ -391,9 +408,11 @@ class="new">&nbsp;new!</span></span>
</li>
</ul>
- </li>
-
-
+ </li>
+
+
+
+
<li>
<span class="heading">
<span class="en">Technical Resources</span>
diff --git a/docs/html/training/location/currentlocation.jd b/docs/html/training/basics/location/currentlocation.jd
index 4692530..4692530 100644
--- a/docs/html/training/location/currentlocation.jd
+++ b/docs/html/training/basics/location/currentlocation.jd
diff --git a/docs/html/training/location/geocoding.jd b/docs/html/training/basics/location/geocoding.jd
index 6364976..6364976 100644
--- a/docs/html/training/location/geocoding.jd
+++ b/docs/html/training/basics/location/geocoding.jd
diff --git a/docs/html/training/location/index.jd b/docs/html/training/basics/location/index.jd
index 48cfbc3..48cfbc3 100644
--- a/docs/html/training/location/index.jd
+++ b/docs/html/training/basics/location/index.jd
diff --git a/docs/html/training/location/locationmanager.jd b/docs/html/training/basics/location/locationmanager.jd
index 61abcbd..61abcbd 100644
--- a/docs/html/training/location/locationmanager.jd
+++ b/docs/html/training/basics/location/locationmanager.jd
diff --git a/docs/html/training/cloudsync/aesync.jd b/docs/html/training/cloudsync/aesync.jd
new file mode 100644
index 0000000..c60d28b
--- /dev/null
+++ b/docs/html/training/cloudsync/aesync.jd
@@ -0,0 +1,432 @@
+page.title=Syncing with App Engine
+parent.title=Syncing to the Cloud
+parent.link=index.html
+
+trainingnavtop=true
+next.title=Using the Backup API
+next.link=backupapi.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- table of contents -->
+<h2>This lesson teaches you how to</h2>
+<ol>
+ <li><a href="#prepare">Prepare Your Environment</a></li>
+ <li><a href="#project">Create Your Project</a></li>
+ <li><a href="#data">Create the Data Layer</a></li>
+ <li><a href="#persistence">Create the Persistence Layer</a></li>
+ <li><a href="#androidapp">Query and Update from the Android App</a></li>
+ <li><a href="#serverc2dm">Configure the C2DM Server-Side</a></li>
+ <li><a href="#clientc2dm">Configure the C2DM Client-Side</a></li>
+</ol>
+<h2>You should also read</h2>
+ <ul>
+ <li><a
+ href="http://developers.google.com/appengine/">App Engine</a></li>
+ <li><a href="http://code.google.com/android/c2dm/">Android Cloud to Device
+ Messaging Framework</a></li>
+ </ul>
+<h2>Try it out</h2>
+
+<p>This lesson uses the Cloud Tasks sample code, originally shown at the
+<a href="http://www.youtube.com/watch?v=M7SxNNC429U">Android + AppEngine: A Developer's Dream Combination</a>
+talk at Google I/O. You can use the sample application as a source of reusable code for your own
+application, or simply as a reference for how the Android and cloud pieces of the overall
+application fit together. You can also build the sample application and see how it runs
+on your own device or emulator.</p>
+<p>
+ <a href="http://code.google.com/p/cloud-tasks-io/" class="button">Cloud Tasks
+ App</a>
+</p>
+
+</div>
+</div>
+
+<p>Writing an app that syncs to the cloud can be a challenge. There are many little
+details to get right, like server-side auth, client-side auth, a shared data
+model, and an API. One way to make this much easier is to use the Google Plugin
+for Eclipse, which handles a lot of the plumbing for you when building Android
+and App Engine applications that talk to each other. This lesson walks you through building such a project.</p>
+
+<p>Following this lesson shows you how to:</p>
+<ul>
+ <li>Build Android and Appengine apps that can communicate with each other</li>
+ <li>Take advantage of Cloud to Device Messaging (C2DM) so your Android app doesn't have to poll for updates</li>
+</ul>
+
+<p>This lesson focuses on local development, and does not cover distribution
+(i.e, pushing your App Engine app live, or publishing your Android App to
+market), as those topics are covered extensively elsewhere.</p>
+
+<h2 id="prepare">Prepare Your Environment</h2>
+<p>If you want to follow along with the code example in this lesson, you must do
+the following to prepare your development environment:</p>
+<ul>
+<li>Install the <a href="http://code.google.com/eclipse/">Google Plugin for
+ Eclipse.</a></li>
+<li>Install the <a
+ href="http://code.google.com/webtoolkit/download.html">GWT SDK</a> and the <a
+ href="http://code.google.com/appengine/">Java App Engine SDK</a>. The <a
+ href="http://code.google.com/eclipse/docs/getting_started.html">Quick Start
+ Guide</a> shows you how to install these components.</li>
+<li>Sign up for <a href="http://code.google.com/android/c2dm/signup.html">C2DM
+ access</a>. We strongly recommend <a
+ href="https://accounts.google.com/SignUp">creating a new Google account</a> specifically for
+connecting to C2DM. The server component in this lesson uses this <em>role
+ account</em> repeatedly to authenticate with Google servers.
+</li>
+</ul>
+
+<h2 id="project">Create Your Projects</h2>
+<p>After installing the Google Plugin for Eclipse, notice that a new kind of Android project
+exists when you create a new Eclipse project: The <strong>App Engine Connected
+ Android Project</strong> (under the <strong>Google</strong> project category).
+A wizard guides you through creating this project,
+during the course of which you are prompted to enter the account credentials for the role
+account you created.</p>
+
+<p class="note"><strong>Note:</strong> Remember to enter the credentials for
+your <i>role account</i> (the one you created to access C2DM services), not an
+account you'd log into as a user, or as an admin.</p>
+
+<p>Once you're done, you'll see two projects waiting for you in your
+workspace&mdash;An Android application and an App Engine application. Hooray!
+These two applications are already fully functional&mdash; the wizard has
+created a sample application which lets you authenticate to the App Engine
+application from your Android device using AccountManager (no need to type in
+your credentials), and an App Engine app that can send messages to any logged-in
+device using C2DM. In order to spin up your application and take it for a test
+drive, do the following:</p>
+
+<p>To spin up the Android application, make sure you have an AVD with a platform
+version of <em>at least</em> Android 2.2 (API Level 8). Right click on the Android project in
+Eclipse, and go to <strong>Debug As &gt; Local App Engine Connected Android
+ Application</strong>. This launches the emulator in such a way that it can
+test C2DM functionality (which typically works through Google Play). It'll
+also launch a local instance of App Engine containing your awesome
+application.</p>
+
+<h2 id="data">Create the Data Layer</h2>
+
+<p>At this point you have a fully functional sample application running. Now
+it's time to start changing the code to create your own application.</p>
+
+<p>First, create the data model that defines the data shared between
+the App Engine and Android applications. To start, open up the source folder of
+your App Engine project, and navigate down to the <strong>(yourApp)-AppEngine
+ &gt; src &gt; (yourapp) &gt; server</strong> package. Create a new class in there containing some data you want to
+store server-side. The code ends up looking something like this:</p>
+<pre>
+package com.cloudtasks.server;
+
+import javax.persistence.*;
+
+&#64;Entity
+public class Task {
+
+ private String emailAddress;
+ private String name;
+ private String userId;
+ private String note;
+
+ &#64;Id
+ &#64;GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ public Task() {
+ }
+
+ public String getEmailAddress() {
+ return this.emailAddress;
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+ ...
+}
+</pre>
+<p>Note the use of annotations: <code>Entity</code>, <code>Id</code> and
+<code>GeneratedValue</code> are all part of the <a
+ href="http://www.oracle.com/technetwork/articles/javaee/jpa-137156.html">Java
+ Persistence API</a>. Essentially, the <code>Entity</code> annotation goes
+above the class declaration, and indicates that this class represents an entity
+in your data layer. The <code>Id</code> and <code>GeneratedValue</code>
+annotations, respectively, indicate the field used as a lookup key for this
+class, and how that id is generated (in this case,
+<code>GenerationType.IDENTITY</code> indicates that the is generated by
+the database). You can find more on this topic in the App Engine documentation,
+on the page <a
+ href="http://code.google.com/appengine/docs/java/datastore/jpa/overview.html">Using
+ JPA with App Engine</a>.</p>
+
+<p>Once you've written all the classes that represent entities in your data
+layer, you need a way for the Android and App Engine applications to communicate
+about this data. This communication is enabled by creating a Remote Procedure
+Call (RPC) service.
+Typically, this involves a lot of monotonous code. Fortunately, there's an easy way! Right
+click on the server project in your App Engine source folder, and in the context
+menu, navigate to <strong>New &gt; Other</strong> and then, in the resulting
+screen, select <strong>Google &gt; RPC Service.</strong> A wizard appears, pre-populated
+with all the Entities you created in the previous step,
+which it found by seeking out the <code>&#64;Entity</code> annotation in the
+source files you added. Pretty neat, right? Click <strong>Finish</strong>, and the wizard
+creates a Service class with stub methods for the Create, Retrieve, Update and
+Delete (CRUD) operations of all your entities.</p>
+
+<h2 id="persistence">Create the Persistence Layer</h2>
+
+<p>The persistence layer is where your application data is stored
+long-term, so any information you want to keep for your users needs to go here.
+You have several options for writing your persistence layer, depending on
+what kind of data you want to store. A few of the options hosted by Google
+(though you don't have to use these services) include <a
+ href="http://code.google.com/apis/storage/">Google Storage for Developers</a>
+and App Engine's built-in <a
+ href="http://code.google.com/appengine/docs/java/gettingstarted/usingdatastore.html">Datastore</a>.
+The sample code for this lesson uses DataStore code.</p>
+
+<p>Create a class in your <code>com.cloudtasks.server</code> package to handle
+persistence layer input and output. In order to access the data store, use the <a
+ href="http://db.apache.org/jdo/api20/apidocs/javax/jdo/PersistenceManager.html">PersistenceManager</a>
+class. You can generate an instance of this class using the PMF class in the
+<code>com.google.android.c2dm.server.PMF</code> package, and then use that to
+perform basic CRUD operations on your data store, like this:</p>
+<pre>
+/**
+* Remove this object from the data store.
+*/
+public void delete(Long id) {
+ PersistenceManager pm = PMF.get().getPersistenceManager();
+ try {
+ Task item = pm.getObjectById(Task.class, id);
+ pm.deletePersistent(item);
+ } finally {
+ pm.close();
+ }
+}
+</pre>
+
+<p>You can also use <a
+ href="http://code.google.com/appengine/docs/python/datastore/queryclass.html">Query</a>
+objects to retrieve data from your Datastore. Here's an example of a method
+that searches out an object by its ID.</p>
+
+<pre>
+public Task find(Long id) {
+ if (id == null) {
+ return null;
+ }
+
+ PersistenceManager pm = PMF.get().getPersistenceManager();
+ try {
+ Query query = pm.newQuery("select from " + Task.class.getName()
+ + " where id==" + id.toString() + " && emailAddress=='" + getUserEmail() + "'");
+ List<Task> list = (List<Task>) query.execute();
+ return list.size() == 0 ? null : list.get(0);
+ } catch (RuntimeException e) {
+ System.out.println(e);
+ throw e;
+ } finally {
+ pm.close();
+ }
+}
+</pre>
+
+<p>For a good example of a class that encapsulates the persistence layer for
+you, check out the <a
+ href="http://code.google.com/p/cloud-tasks-io/source/browse/trunk/CloudTasks-AppEngine/src/com/cloudtasks/server/DataStore.java">DataStore</a>
+class in the Cloud Tasks app.</p>
+
+
+
+<h2 id="androidapp">Query and Update from the Android App</h2>
+
+<p>In order to keep in sync with the App Engine application, your Android application
+needs to know how to do two things: Pull data from the cloud, and send data up
+to the cloud. Much of the plumbing for this is generated by the
+plugin, but you need to wire it up to your Android user interface yourself.</p>
+
+<p>Pop open the source code for the main Activity in your project and look for
+<code>&lt;YourProjectName&gt; Activity.java</code>, then for the method
+<code>setHelloWorldScreenContent()</code>. Obviously you're not building a
+HelloWorld app, so delete this method entirely and replace it
+with something relevant. However, the boilerplate code has some very important
+characteristics. For one, the code that communicates with the cloud is wrapped
+in an {@link android.os.AsyncTask} and therefore <em>not</em> hitting the
+network on the UI thread. Also, it gives an easy template for how to access
+the cloud in your own code, using the <a
+ href="http://code.google.com/webtoolkit/doc/latest/DevGuideRequestFactory.html">RequestFactory</a>
+class generated that was auto-generated for you by the Eclipse plugin (called
+MyRequestFactory in the example below), and various {@code Request} types.</p>
+
+<p>For instance, if your server-side data model included an object called {@code
+Task} when you generated an RPC layer it automatically created a
+{@code TaskRequest} class for you, as well as a {@code TaskProxy} representing the individual
+task. In code, requesting a list of all these tasks from the server looks
+like this:</p>
+
+<pre>
+public void fetchTasks (Long id) {
+ // Request is wrapped in an AsyncTask to avoid making a network request
+ // on the UI thread.
+ new AsyncTask<Long, Void, List<TaskProxy>>() {
+ &#64;Override
+ protected List<TaskProxy> doInBackground(Long... arguments) {
+ final List<TaskProxy> list = new ArrayList<TaskProxy>();
+ MyRequestFactory factory = Util.getRequestFactory(mContext,
+ MyRequestFactory.class);
+ TaskRequest taskRequest = factory.taskNinjaRequest();
+
+ if (arguments.length == 0 || arguments[0] == -1) {
+ factory.taskRequest().queryTasks().fire(new Receiver<List<TaskProxy>>() {
+ &#64;Override
+ public void onSuccess(List<TaskProxy> arg0) {
+ list.addAll(arg0);
+ }
+ });
+ } else {
+ newTask = true;
+ factory.taskRequest().readTask(arguments[0]).fire(new Receiver<TaskProxy>() {
+ &#64;Override
+ public void onSuccess(TaskProxy arg0) {
+ list.add(arg0);
+ }
+ });
+ }
+ return list;
+ }
+
+ &#64;Override
+ protected void onPostExecute(List<TaskProxy> result) {
+ TaskNinjaActivity.this.dump(result);
+ }
+
+ }.execute(id);
+}
+...
+
+public void dump (List<TaskProxy> tasks) {
+ for (TaskProxy task : tasks) {
+ Log.i("Task output", task.getName() + "\n" + task.getNote());
+ }
+}
+</pre>
+
+<p>This {@link android.os.AsyncTask} returns a list of
+<code>TaskProxy</code> objects, and sends it to the debug {@code dump()} method
+upon completion. Note that if the argument list is empty, or the first argument
+is a -1, all tasks are retrieved from the server. Otherwise, only the ones with
+IDs in the supplied list are returned. All the fields you added to the task
+entity when building out the App Engine application are available via get/set
+methods in the <code>TaskProxy</code> class.</p>
+
+<p>In order to create new tasks and send them to the cloud, create a request
+object and use it to create a proxy object. Then populate the proxy object and
+call its update method. Once again, this should be done in an
+<code>AsyncTask</code> to avoid doing networking on the UI thread. The end
+result looks something like this.</p>
+
+<pre>
+new AsyncTask<Void, Void, Void>() {
+ &#64;Override
+ protected Void doInBackground(Void... arg0) {
+ MyRequestFactory factory = (MyRequestFactory)
+ Util.getRequestFactory(TasksActivity.this,
+ MyRequestFactory.class);
+ TaskRequest request = factory.taskRequest();
+
+ // Create your local proxy object, populate it
+ TaskProxy task = request.create(TaskProxy.class);
+ task.setName(taskName);
+ task.setNote(taskDetails);
+ task.setDueDate(dueDate);
+
+ // To the cloud!
+ request.updateTask(task).fire();
+ return null;
+ }
+}.execute();
+</pre>
+
+<h2 id="serverc2dm">Configure the C2DM Server-Side</h2>
+
+<p>In order to set up C2DM messages to be sent to your Android device, go back
+into your App Engine codebase, and open up the service class that was created
+when you generated your RPC layer. If the name of your project is Foo,
+this class is called FooService. Add a line to each of the methods for
+adding, deleting, or updating data so that a C2DM message is sent to the
+user's device. Here's an example of an update task:
+</p>
+
+<pre>
+public static Task updateTask(Task task) {
+ task.setEmailAddress(DataStore.getUserEmail());
+ task = db.update(task);
+ DataStore.sendC2DMUpdate(TaskChange.UPDATE + TaskChange.SEPARATOR + task.getId());
+ return task;
+}
+
+// Helper method. Given a String, send it to the current user's device via C2DM.
+public static void sendC2DMUpdate(String message) {
+ UserService userService = UserServiceFactory.getUserService();
+ User user = userService.getCurrentUser();
+ ServletContext context = RequestFactoryServlet.getThreadLocalRequest().getSession().getServletContext();
+ SendMessage.sendMessage(context, user.getEmail(), message);
+}
+</pre>
+
+<p>In the following example, a helper class, {@code TaskChange}, has been created with a few
+constants. Creating such a helper class makes managing the communication
+between App Engine and Android apps much easier. Just create it in the shared
+folder, define a few constants (flags for what kind of message you're sending
+and a seperator is typically enough), and you're done. By way of example,
+the above code works off of a {@code TaskChange} class defined as this:</p>
+
+<pre>
+public class TaskChange {
+ public static String UPDATE = "Update";
+ public static String DELETE = "Delete";
+ public static String SEPARATOR = ":";
+}
+</pre>
+
+<h2 id="clientc2dm">Configure the C2DM Client-Side</h2>
+
+<p>In order to define the Android applications behavior when a C2DM is recieved,
+open up the <code>C2DMReceiver</code> class, and browse to the
+<code>onMessage()</code> method. Tweak this method to update based on the content
+of the message.</p>
+<pre>
+//In your C2DMReceiver class
+
+public void notifyListener(Intent intent) {
+ if (listener != null) {
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ String message = (String) extras.get("message");
+ String[] messages = message.split(Pattern.quote(TaskChange.SEPARATOR));
+ listener.onTaskUpdated(messages[0], Long.parseLong(messages[1]));
+ }
+ }
+}
+</pre>
+
+<pre>
+// Elsewhere in your code, wherever it makes sense to perform local updates
+public void onTasksUpdated(String messageType, Long id) {
+ if (messageType.equals(TaskChange.DELETE)) {
+ // Delete this task from your local data store
+ ...
+ } else {
+ // Call that monstrous Asynctask defined earlier.
+ fetchTasks(id);
+ }
+}
+</pre>
+<p>
+Once you have C2DM set up to trigger local updates, you're all done.
+Congratulations, you have a cloud-connected Android application!</p>
diff --git a/docs/html/training/cloudsync/backupapi.jd b/docs/html/training/cloudsync/backupapi.jd
new file mode 100644
index 0000000..3055596
--- /dev/null
+++ b/docs/html/training/cloudsync/backupapi.jd
@@ -0,0 +1,193 @@
+page.title=Using the Backup API
+parent.title=Syncing to the Cloud
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Syncing with App Engine
+previous.link=aesync.html
+
+@jd:body
+
+<div id="tb-wrapper">
+ <div id="tb">
+ <h2>This lesson teaches you to</h2>
+ <ol>
+ <li><a href="#register">Register for the Android Backup Service</a></li>
+ <li><a href="#manifest">Configure Your Manifest</a></li>
+ <li><a href="#agent">Write Your Backup Agent</a></li>
+ <li><a href="#backup">Request a Backup</a></li>
+ <li><a href="#restore">Restore from a Backup</a></li>
+ </ol>
+ <h2>You should also read</h2>
+ <ul>
+ <li><a
+ href="http://developer.android.com/guide/topics/data/backup.html">Data
+ Backup</a></li>
+ </ul>
+ </div>
+</div>
+
+<p>When a user purchases a new device or resets their existing one, they might
+expect that when Google Play restores your app back to their device during the
+initial setup, the previous data associated with the app restores as well. By
+default, that doesn't happen and all the user's accomplishments or settings in
+your app are lost.</p>
+<p>For situations where the volume of data is relatively light (less than a
+megabyte), like the user's preferences, notes, game high scores or other
+stats, the Backup API provides a lightweight solution. This lesson walks you
+through integrating the Backup API into your application, and restoring data to
+new devices using the Backup API.</p>
+
+<h2 id="register">Register for the Android Backup Service</h2>
+<p>This lesson requires the use of the <a
+ href="http://code.google.com/android/backup/index.html">Android Backup
+ Service</a>, which requires registration. Go ahead and <a
+ href="http://code.google.com/android/backup/signup.html">register here</a>. Once
+that's done, the service pre-populates an XML tag for insertion in your Android
+Manifest, which looks like this:</p>
+<pre>
+&lt;meta-data android:name="com.google.android.backup.api_key"
+android:value="ABcDe1FGHij2KlmN3oPQRs4TUvW5xYZ" /&gt;
+</pre>
+<p>Note that each backup key works with a specific package name. If you have
+different applications, register separate keys for each one.</p>
+
+
+<h2 id="manifest">Configure Your Manifest</h2>
+<p>Use of the Android Backup Service requires two additions to your application
+manifest. First, declare the name of the class that acts as your backup agent,
+then add the snippet above as a child element of the Application tag. Assuming
+your backup agent is going to be called {@code TheBackupAgent}, here's an example of
+what the manifest looks like with this tag included:</p>
+
+<pre>
+&lt;application android:label="MyApp"
+ android:backupAgent="TheBackupAgent"&gt;
+ ...
+ &lt;meta-data android:name="com.google.android.backup.api_key"
+ android:value="ABcDe1FGHij2KlmN3oPQRs4TUvW5xYZ" /&gt;
+ ...
+&lt;/application&gt;
+</pre>
+<h2 id="agent">Write Your Backup Agent</h2>
+<p>The easiest way to create your backup agent is by extending the wrapper class
+{@link android.app.backup.BackupAgentHelper}. Creating this helper class is
+actually a very simple process. Just create a class with the same name as you
+used in the manifest in the previous step (in this example, {@code
+TheBackupAgent}),
+and extend {@code BackupAgentHelper}. Then override the {@link
+android.app.backup.BackupAgent#onCreate()}.</p>
+
+<p>Inside the {@link android.app.backup.BackupAgent#onCreate()} method, create a {@link
+android.app.backup.BackupHelper}. These helpers are
+specialized classes for backing up certain kinds of data. The Android framework
+currently includes two such helpers: {@link
+android.app.backup.FileBackupHelper} and {@link
+android.app.backup.SharedPreferencesBackupHelper}. After you create the helper
+and point it at the data you want to back up, just add it to the
+BackupAgentHelper using the {@link android.app.backup.BackupAgentHelper#addHelper(String, BackupHelper) addHelper()}
+method, adding a key which is used to
+retrieve the data later. In most cases the entire
+implementation is perhaps 10 lines of code.</p>
+
+<p>Here's an example that backs up a high scores file.</p>
+
+<pre>
+ import android.app.backup.BackupAgentHelper;
+ import android.app.backup.FileBackupHelper;
+
+
+ public class TheBackupAgent extends BackupAgentHelper {
+ // The name of the SharedPreferences file
+ static final String HIGH_SCORES_FILENAME = "scores";
+
+ // A key to uniquely identify the set of backup data
+ static final String FILES_BACKUP_KEY = "myfiles";
+
+ // Allocate a helper and add it to the backup agent
+ &#64;Override
+ void onCreate() {
+ FileBackupHelper helper = new FileBackupHelper(this, HIGH_SCORES_FILENAME);
+ addHelper(FILES_BACKUP_KEY, helper);
+ }
+}
+</pre>
+<p>For added flexibility, {@link android.app.backup.FileBackupHelper}'s
+constructor can take a variable number of filenames. You could just as easily
+have backed up both a high scores file and a game progress file just by adding
+an extra parameter, like this:</p>
+<pre>
+ &#64;Override
+ void onCreate() {
+ FileBackupHelper helper = new FileBackupHelper(this, HIGH_SCORES_FILENAME, PROGRESS_FILENAME);
+ addHelper(FILES_BACKUP_KEY, helper);
+ }
+</pre>
+<p>Backing up preferences is similarly easy. Create a {@link
+android.app.backup.SharedPreferencesBackupHelper} the same way you did a {@link
+android.app.backup.FileBackupHelper}. In this case, instead of adding filenames
+to the constructor, add the names of the shared preference groups being used by
+your application. Here's an example of how your backup agent helper might look if
+high scores are implemented as preferences instead of a flat file:</p>
+
+<pre>
+ import android.app.backup.BackupAgentHelper;
+ import android.app.backup.SharedPreferencesBackupHelper;
+
+ public class TheBackupAgent extends BackupAgentHelper {
+ // The names of the SharedPreferences groups that the application maintains. These
+ // are the same strings that are passed to getSharedPreferences(String, int).
+ static final String PREFS_DISPLAY = "displayprefs";
+ static final String PREFS_SCORES = "highscores";
+
+ // An arbitrary string used within the BackupAgentHelper implementation to
+ // identify the SharedPreferencesBackupHelper's data.
+ static final String MY_PREFS_BACKUP_KEY = "myprefs";
+
+ // Simply allocate a helper and install it
+ void onCreate() {
+ SharedPreferencesBackupHelper helper =
+ new SharedPreferencesBackupHelper(this, PREFS_DISPLAY, PREFS_SCORES);
+ addHelper(MY_PREFS_BACKUP_KEY, helper);
+ }
+ }
+</pre>
+
+<p>You can add as many backup helper instances to your backup agent helper as you
+like, but remember that you only need one of each type. One {@link
+android.app.backup.FileBackupHelper} handles all the files that you need to back up, and one
+{@link android.app.backup.SharedPreferencesBackupHelper} handles all the shared
+preferencegroups you need backed up.
+</p>
+
+
+<h2 id="backup">Request a Backup</h2>
+<p>In order to request a backup, just create an instance of the {@link
+android.app.backup.BackupManager}, and call it's {@link
+android.app.backup.BackupManager#dataChanged()} method.</p>
+
+<pre>
+ import android.app.backup.BackupManager;
+ ...
+
+ public void requestBackup() {
+ BackupManager bm = new BackupManager(this);
+ bm.dataChanged();
+ }
+</pre>
+
+<p>This call notifies the backup manager that there is data ready to be backed
+up to the cloud. At some point in the future, the backup manager then calls
+your backup agent's {@link
+android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput,
+ParcelFileDescriptor) onBackup()} method. You can make
+the call whenever your data has changed, without having to worry about causing
+excessive network activity. If you request a backup twice before a backup
+occurs, the backup only occurs once.</p>
+
+
+<h2 id="restore">Restore from a Backup</h2>
+<p>Typically you shouldn't ever have to manually request a restore, as it
+happens automatically when your application is installed on a device. However,
+if it <em>is</em> necessary to trigger a manual restore, just call the
+{@link android.app.backup.BackupManager#requestRestore(RestoreObserver) requestRestore()} method.</p>
diff --git a/docs/html/training/cloudsync/index.jd b/docs/html/training/cloudsync/index.jd
new file mode 100644
index 0000000..e53844b
--- /dev/null
+++ b/docs/html/training/cloudsync/index.jd
@@ -0,0 +1,34 @@
+page.title=Syncing to the Cloud
+
+trainingnavtop=true
+startpage=true
+next.title=Syncing with App Engine
+next.link=aesync.html
+
+@jd:body
+
+<p>By providing powerful APIs for internet connectivity, the Android framework
+helps you build rich cloud-enabled apps that sync their data to a remote web
+service, making sure all your devices always stay in sync, and your valuable
+data is always backed up to the cloud.</p>
+
+<p>This class covers different strategies for cloud enabled applications. It
+covers syncing data with the cloud using your own back-end web application, and
+backing up data using the cloud so that users can restore their data when
+installing your application on a new device.
+</p>
+
+<h2>Lessons</h2>
+
+<dl>
+ <dt><strong><a href="aesync.html">Syncing with App Engine.</a></strong></dt>
+ <dd>Learn how to create a paired App Engine app and Android app which share a
+ data model, authenticates using the AccountManager, and communicate with each
+ other via REST and C2DM.</dd>
+ <dt><strong><a href="backupapi.html">Using the Backup
+ API</a></strong></dt>
+ <dd>Learn how to integrate the Backup API into your Android Application, so
+ that user data such as preferences, notes, and high scores update seamlessly
+ across all of a user's devices</dd>
+</dl>
+
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 4e112af..34bf368 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -497,7 +497,6 @@ public class AudioRecord
* Returns the audio session ID.
*
* @return the ID of the audio session this AudioRecord belongs to.
- * @hide
*/
public int getAudioSessionId() {
return mSessionId;
diff --git a/media/java/android/media/audiofx/AcousticEchoCanceler.java b/media/java/android/media/audiofx/AcousticEchoCanceler.java
index e31f84c..4b59c88 100644
--- a/media/java/android/media/audiofx/AcousticEchoCanceler.java
+++ b/media/java/android/media/audiofx/AcousticEchoCanceler.java
@@ -37,7 +37,6 @@ import android.util.Log;
* state on a particular AudioRecord session.
* <p>See {@link android.media.audiofx.AudioEffect} class for more details on
* controlling audio effects.
- * @hide
*/
public class AcousticEchoCanceler extends AudioEffect {
@@ -90,9 +89,8 @@ public class AcousticEchoCanceler extends AudioEffect {
* @throws java.lang.IllegalArgumentException
* @throws java.lang.UnsupportedOperationException
* @throws java.lang.RuntimeException
- * @hide
*/
- public AcousticEchoCanceler(int audioSession)
+ private AcousticEchoCanceler(int audioSession)
throws IllegalArgumentException, UnsupportedOperationException, RuntimeException {
super(EFFECT_TYPE_AEC, EFFECT_TYPE_NULL, 0, audioSession);
}
diff --git a/media/java/android/media/audiofx/AutomaticGainControl.java b/media/java/android/media/audiofx/AutomaticGainControl.java
index eca7eec..83eb4e9 100644
--- a/media/java/android/media/audiofx/AutomaticGainControl.java
+++ b/media/java/android/media/audiofx/AutomaticGainControl.java
@@ -37,7 +37,6 @@ import android.util.Log;
* state on a particular AudioRecord session.
* <p>See {@link android.media.audiofx.AudioEffect} class for more details on
* controlling audio effects.
- * @hide
*/
public class AutomaticGainControl extends AudioEffect {
@@ -90,9 +89,8 @@ public class AutomaticGainControl extends AudioEffect {
* @throws java.lang.IllegalArgumentException
* @throws java.lang.UnsupportedOperationException
* @throws java.lang.RuntimeException
- * @hide
*/
- public AutomaticGainControl(int audioSession)
+ private AutomaticGainControl(int audioSession)
throws IllegalArgumentException, UnsupportedOperationException, RuntimeException {
super(EFFECT_TYPE_AGC, EFFECT_TYPE_NULL, 0, audioSession);
}
diff --git a/media/java/android/media/audiofx/NoiseSuppressor.java b/media/java/android/media/audiofx/NoiseSuppressor.java
index a2d3386..0ea42ab 100644
--- a/media/java/android/media/audiofx/NoiseSuppressor.java
+++ b/media/java/android/media/audiofx/NoiseSuppressor.java
@@ -38,7 +38,6 @@ import android.util.Log;
* state on a particular AudioRecord session.
* <p>See {@link android.media.audiofx.AudioEffect} class for more details on
* controlling audio effects.
- * @hide
*/
public class NoiseSuppressor extends AudioEffect {
@@ -92,7 +91,6 @@ public class NoiseSuppressor extends AudioEffect {
* @throws java.lang.IllegalArgumentException
* @throws java.lang.UnsupportedOperationException
* @throws java.lang.RuntimeException
- * @hide
*/
private NoiseSuppressor(int audioSession)
throws IllegalArgumentException, UnsupportedOperationException, RuntimeException {
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index 91d0add..47c0d57 100755
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -81,6 +81,20 @@ public class Visualizer {
*/
public static final int STATE_ENABLED = 2;
+ // to keep in sync with system/media/audio_effects/include/audio_effects/effect_visualizer.h
+ /**
+ * @hide
+ * Defines a capture mode where amplification is applied based on the content of the captured
+ * data. This is the default Visualizer mode, and is suitable for music visualization.
+ */
+ public static final int SCALING_MODE_NORMALIZED = 0;
+ /**
+ * @hide
+ * Defines a capture mode where the playback volume will affect (scale) the range of the
+ * captured data. A low playback volume will lead to low sample and fft values, and vice-versa.
+ */
+ public static final int SCALING_MODE_AS_PLAYED = 1;
+
// to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp
private static final int NATIVE_EVENT_PCM_CAPTURE = 0;
private static final int NATIVE_EVENT_FFT_CAPTURE = 1;
@@ -302,6 +316,44 @@ public class Visualizer {
}
/**
+ * @hide
+ * Set the type of scaling applied on the captured visualization data.
+ * @param mode see {@link #SCALING_MODE_NORMALIZED}
+ * and {@link #SCALING_MODE_AS_PLAYED}
+ * @return {@link #SUCCESS} in case of success,
+ * {@link #ERROR_BAD_VALUE} in case of failure.
+ * @throws IllegalStateException
+ */
+ public int setScalingMode(int mode)
+ throws IllegalStateException {
+ synchronized (mStateLock) {
+ if (mState == STATE_UNINITIALIZED) {
+ throw(new IllegalStateException("setScalingMode() called in wrong state: "
+ + mState));
+ }
+ return native_setScalingMode(mode);
+ }
+ }
+
+ /**
+ * @hide
+ * Returns the current scaling mode on the captured visualization data.
+ * @return the scaling mode, see {@link #SCALING_MODE_NORMALIZED}
+ * and {@link #SCALING_MODE_AS_PLAYED}.
+ * @throws IllegalStateException
+ */
+ public int getScalingMode()
+ throws IllegalStateException {
+ synchronized (mStateLock) {
+ if (mState == STATE_UNINITIALIZED) {
+ throw(new IllegalStateException("getScalingMode() called in wrong state: "
+ + mState));
+ }
+ return native_getScalingMode();
+ }
+ }
+
+ /**
* Returns the sampling rate of the captured audio.
* @return the sampling rate in milliHertz.
*/
@@ -588,6 +640,10 @@ public class Visualizer {
private native final int native_getCaptureSize();
+ private native final int native_setScalingMode(int mode);
+
+ private native final int native_getScalingMode();
+
private native final int native_getSamplingRate();
private native final int native_getWaveForm(byte[] waveform);
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index f015afb..c2655c7 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -491,6 +491,27 @@ android_media_visualizer_native_getCaptureSize(JNIEnv *env, jobject thiz)
}
static jint
+android_media_visualizer_native_setScalingMode(JNIEnv *env, jobject thiz, jint mode)
+{
+ Visualizer* lpVisualizer = getVisualizer(env, thiz);
+ if (lpVisualizer == NULL) {
+ return VISUALIZER_ERROR_NO_INIT;
+ }
+
+ return translateError(lpVisualizer->setScalingMode(mode));
+}
+
+static jint
+android_media_visualizer_native_getScalingMode(JNIEnv *env, jobject thiz)
+{
+ Visualizer* lpVisualizer = getVisualizer(env, thiz);
+ if (lpVisualizer == NULL) {
+ return -1;
+ }
+ return lpVisualizer->getScalingMode();
+}
+
+static jint
android_media_visualizer_native_getSamplingRate(JNIEnv *env, jobject thiz)
{
Visualizer* lpVisualizer = getVisualizer(env, thiz);
@@ -582,6 +603,8 @@ static JNINativeMethod gMethods[] = {
{"getMaxCaptureRate", "()I", (void *)android_media_visualizer_native_getMaxCaptureRate},
{"native_setCaptureSize", "(I)I", (void *)android_media_visualizer_native_setCaptureSize},
{"native_getCaptureSize", "()I", (void *)android_media_visualizer_native_getCaptureSize},
+ {"native_setScalingMode", "(I)I", (void *)android_media_visualizer_native_setScalingMode},
+ {"native_getScalingMode", "()I", (void *)android_media_visualizer_native_getScalingMode},
{"native_getSamplingRate", "()I", (void *)android_media_visualizer_native_getSamplingRate},
{"native_getWaveForm", "([B)I", (void *)android_media_visualizer_native_getWaveForm},
{"native_getFft", "([B)I", (void *)android_media_visualizer_native_getFft},
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVisualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVisualizerTest.java
index b0bf654..abf85d7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVisualizerTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVisualizerTest.java
@@ -188,6 +188,37 @@ public class MediaVisualizerTest extends ActivityInstrumentationTestCase2<MediaF
assertTrue(msg, result);
}
+ //Test case 1.2: check scaling mode
+ @LargeTest
+ public void test1_2ScalingMode() throws Exception {
+ boolean result = false;
+ String msg = "test1_2ScalingMode()";
+ getVisualizer(0);
+ try {
+ int res = mVisualizer.setScalingMode(Visualizer.SCALING_MODE_AS_PLAYED);
+ assertEquals(msg + ": setting SCALING_MODE_AS_PLAYED failed",
+ res, Visualizer.SUCCESS);
+ int mode = mVisualizer.getScalingMode();
+ assertEquals(msg + ": setting SCALING_MODE_AS_PLAYED didn't stick",
+ mode, Visualizer.SCALING_MODE_AS_PLAYED);
+
+ res = mVisualizer.setScalingMode(Visualizer.SCALING_MODE_NORMALIZED);
+ assertEquals(msg + ": setting SCALING_MODE_NORMALIZED failed",
+ res, Visualizer.SUCCESS);
+ mode = mVisualizer.getScalingMode();
+ assertEquals(msg + ": setting SCALING_MODE_NORMALIZED didn't stick",
+ mode, Visualizer.SCALING_MODE_NORMALIZED);
+
+ result = true;
+ } catch (IllegalStateException e) {
+ msg = msg.concat("IllegalStateException");
+ loge(msg, "set/get parameter() called in wrong state: " + e);
+ } finally {
+ releaseVisualizer();
+ }
+ assertTrue(msg, result);
+ }
+
//-----------------------------------------------------------------
// 2 - check capture
//----------------------------------
@@ -403,6 +434,91 @@ public class MediaVisualizerTest extends ActivityInstrumentationTestCase2<MediaF
assertTrue(msg, result);
}
+ //Test case 2.2: test capture in polling mode with volume scaling
+ @LargeTest
+ public void test2_2PollingCaptureVolumeScaling() throws Exception {
+ // test that when playing a sound, the energy measured with Visualizer in
+ // SCALING_MODE_AS_PLAYED mode decreases when lowering the volume
+ boolean result = false;
+ String msg = "test2_2PollingCaptureVolumeScaling()";
+ AudioEffect vc = null;
+ MediaPlayer mp = null;
+ AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
+ int ringerMode = am.getRingerMode();
+ am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ final int volMax = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ am.setStreamVolume(AudioManager.STREAM_MUSIC, volMax, 0);
+
+ try {
+ // test setup not related to tested functionality:
+ // creating a volume controller on output mix ensures that ro.audio.silent mutes
+ // audio after the effects and not before
+ vc = new AudioEffect(
+ AudioEffect.EFFECT_TYPE_NULL,
+ VOLUME_EFFECT_UUID,
+ 0,
+ 0);
+ vc.setEnabled(true);
+
+ mp = new MediaPlayer();
+ mp.setDataSource(MediaNames.SINE_200_1000);
+ mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ getVisualizer(mp.getAudioSessionId());
+
+ // verify we successfully set the Visualizer in SCALING_MODE_AS_PLAYED mode
+ mVisualizer.setScalingMode(Visualizer.SCALING_MODE_AS_PLAYED);
+ assertTrue(msg + " get volume scaling doesn't return SCALING_MODE_AS_PLAYED",
+ mVisualizer.getScalingMode() == Visualizer.SCALING_MODE_AS_PLAYED);
+ mVisualizer.setEnabled(true);
+ mp.prepare();
+ mp.start();
+ Thread.sleep(500);
+
+ // check capture on sound with music volume at max
+ byte[] data = new byte[mVisualizer.getCaptureSize()];
+ mVisualizer.getWaveForm(data);
+ int energyAtVolMax = computeEnergy(data, true);
+ assertTrue(msg +": getWaveForm reads insufficient level",
+ energyAtVolMax > 0);
+ log(msg, " engergy at max volume = "+energyAtVolMax);
+
+ // check capture on sound with music volume lowered from max
+ am.setStreamVolume(AudioManager.STREAM_MUSIC, (volMax * 2) / 3, 0);
+ Thread.sleep(500);
+ mVisualizer.getWaveForm(data);
+ int energyAtLowerVol = computeEnergy(data, true);
+ assertTrue(msg +": getWaveForm at lower volume reads insufficient level",
+ energyAtLowerVol > 0);
+ log(msg, "energy at lower volume = "+energyAtLowerVol);
+ assertTrue(msg +": getWaveForm didn't report lower energy when volume decreases",
+ energyAtVolMax > energyAtLowerVol);
+
+ result = true;
+ } catch (IllegalArgumentException e) {
+ msg = msg.concat(": IllegalArgumentException");
+ loge(msg, " hit exception " + e);
+ } catch (UnsupportedOperationException e) {
+ msg = msg.concat(": UnsupportedOperationException");
+ loge(msg, " hit exception " + e);
+ } catch (IllegalStateException e) {
+ msg = msg.concat("IllegalStateException");
+ loge(msg, " hit exception " + e);
+ } catch (InterruptedException e) {
+ loge(msg, " sleep() interrupted");
+ }
+ finally {
+ releaseVisualizer();
+ if (mp != null) {
+ mp.release();
+ }
+ if (vc != null) {
+ vc.release();
+ }
+ am.setRingerMode(ringerMode);
+ }
+ assertTrue(msg, result);
+ }
+
//-----------------------------------------------------------------
// private methods
//----------------------------------
diff --git a/packages/SystemUI/res/menu/notification_popup_menu.xml b/packages/SystemUI/res/menu/notification_popup_menu.xml
new file mode 100644
index 0000000..8923fb6
--- /dev/null
+++ b/packages/SystemUI/res/menu/notification_popup_menu.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/notification_inspect_item" android:title="@string/status_bar_notification_inspect_item_title" />
+</menu>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6dbe9d3..236ca6b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -365,4 +365,8 @@
<!-- Description of the desk dock action that invokes the Android Dreams screen saver feature -->
<string name="dreams_dock_launcher">Activate screen saver</string>
+
+ <!-- Title shown in notification popup for inspecting the responsible
+ application -->
+ <string name="status_bar_notification_inspect_item_title">App info</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 19657a9..414af89 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -23,11 +23,14 @@ import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.graphics.RectF;
+import android.os.Handler;
import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
import android.view.animation.LinearInterpolator;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
+import android.view.ViewConfiguration;
public class SwipeHelper implements Gefingerpoken {
static final String TAG = "com.android.systemui.SwipeHelper";
@@ -57,6 +60,7 @@ public class SwipeHelper implements Gefingerpoken {
private float mPagingTouchSlop;
private Callback mCallback;
+ private Handler mHandler;
private int mSwipeDirection;
private VelocityTracker mVelocityTracker;
@@ -67,15 +71,24 @@ public class SwipeHelper implements Gefingerpoken {
private boolean mCanCurrViewBeDimissed;
private float mDensityScale;
+ private boolean mLongPressSent;
+ private View.OnLongClickListener mLongPressListener;
+ private Runnable mWatchLongPress;
+
public SwipeHelper(int swipeDirection, Callback callback, float densityScale,
float pagingTouchSlop) {
mCallback = callback;
+ mHandler = new Handler();
mSwipeDirection = swipeDirection;
mVelocityTracker = VelocityTracker.obtain();
mDensityScale = densityScale;
mPagingTouchSlop = pagingTouchSlop;
}
+ public void setLongPressListener(View.OnLongClickListener listener) {
+ mLongPressListener = listener;
+ }
+
public void setDensityScale(float densityScale) {
mDensityScale = densityScale;
}
@@ -167,12 +180,19 @@ public class SwipeHelper implements Gefingerpoken {
}
}
+ private void removeLongPressCallback() {
+ if (mWatchLongPress != null) {
+ mHandler.removeCallbacks(mWatchLongPress);
+ }
+ }
+
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDragging = false;
+ mLongPressSent = false;
mCurrView = mCallback.getChildAtPosition(ev);
mVelocityTracker.clear();
if (mCurrView != null) {
@@ -180,10 +200,28 @@ public class SwipeHelper implements Gefingerpoken {
mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
mVelocityTracker.addMovement(ev);
mInitialTouchPos = getPos(ev);
+
+ if (mLongPressListener != null) {
+ if (mWatchLongPress == null) {
+ mWatchLongPress = new Runnable() {
+ @Override
+ public void run() {
+ if (mCurrView != null && !mLongPressSent) {
+ mLongPressSent = true;
+ mCurrView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+ mLongPressListener.onLongClick(mCurrView);
+ }
+ }
+ };
+ }
+ mHandler.postDelayed(mWatchLongPress, ViewConfiguration.getLongPressTimeout());
+ }
+
}
break;
+
case MotionEvent.ACTION_MOVE:
- if (mCurrView != null) {
+ if (mCurrView != null && !mLongPressSent) {
mVelocityTracker.addMovement(ev);
float pos = getPos(ev);
float delta = pos - mInitialTouchPos;
@@ -191,14 +229,19 @@ public class SwipeHelper implements Gefingerpoken {
mCallback.onBeginDrag(mCurrView);
mDragging = true;
mInitialTouchPos = getPos(ev) - getTranslation(mCurrAnimView);
+
+ removeLongPressCallback();
}
}
+
break;
+
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mDragging = false;
mCurrView = null;
mCurrAnimView = null;
+ mLongPressSent = false;
break;
}
return mDragging;
@@ -269,6 +312,10 @@ public class SwipeHelper implements Gefingerpoken {
}
public boolean onTouchEvent(MotionEvent ev) {
+ if (mLongPressSent) {
+ return true;
+ }
+
if (!mDragging) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index ede8e7a..3803092 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -26,17 +26,20 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
import android.view.Display;
import android.view.IWindowManager;
import android.view.LayoutInflater;
+import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -45,6 +48,7 @@ import android.view.WindowManager;
import android.view.WindowManagerImpl;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
+import android.widget.PopupMenu;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
@@ -214,6 +218,39 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
+ private void startApplicationDetailsActivity(String packageName) {
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ Uri.fromParts("package", packageName, null));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
+ protected View.OnLongClickListener getNotificationLongClicker() {
+ return new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ final String packageNameF = (String) v.getTag();
+ if (packageNameF == null) return false;
+ PopupMenu popup = new PopupMenu(mContext, v);
+ popup.getMenuInflater().inflate(R.menu.notification_popup_menu, popup.getMenu());
+ popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ if (item.getItemId() == R.id.notification_inspect_item) {
+ startApplicationDetailsActivity(packageNameF);
+ animateCollapse();
+ } else {
+ return false;
+ }
+ return true;
+ }
+ });
+ popup.show();
+
+ return true;
+ }
+ };
+ }
+
public void dismissIntruder() {
// pass
}
@@ -354,9 +391,13 @@ public abstract class BaseStatusBar extends SystemUI implements
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
+
+ // for blaming (see SwipeHelper.setLongPressListener)
+ row.setTag(sbn.pkg);
+
// XXX: temporary: while testing big notifications, auto-expand all of them
ViewGroup.LayoutParams lp = row.getLayoutParams();
- if (sbn.notification.bigContentView != null) {
+ if (large != null) {
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
} else {
lp.height = minHeight;
@@ -372,9 +413,16 @@ public abstract class BaseStatusBar extends SystemUI implements
// bind the click event to the content area
ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive);
- // XXX: update to allow controls within notification views
+
+ // Ensure that R.id.content is properly set to 64dp high if 1U
+ lp = content.getLayoutParams();
+ if (large == null) {
+ lp.height = minHeight;
+ }
+ content.setLayoutParams(lp);
+
content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-// content.setOnFocusChangeListener(mFocusChangeListener);
+
PendingIntent contentIntent = sbn.notification.contentIntent;
if (contentIntent != null) {
final View.OnClickListener listener = new NotificationClicker(contentIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index f45b3ad..da98c80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -94,7 +94,7 @@ public class PhoneStatusBar extends BaseStatusBar {
public static final String ACTION_STATUSBAR_START
= "com.android.internal.policy.statusbar.START";
- private static final boolean ENABLE_INTRUDERS = true;
+ private static final boolean ENABLE_INTRUDERS = false;
static final int EXPANDED_LEAVE_ALONE = -10000;
static final int EXPANDED_FULL_OPEN = -10001;
@@ -218,11 +218,6 @@ public class PhoneStatusBar extends BaseStatusBar {
private int mNavigationIconHints = 0;
- // TODO(dsandler): codify this stuff in NotificationManager's header somewhere
- private int mDisplayMinScore = Notification.PRIORITY_LOW * 10;
- private int mIntruderMinScore = Notification.PRIORITY_HIGH * 10;
- private int mIntruderInImmersiveMinScore = Notification.PRIORITY_HIGH * 10 + 5;
-
private class ExpandedDialog extends Dialog {
ExpandedDialog(Context context) {
super(context, com.android.internal.R.style.Theme_Translucent_NoTitleBar);
@@ -280,9 +275,11 @@ public class PhoneStatusBar extends BaseStatusBar {
}
mNotificationPanel = expanded.findViewById(R.id.notification_panel);
- mIntruderAlertView = (IntruderAlertView) View.inflate(context, R.layout.intruder_alert, null);
- mIntruderAlertView.setVisibility(View.GONE);
- mIntruderAlertView.setBar(this);
+ if (ENABLE_INTRUDERS) {
+ mIntruderAlertView = (IntruderAlertView) View.inflate(context, R.layout.intruder_alert, null);
+ mIntruderAlertView.setVisibility(View.GONE);
+ mIntruderAlertView.setBar(this);
+ }
PhoneStatusBarView sb = (PhoneStatusBarView)View.inflate(context,
R.layout.status_bar, null);
@@ -312,6 +309,7 @@ public class PhoneStatusBar extends BaseStatusBar {
mExpandedDialog = new ExpandedDialog(context);
mPile = (NotificationRowLayout)expanded.findViewById(R.id.latestItems);
+ mPile.setLongPressListener(getNotificationLongClicker());
mExpandedContents = mPile; // was: expanded.findViewById(R.id.notificationLinearLayout);
mClearButton = expanded.findViewById(R.id.clear_all_button);
@@ -520,12 +518,12 @@ public class PhoneStatusBar extends BaseStatusBar {
}
} catch (RemoteException ex) {
}
+
+ /*
+ * DISABLED due to missing API
if (ENABLE_INTRUDERS && (
// TODO(dsandler): Only if the screen is on
notification.notification.intruderView != null)) {
-// notification.notification.fullScreenIntent != null
-// || (notification.score >= mIntruderInImmersiveMinScore)
-// || (!immersive && (notification.score > mIntruderMinScore)))) {
Slog.d(TAG, "Presenting high-priority notification");
// special new transient ticker mode
// 1. Populate mIntruderAlertView
@@ -554,7 +552,10 @@ public class PhoneStatusBar extends BaseStatusBar {
if (INTRUDER_ALERT_DECAY_MS > 0) {
mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS);
}
- } else if (notification.notification.fullScreenIntent != null) {
+ } else
+ */
+
+ if (notification.notification.fullScreenIntent != null) {
// not immersive & a full-screen alert should be shown
Slog.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
try {
@@ -675,7 +676,7 @@ public class PhoneStatusBar extends BaseStatusBar {
updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
// See if we need to update the intruder.
- if (oldNotification == mCurrentlyIntrudingNotification) {
+ if (ENABLE_INTRUDERS && oldNotification == mCurrentlyIntrudingNotification) {
if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification);
// XXX: this is a hack for Alarms. The real implementation will need to *update*
// the intruder.
@@ -697,7 +698,7 @@ public class PhoneStatusBar extends BaseStatusBar {
// Recalculate the position of the sliding windows and the titles.
updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
- if (old == mCurrentlyIntrudingNotification) {
+ if (ENABLE_INTRUDERS && old == mCurrentlyIntrudingNotification) {
mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
}
@@ -2039,6 +2040,7 @@ public class PhoneStatusBar extends BaseStatusBar {
};
private void setIntruderAlertVisibility(boolean vis) {
+ if (!ENABLE_INTRUDERS) return;
if (DEBUG) {
Slog.v(TAG, (vis ? "showing" : "hiding") + " intruder alert window");
}
@@ -2125,30 +2127,6 @@ public class PhoneStatusBar extends BaseStatusBar {
vib.vibrate(250);
}
- public int getScoreThreshold() {
- return mDisplayMinScore;
- }
-
- public void setScoreThreshold(int score) {
- // XXX HAX
- if (mDisplayMinScore != score) {
- this.mDisplayMinScore = score;
- applyScoreThreshold();
- }
- }
-
- private void applyScoreThreshold() {
- int N = mNotificationData.size();
- for (int i=0; i<N; i++) {
- NotificationData.Entry entry = mNotificationData.get(i);
- int vis = (entry.notification.score < mDisplayMinScore)
- ? View.GONE
- : View.VISIBLE;
- entry.row.setVisibility(vis);
- entry.icon.setVisibility(vis);
- }
- }
-
Runnable mStartTracing = new Runnable() {
public void run() {
vibrate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
index 5369317..5c38db5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
@@ -104,6 +104,10 @@ public class NotificationRowLayout
mExpandHelper = new ExpandHelper(mContext, this, minHeight, maxHeight);
}
+ public void setLongPressListener(View.OnLongClickListener listener) {
+ mSwipeHelper.setLongPressListener(listener);
+ }
+
public void setAnimateBounds(boolean anim) {
mAnimateBounds = anim;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index 8c1509b..c868f78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -74,6 +74,7 @@ import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CompatModeButton;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NotificationRowLayout;
import com.android.systemui.statusbar.policy.Prefs;
import java.io.FileDescriptor;
@@ -153,7 +154,7 @@ public class TabletStatusBar extends BaseStatusBar implements
int mNotificationPeekTapDuration;
int mNotificationFlingVelocity;
- ViewGroup mPile;
+ NotificationRowLayout mPile;
BatteryController mBatteryController;
BluetoothController mBluetoothController;
@@ -375,8 +376,9 @@ public class TabletStatusBar extends BaseStatusBar implements
mRecentButton.setOnTouchListener(mRecentsPanel);
- mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
+ mPile = (NotificationRowLayout)mNotificationPanel.findViewById(R.id.content);
mPile.removeAllViews();
+ mPile.setLongPressListener(getNotificationLongClicker());
ScrollView scroller = (ScrollView)mPile.getParent();
scroller.setFillViewport(true);
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index b22be76..1ba7e79 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -16,7 +16,9 @@
package com.android.server;
+import com.android.internal.os.AtomicFile;
import com.android.internal.statusbar.StatusBarNotification;
+import com.android.internal.util.FastXmlSerializer;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
@@ -37,9 +39,10 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioManager;
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
import android.net.Uri;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -53,14 +56,36 @@ import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
+import android.util.Xml;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
+import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeBooleanAttribute;
+import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeIntAttribute;
+import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeLongAttribute;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
/** {@hide} */
public class NotificationManagerService extends INotificationManager.Stub
@@ -81,6 +106,13 @@ public class NotificationManagerService extends INotificationManager.Stub
private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
private static final boolean SCORE_ONGOING_HIGHER = false;
+ private static final int JUNK_SCORE = -1000;
+ private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
+ private static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER;
+
+ private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
+ private static final boolean ENABLE_BLOCKED_TOASTS = true;
+
final Context mContext;
final IActivityManager mAm;
final IBinder mForegroundToken = new Binder();
@@ -115,6 +147,144 @@ public class NotificationManagerService extends INotificationManager.Stub
private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
private NotificationRecord mLedNotification;
+ // Notification control database. For now just contains disabled packages.
+ private AtomicFile mPolicyFile;
+ private HashSet<String> mBlockedPackages = new HashSet<String>();
+
+ private static final int DB_VERSION = 1;
+
+ private static final String TAG_BODY = "notification-policy";
+ private static final String ATTR_VERSION = "version";
+
+ private static final String TAG_BLOCKED_PKGS = "blocked-packages";
+ private static final String TAG_PACKAGE = "package";
+ private static final String ATTR_NAME = "name";
+
+ private void loadBlockDb() {
+ synchronized(mBlockedPackages) {
+ if (mPolicyFile == null) {
+ File dir = new File("/data/system");
+ mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml"));
+
+ mBlockedPackages.clear();
+
+ FileInputStream infile = null;
+ try {
+ infile = mPolicyFile.openRead();
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(infile, null);
+
+ int type;
+ String tag;
+ int version = DB_VERSION;
+ while ((type = parser.next()) != END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == START_TAG) {
+ if (TAG_BODY.equals(tag)) {
+ version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
+ } else if (TAG_BLOCKED_PKGS.equals(tag)) {
+ while ((type = parser.next()) != END_DOCUMENT) {
+ tag = parser.getName();
+ if (TAG_PACKAGE.equals(tag)) {
+ mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
+ } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // No data yet
+ } catch (IOException e) {
+ Log.wtf(TAG, "Unable to read blocked notifications database", e);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Unable to parse blocked notifications database", e);
+ } catch (XmlPullParserException e) {
+ Log.wtf(TAG, "Unable to parse blocked notifications database", e);
+ } finally {
+ IoUtils.closeQuietly(infile);
+ }
+ }
+ }
+ }
+
+ private void writeBlockDb() {
+ synchronized(mBlockedPackages) {
+ FileOutputStream outfile = null;
+ try {
+ outfile = mPolicyFile.startWrite();
+
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(outfile, "utf-8");
+
+ out.startDocument(null, true);
+
+ out.startTag(null, TAG_BODY); {
+ out.attribute(null, ATTR_VERSION, String.valueOf(DB_VERSION));
+ out.startTag(null, TAG_BLOCKED_PKGS); {
+ // write all known network policies
+ for (String pkg : mBlockedPackages) {
+ out.startTag(null, TAG_PACKAGE); {
+ out.attribute(null, ATTR_NAME, pkg);
+ } out.endTag(null, TAG_PACKAGE);
+ }
+ } out.endTag(null, TAG_BLOCKED_PKGS);
+ } out.endTag(null, TAG_BODY);
+
+ out.endDocument();
+
+ mPolicyFile.finishWrite(outfile);
+ } catch (IOException e) {
+ if (outfile != null) {
+ mPolicyFile.failWrite(outfile);
+ }
+ }
+ }
+ }
+
+ public boolean areNotificationsEnabledForPackage(String pkg) {
+ checkCallerIsSystem();
+ return areNotificationsEnabledForPackageInt(pkg);
+ }
+
+ // Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
+ private boolean areNotificationsEnabledForPackageInt(String pkg) {
+ final boolean enabled = !mBlockedPackages.contains(pkg);
+ if (DBG) {
+ Slog.v(TAG, "notifications are " + (enabled?"en":"dis") + "abled for " + pkg);
+ }
+ return enabled;
+ }
+
+ public void setNotificationsEnabledForPackage(String pkg, boolean enabled) {
+ checkCallerIsSystem();
+ if (DBG) {
+ Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
+ }
+ if (enabled) {
+ mBlockedPackages.remove(pkg);
+ } else {
+ mBlockedPackages.add(pkg);
+
+ // Now, cancel any outstanding notifications that are part of a just-disabled app
+ if (ENABLE_BLOCKED_NOTIFICATIONS) {
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ for (int i=0; i<N; i++) {
+ final NotificationRecord r = mNotificationList.get(i);
+ if (r.pkg.equals(pkg)) {
+ cancelNotificationLocked(r, false);
+ }
+ }
+ }
+ }
+ // Don't bother canceling toasts, they'll go away soon enough.
+ }
+ writeBlockDb();
+ }
+
+
private static String idDebugString(Context baseContext, String packageName, int id) {
Context c = null;
@@ -405,6 +575,8 @@ public class NotificationManagerService extends INotificationManager.Stub
mToastQueue = new ArrayList<ToastRecord>();
mHandler = new WorkerHandler();
+ loadBlockDb();
+
mStatusBar = statusBar;
statusBar.setNotificationCallbacks(mNotificationCallbacks);
@@ -465,6 +637,13 @@ public class NotificationManagerService extends INotificationManager.Stub
return ;
}
+ final boolean isSystemToast = ("android".equals(pkg));
+
+ if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) {
+ Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
+ return;
+ }
+
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
@@ -479,7 +658,7 @@ public class NotificationManagerService extends INotificationManager.Stub
} else {
// Limit the number of toasts that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
- if (!"android".equals(pkg)) {
+ if (!isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
@@ -675,11 +854,15 @@ public class NotificationManagerService extends INotificationManager.Stub
public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int[] idOut)
{
- checkIncomingCall(pkg);
+ if (DBG) {
+ Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
+ }
+ checkCallerIsSystemOrSameApp(pkg);
+ final boolean isSystemNotification = ("android".equals(pkg));
// Limit the number of notifications that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
- if (!"android".equals(pkg)) {
+ if (!isSystemNotification) {
synchronized (mNotificationList) {
int count = 0;
final int N = mNotificationList.size();
@@ -717,7 +900,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
// === Scoring ===
-
+
// 0. Sanitize inputs
notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX);
// Migrate notification flags to scores
@@ -726,19 +909,27 @@ public class NotificationManagerService extends INotificationManager.Stub
} else if (SCORE_ONGOING_HIGHER && 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) {
if (notification.priority < Notification.PRIORITY_HIGH) notification.priority = Notification.PRIORITY_HIGH;
}
-
+
// 1. initial score: buckets of 10, around the app
- int score = notification.priority * 10; //[-20..20]
+ int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]
- // 2. Consult oracles (external heuristics)
- // TODO(dsandler): oracles
+ // 2. Consult external heuristics (TBD)
- // 3. Apply local heuristics & overrides
+ // 3. Apply local rules
// blocked apps
- // TODO(dsandler): add block db
- if (pkg.startsWith("com.test.spammer.")) {
- score = -1000;
+ if (ENABLE_BLOCKED_NOTIFICATIONS && !isSystemNotification && !areNotificationsEnabledForPackageInt(pkg)) {
+ score = JUNK_SCORE;
+ Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request.");
+ }
+
+ if (DBG) {
+ Slog.v(TAG, "Assigned score=" + score + " to " + notification);
+ }
+
+ if (score < SCORE_DISPLAY_THRESHOLD) {
+ // Notification will be blocked because the score is too low.
+ return;
}
synchronized (mNotificationList) {
@@ -1030,7 +1221,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
public void cancelNotificationWithTag(String pkg, String tag, int id) {
- checkIncomingCall(pkg);
+ checkCallerIsSystemOrSameApp(pkg);
// Don't allow client applications to cancel foreground service notis.
cancelNotification(pkg, tag, id, 0,
Binder.getCallingUid() == Process.SYSTEM_UID
@@ -1038,14 +1229,22 @@ public class NotificationManagerService extends INotificationManager.Stub
}
public void cancelAllNotifications(String pkg) {
- checkIncomingCall(pkg);
+ checkCallerIsSystemOrSameApp(pkg);
// Calling from user space, don't allow the canceling of actively
// running foreground services.
cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true);
}
- void checkIncomingCall(String pkg) {
+ void checkCallerIsSystem() {
+ int uid = Binder.getCallingUid();
+ if (uid == Process.SYSTEM_UID || uid == 0) {
+ return;
+ }
+ throw new SecurityException("Disallowed call for uid " + uid);
+ }
+
+ void checkCallerIsSystemOrSameApp(String pkg) {
int uid = Binder.getCallingUid();
if (uid == Process.SYSTEM_UID || uid == 0) {
return;
diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java
index 8014e27..839fbe2 100644
--- a/services/java/com/android/server/NsdService.java
+++ b/services/java/com/android/server/NsdService.java
@@ -17,6 +17,8 @@
package com.android.server;
import android.content.Context;
+import android.content.ContentResolver;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.nsd.DnsSdServiceInfo;
import android.net.nsd.DnsSdTxtRecord;
@@ -28,6 +30,7 @@ import android.os.HandlerThread;
import android.os.Message;
import android.os.Messenger;
import android.os.IBinder;
+import android.provider.Settings;
import android.util.Slog;
import java.io.FileDescriptor;
@@ -41,6 +44,9 @@ import java.util.concurrent.CountDownLatch;
import com.android.internal.app.IBatteryStats;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
import com.android.server.am.BatteryStatsService;
import com.android.server.NativeDaemonConnector.Command;
import com.android.internal.R;
@@ -58,6 +64,8 @@ public class NsdService extends INsdManager.Stub {
private static final boolean DBG = true;
private Context mContext;
+ private ContentResolver mContentResolver;
+ private NsdStateMachine mNsdStateMachine;
/**
* Clients receiving asynchronous messages
@@ -69,189 +77,342 @@ public class NsdService extends INsdManager.Stub {
private int INVALID_ID = 0;
private int mUniqueId = 1;
- /**
- * Handles client(app) connections
- */
- private class AsyncServiceHandler extends Handler {
+ private static final int BASE = Protocol.BASE_NSD_MANAGER;
+ private static final int CMD_TO_STRING_COUNT = NsdManager.STOP_RESOLVE - BASE + 1;
+ private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
+
+ static {
+ sCmdToString[NsdManager.DISCOVER_SERVICES - BASE] = "DISCOVER";
+ sCmdToString[NsdManager.STOP_DISCOVERY - BASE] = "STOP-DISCOVER";
+ sCmdToString[NsdManager.REGISTER_SERVICE - BASE] = "REGISTER";
+ sCmdToString[NsdManager.UNREGISTER_SERVICE - BASE] = "UNREGISTER";
+ sCmdToString[NsdManager.RESOLVE_SERVICE - BASE] = "RESOLVE";
+ sCmdToString[NsdManager.STOP_RESOLVE - BASE] = "STOP-RESOLVE";
+ }
- AsyncServiceHandler(android.os.Looper looper) {
- super(looper);
+ private static String cmdToString(int cmd) {
+ cmd -= BASE;
+ if ((cmd >= 0) && (cmd < sCmdToString.length)) {
+ return sCmdToString[cmd];
+ } else {
+ return null;
}
+ }
+
+ private class NsdStateMachine extends StateMachine {
+
+ private DefaultState mDefaultState = new DefaultState();
+ private DisabledState mDisabledState = new DisabledState();
+ private EnabledState mEnabledState = new EnabledState();
@Override
- public void handleMessage(Message msg) {
- ClientInfo clientInfo;
- DnsSdServiceInfo servInfo;
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
- if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
- AsyncChannel c = (AsyncChannel) msg.obj;
- if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
- c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
- ClientInfo cInfo = new ClientInfo(c, msg.replyTo);
- if (mClients.size() == 0) {
- startMDnsDaemon();
+ protected String getMessageInfo(Message msg) {
+ return cmdToString(msg.what);
+ }
+
+ NsdStateMachine(String name) {
+ super(name);
+ addState(mDefaultState);
+ addState(mDisabledState, mDefaultState);
+ addState(mEnabledState, mDefaultState);
+ if (isNsdEnabled()) {
+ setInitialState(mEnabledState);
+ } else {
+ setInitialState(mDisabledState);
+ }
+ setProcessedMessagesSize(25);
+ }
+
+ class DefaultState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ AsyncChannel c = (AsyncChannel) msg.obj;
+ if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
+ c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
+ ClientInfo cInfo = new ClientInfo(c, msg.replyTo);
+ mClients.put(msg.replyTo, cInfo);
+ } else {
+ Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
}
- mClients.put(msg.replyTo, cInfo);
- } else {
- Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
- }
- break;
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
- if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
- Slog.e(TAG, "Send failed, client connection lost");
- } else {
- if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
- }
- mClients.remove(msg.replyTo);
- if (mClients.size() == 0) {
- stopMDnsDaemon();
- }
- break;
- case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
- AsyncChannel ac = new AsyncChannel();
- ac.connect(mContext, this, msg.replyTo);
- break;
- case NsdManager.DISCOVER_SERVICES:
- if (DBG) Slog.d(TAG, "Discover services");
- servInfo = (DnsSdServiceInfo) msg.obj;
- clientInfo = mClients.get(msg.replyTo);
- if (clientInfo.mDiscoveryId != INVALID_ID) {
- //discovery already in progress
- if (DBG) Slog.d(TAG, "discovery in progress");
- mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
- NsdManager.ALREADY_ACTIVE);
break;
- }
- clientInfo.mDiscoveryId = getUniqueId();
- if (discoverServices(clientInfo.mDiscoveryId, servInfo.getServiceType())) {
- mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED);
- } else {
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
+ Slog.e(TAG, "Send failed, client connection lost");
+ } else {
+ if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
+ }
+ mClients.remove(msg.replyTo);
+ break;
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
+ AsyncChannel ac = new AsyncChannel();
+ ac.connect(mContext, getHandler(), msg.replyTo);
+ break;
+ case NsdManager.DISCOVER_SERVICES:
mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
- NsdManager.ERROR);
- clientInfo.mDiscoveryId = INVALID_ID;
- }
- break;
- case NsdManager.STOP_DISCOVERY:
- if (DBG) Slog.d(TAG, "Stop service discovery");
- clientInfo = mClients.get(msg.replyTo);
- if (clientInfo.mDiscoveryId == INVALID_ID) {
- //already stopped
- if (DBG) Slog.d(TAG, "discovery already stopped");
- mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
- NsdManager.ALREADY_ACTIVE);
+ NsdManager.BUSY);
+ break;
+ case NsdManager.STOP_DISCOVERY:
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
+ NsdManager.ERROR);
break;
- }
- if (stopServiceDiscovery(clientInfo.mDiscoveryId)) {
- clientInfo.mDiscoveryId = INVALID_ID;
- mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
- } else {
- mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
- NsdManager.ERROR);
- }
- break;
- case NsdManager.REGISTER_SERVICE:
- if (DBG) Slog.d(TAG, "Register service");
- clientInfo = mClients.get(msg.replyTo);
- if (clientInfo.mRegisteredIds.size() >= ClientInfo.MAX_REG) {
- if (DBG) Slog.d(TAG, "register service exceeds limit");
- mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
- NsdManager.MAX_REGS_REACHED);
- }
-
- int id = getUniqueId();
- if (registerService(id, (DnsSdServiceInfo) msg.obj)) {
- clientInfo.mRegisteredIds.add(id);
- } else {
+ case NsdManager.REGISTER_SERVICE:
mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
NsdManager.ERROR);
- }
- break;
- case NsdManager.UNREGISTER_SERVICE:
- if (DBG) Slog.d(TAG, "unregister service");
- clientInfo = mClients.get(msg.replyTo);
- int regId = msg.arg1;
- if (clientInfo.mRegisteredIds.remove(new Integer(regId)) &&
- unregisterService(regId)) {
- mReplyChannel.replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED);
- } else {
+ break;
+ case NsdManager.UNREGISTER_SERVICE:
mReplyChannel.replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
NsdManager.ERROR);
- }
- break;
- case NsdManager.UPDATE_SERVICE:
- if (DBG) Slog.d(TAG, "Update service");
- //TODO: implement
- mReplyChannel.replyToMessage(msg, NsdManager.UPDATE_SERVICE_FAILED);
- break;
- case NsdManager.RESOLVE_SERVICE:
- if (DBG) Slog.d(TAG, "Resolve service");
- servInfo = (DnsSdServiceInfo) msg.obj;
- clientInfo = mClients.get(msg.replyTo);
- if (clientInfo.mResolveId != INVALID_ID) {
- //first cancel existing resolve
- stopResolveService(clientInfo.mResolveId);
- }
-
- clientInfo.mResolveId = getUniqueId();
- if (!resolveService(clientInfo.mResolveId, servInfo)) {
+ break;
+ case NsdManager.RESOLVE_SERVICE:
mReplyChannel.replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
NsdManager.ERROR);
- clientInfo.mResolveId = INVALID_ID;
- }
- break;
- case NsdManager.STOP_RESOLVE:
- if (DBG) Slog.d(TAG, "Stop resolve");
- clientInfo = mClients.get(msg.replyTo);
- if (clientInfo.mResolveId == INVALID_ID) {
- //already stopped
- if (DBG) Slog.d(TAG, "resolve already stopped");
- mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED,
- NsdManager.ALREADY_ACTIVE);
break;
- }
- if (stopResolveService(clientInfo.mResolveId)) {
- clientInfo.mResolveId = INVALID_ID;
- mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_SUCCEEDED);
- } else {
+ case NsdManager.STOP_RESOLVE:
mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED,
NsdManager.ERROR);
- }
- break;
- default:
- Slog.d(TAG, "NsdServicehandler.handleMessage ignoring msg=" + msg);
- break;
+ break;
+ default:
+ Slog.e(TAG, "Unhandled " + msg);
+ return NOT_HANDLED;
+ }
+ return HANDLED;
}
}
+
+ class DisabledState extends State {
+ @Override
+ public void enter() {
+ sendNsdStateChangeBroadcast(false);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case NsdManager.ENABLE:
+ transitionTo(mEnabledState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class EnabledState extends State {
+ @Override
+ public void enter() {
+ sendNsdStateChangeBroadcast(true);
+ if (mClients.size() > 0) {
+ startMDnsDaemon();
+ }
+ }
+
+ @Override
+ public void exit() {
+ if (mClients.size() > 0) {
+ stopMDnsDaemon();
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ ClientInfo clientInfo;
+ DnsSdServiceInfo servInfo;
+ boolean result = HANDLED;
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ //First client
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL &&
+ mClients.size() == 0) {
+ startMDnsDaemon();
+ }
+ result = NOT_HANDLED;
+ break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ //Last client
+ if (mClients.size() == 1) {
+ stopMDnsDaemon();
+ }
+ result = NOT_HANDLED;
+ break;
+ case NsdManager.DISABLE:
+ //TODO: cleanup clients
+ transitionTo(mDisabledState);
+ break;
+ case NsdManager.DISCOVER_SERVICES:
+ if (DBG) Slog.d(TAG, "Discover services");
+ servInfo = (DnsSdServiceInfo) msg.obj;
+ clientInfo = mClients.get(msg.replyTo);
+ if (clientInfo.mDiscoveryId != INVALID_ID) {
+ //discovery already in progress
+ if (DBG) Slog.d(TAG, "discovery in progress");
+ mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
+ NsdManager.ALREADY_ACTIVE);
+ break;
+ }
+ clientInfo.mDiscoveryId = getUniqueId();
+ if (discoverServices(clientInfo.mDiscoveryId, servInfo.getServiceType())) {
+ mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED);
+ } else {
+ mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
+ NsdManager.ERROR);
+ clientInfo.mDiscoveryId = INVALID_ID;
+ }
+ break;
+ case NsdManager.STOP_DISCOVERY:
+ if (DBG) Slog.d(TAG, "Stop service discovery");
+ clientInfo = mClients.get(msg.replyTo);
+ if (clientInfo.mDiscoveryId == INVALID_ID) {
+ //already stopped
+ if (DBG) Slog.d(TAG, "discovery already stopped");
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
+ NsdManager.ALREADY_ACTIVE);
+ break;
+ }
+ if (stopServiceDiscovery(clientInfo.mDiscoveryId)) {
+ clientInfo.mDiscoveryId = INVALID_ID;
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
+ } else {
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
+ NsdManager.ERROR);
+ }
+ break;
+ case NsdManager.REGISTER_SERVICE:
+ if (DBG) Slog.d(TAG, "Register service");
+ clientInfo = mClients.get(msg.replyTo);
+ if (clientInfo.mRegisteredIds.size() >= ClientInfo.MAX_REG) {
+ if (DBG) Slog.d(TAG, "register service exceeds limit");
+ mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
+ NsdManager.MAX_REGS_REACHED);
+ }
+
+ int id = getUniqueId();
+ if (registerService(id, (DnsSdServiceInfo) msg.obj)) {
+ clientInfo.mRegisteredIds.add(id);
+ } else {
+ mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
+ NsdManager.ERROR);
+ }
+ break;
+ case NsdManager.UNREGISTER_SERVICE:
+ if (DBG) Slog.d(TAG, "unregister service");
+ clientInfo = mClients.get(msg.replyTo);
+ int regId = msg.arg1;
+ if (clientInfo.mRegisteredIds.remove(new Integer(regId)) &&
+ unregisterService(regId)) {
+ mReplyChannel.replyToMessage(msg,
+ NsdManager.UNREGISTER_SERVICE_SUCCEEDED);
+ } else {
+ mReplyChannel.replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
+ NsdManager.ERROR);
+ }
+ break;
+ case NsdManager.UPDATE_SERVICE:
+ if (DBG) Slog.d(TAG, "Update service");
+ //TODO: implement
+ mReplyChannel.replyToMessage(msg, NsdManager.UPDATE_SERVICE_FAILED);
+ break;
+ case NsdManager.RESOLVE_SERVICE:
+ if (DBG) Slog.d(TAG, "Resolve service");
+ servInfo = (DnsSdServiceInfo) msg.obj;
+ clientInfo = mClients.get(msg.replyTo);
+ if (clientInfo.mResolveId != INVALID_ID) {
+ //first cancel existing resolve
+ stopResolveService(clientInfo.mResolveId);
+ }
+
+ clientInfo.mResolveId = getUniqueId();
+ if (!resolveService(clientInfo.mResolveId, servInfo)) {
+ mReplyChannel.replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
+ NsdManager.ERROR);
+ clientInfo.mResolveId = INVALID_ID;
+ }
+ break;
+ case NsdManager.STOP_RESOLVE:
+ if (DBG) Slog.d(TAG, "Stop resolve");
+ clientInfo = mClients.get(msg.replyTo);
+ if (clientInfo.mResolveId == INVALID_ID) {
+ //already stopped
+ if (DBG) Slog.d(TAG, "resolve already stopped");
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED,
+ NsdManager.ALREADY_ACTIVE);
+ break;
+ }
+ if (stopResolveService(clientInfo.mResolveId)) {
+ clientInfo.mResolveId = INVALID_ID;
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_SUCCEEDED);
+ } else {
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED,
+ NsdManager.ERROR);
+ }
+ break;
+ default:
+ result = NOT_HANDLED;
+ break;
+ }
+ return result;
+ }
+ }
}
- private AsyncServiceHandler mAsyncServiceHandler;
private NativeDaemonConnector mNativeConnector;
private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1);
private NsdService(Context context) {
mContext = context;
-
- HandlerThread nsdThread = new HandlerThread("NsdService");
- nsdThread.start();
- mAsyncServiceHandler = new AsyncServiceHandler(nsdThread.getLooper());
+ mContentResolver = context.getContentResolver();
mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
MDNS_TAG, 25);
+
+ mNsdStateMachine = new NsdStateMachine(TAG);
+ mNsdStateMachine.start();
+
Thread th = new Thread(mNativeConnector, MDNS_TAG);
th.start();
}
public static NsdService create(Context context) throws InterruptedException {
NsdService service = new NsdService(context);
- /* service.mNativeDaemonConnected.await(); */
+ service.mNativeDaemonConnected.await();
return service;
}
public Messenger getMessenger() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET,
"NsdService");
- return new Messenger(mAsyncServiceHandler);
+ return new Messenger(mNsdStateMachine.getHandler());
+ }
+
+ public void setEnabled(boolean enable) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
+ "NsdService");
+ Settings.Secure.putInt(mContentResolver, Settings.Secure.NSD_ON, enable ? 1 : 0);
+ if (enable) {
+ mNsdStateMachine.sendMessage(NsdManager.ENABLE);
+ } else {
+ mNsdStateMachine.sendMessage(NsdManager.DISABLE);
+ }
+ }
+
+ private void sendNsdStateChangeBroadcast(boolean enabled) {
+ final Intent intent = new Intent(NsdManager.NSD_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ if (enabled) {
+ intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_ENABLED);
+ } else {
+ intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_DISABLED);
+ }
+ mContext.sendStickyBroadcast(intent);
+ }
+
+ private boolean isNsdEnabled() {
+ boolean ret = Settings.Secure.getInt(mContentResolver, Settings.Secure.NSD_ON, 1) == 1;
+ if (DBG) Slog.d(TAG, "Network service discovery enabled " + ret);
+ return ret;
}
private int getUniqueId() {
@@ -522,7 +683,7 @@ public class NsdService extends INsdManager.Stub {
}
@Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump ServiceDiscoverService from from pid="
@@ -531,7 +692,12 @@ public class NsdService extends INsdManager.Stub {
return;
}
- pw.println("Internal state:");
+ for (ClientInfo client : mClients.values()) {
+ pw.println("Client Info");
+ pw.println(client);
+ }
+
+ mNsdStateMachine.dump(fd, pw, args);
}
private ClientInfo getClientByDiscovery(int discoveryId) {
@@ -579,5 +745,19 @@ public class NsdService extends INsdManager.Stub {
mDiscoveryId = mResolveId = INVALID_ID;
if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
}
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("mChannel ").append(mChannel).append("\n");
+ sb.append("mMessenger ").append(mMessenger).append("\n");
+ sb.append("mDiscoveryId ").append(mDiscoveryId).append("\n");
+ sb.append("mResolveId ").append(mResolveId).append("\n");
+ sb.append("mResolvedService ").append(mResolvedService).append("\n");
+ for(int regId : mRegisteredIds) {
+ sb.append("regId ").append(regId).append("\n");
+ }
+ return sb.toString();
+ }
}
}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 7aee8d2..b4a458f 100644..100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -3557,17 +3557,7 @@ public class WindowManagerService extends IWindowManager.Stub
continue;
}
- if (!haveGroup) {
- // We ignore any hidden applications on the top.
- if (wtoken.hiddenRequested || wtoken.willBeHidden) {
- if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + wtoken
- + " -- hidden on top");
- continue;
- }
- haveGroup = true;
- curGroup = wtoken.groupId;
- lastOrientation = wtoken.requestedOrientation;
- } else if (curGroup != wtoken.groupId) {
+ if (haveGroup == true && curGroup != wtoken.groupId) {
// If we have hit a new application group, and the bottom
// of the previous group didn't explicitly say to use
// the orientation behind it, and the last app was
@@ -3580,6 +3570,20 @@ public class WindowManagerService extends IWindowManager.Stub
return lastOrientation;
}
}
+
+ // We ignore any hidden applications on the top.
+ if (wtoken.hiddenRequested || wtoken.willBeHidden) {
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + wtoken
+ + " -- hidden on top");
+ continue;
+ }
+
+ if (!haveGroup) {
+ haveGroup = true;
+ curGroup = wtoken.groupId;
+ lastOrientation = wtoken.requestedOrientation;
+ }
+
int or = wtoken.requestedOrientation;
// If this application is fullscreen, and didn't explicitly say
// to use the orientation behind it, then just take whatever
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index a124c7f..da03f76 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -209,7 +209,7 @@ public abstract class DataConnection extends StateMachine {
protected static final int EVENT_RIL_CONNECTED = BASE + 5;
protected static final int EVENT_DISCONNECT_ALL = BASE + 6;
- private static final int CMD_TO_STRING_COUNT = EVENT_DISCONNECT_ALL + 1;
+ private static final int CMD_TO_STRING_COUNT = EVENT_DISCONNECT_ALL - BASE + 1;
private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
static {
sCmdToString[EVENT_CONNECT - BASE] = "EVENT_CONNECT";
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionAc.java b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
index 4744ff0..96419ae 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionAc.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
@@ -82,7 +82,7 @@ public class DataConnectionAc extends AsyncChannel {
public static final int REQ_GET_RECONNECT_INTENT = BASE + 26;
public static final int RSP_GET_RECONNECT_INTENT = BASE + 27;
- private static final int CMD_TO_STRING_COUNT = RSP_GET_RECONNECT_INTENT + 1;
+ private static final int CMD_TO_STRING_COUNT = RSP_GET_RECONNECT_INTENT - BASE + 1;
private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
static {
sCmdToString[REQ_IS_INACTIVE - BASE] = "REQ_IS_INACTIVE";
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 5eac1f2..a90af15 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -24,6 +24,7 @@ import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
import android.os.Environment;
import android.os.Vibrator;
import android.os.Handler;
@@ -44,10 +45,10 @@ public class NotificationTestList extends TestActivity
private final static String TAG = "NotificationTestList";
NotificationManager mNM;
- Vibrator mVibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE);
+ Vibrator mVibrator;
Handler mHandler = new Handler();
- long mActivityCreateTime = System.currentTimeMillis();
+ long mActivityCreateTime;
long mChronometerBase = 0;
boolean mProgressDone = true;
@@ -67,6 +68,13 @@ public class NotificationTestList extends TestActivity
final int kUnnumberedIconResID = R.drawable.notificationx;
@Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mVibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE);
+ mActivityCreateTime = System.currentTimeMillis();
+ }
+
+ @Override
protected String tag() {
return TAG;
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 314e33e..6168f0e 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -1500,8 +1500,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
int key;
WifiP2pServiceRequest req;
for (int i=0; i < c.mReqList.size(); i++) {
- key = c.mReqList.keyAt(i);
- req = c.mReqList.get(key);
+ req = c.mReqList.valueAt(i);
if (req != null) {
sb.append(req.getSupplicantQuery());
}
@@ -1539,7 +1538,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
return false;
}
- req.setTransactionId(++mServiceTransactionId);
+ ++mServiceTransactionId;
+ //The Wi-Fi p2p spec says transaction id should be non-zero
+ if (mServiceTransactionId == 0) ++mServiceTransactionId;
+ req.setTransactionId(mServiceTransactionId);
clientInfo.mReqList.put(mServiceTransactionId, req);
if (mServiceDiscReqId == null) {
@@ -1550,13 +1552,23 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
}
private void removeServiceRequest(Messenger m, WifiP2pServiceRequest req) {
-
ClientInfo clientInfo = getClientInfo(m, false);
if (clientInfo == null) {
return;
}
- clientInfo.mReqList.remove(req.getTransactionId());
+ //Application does not have transaction id information
+ //go through stored requests to remove
+ boolean removed = false;
+ for (int i=0; i < clientInfo.mReqList.size(); i++) {
+ if (req.equals(clientInfo.mReqList.valueAt(i))) {
+ removed = true;
+ clientInfo.mReqList.removeAt(i);
+ break;
+ }
+ }
+
+ if (!removed) return;
if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
if (DBG) logd("remove client information from framework");
@@ -1670,6 +1682,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
} catch (RemoteException e) {
if (DBG) logd("detect dead channel");
clearClientInfo(c.mMessenger);
+ return;
}
}
}
@@ -1683,6 +1696,8 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
* TODO: This can be done better with full async channels.
*/
private void clearClientDeadChannels() {
+ ArrayList<Messenger> deadClients = new ArrayList<Messenger>();
+
for (ClientInfo c : mClientInfoList.values()) {
Message msg = Message.obtain();
msg.what = WifiP2pManager.PING;
@@ -1693,9 +1708,13 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
c.mMessenger.send(msg);
} catch (RemoteException e) {
if (DBG) logd("detect dead channel");
- clearClientInfo(c.mMessenger);
+ deadClients.add(c.mMessenger);
}
}
+
+ for (Messenger m : deadClients) {
+ clearClientInfo(m);
+ }
}
/**