Merge "Changes documention for how input buttons should be labelled"
diff --git a/Android.mk b/Android.mk
index f45f977..d684cd3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -78,8 +78,9 @@
core/java/android/app/IThumbnailReceiver.aidl \
core/java/android/app/IThumbnailRetriever.aidl \
core/java/android/app/ITransientNotification.aidl \
+ core/java/android/app/IUiAutomationConnection.aidl \
core/java/android/app/IUiModeManager.aidl \
- core/java/android/app/IUserSwitchObserver.aidl \
+ core/java/android/app/IUserSwitchObserver.aidl \
core/java/android/app/IWallpaperManager.aidl \
core/java/android/app/IWallpaperManagerCallback.aidl \
core/java/android/app/admin/IDevicePolicyManager.aidl \
@@ -219,10 +220,7 @@
telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \
telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
wifi/java/android/net/wifi/IWifiManager.aidl \
- wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
- voip/java/android/net/sip/ISipSession.aidl \
- voip/java/android/net/sip/ISipSessionListener.aidl \
- voip/java/android/net/sip/ISipService.aidl
+ wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
#
@@ -353,6 +351,8 @@
../../external/apache-http/src/org/apache/http \
../opt/telephony/src/java/android/telephony \
../opt/telephony/src/java/android/telephony/gsm \
+ ../opt/net/voip/src/java/android/net/rtp \
+ ../opt/net/voip/src/java/android/net/sip
# These are relative to frameworks/base
dirs_to_check_apis := \
@@ -405,6 +405,7 @@
framework \
mms-common \
telephony-common \
+ voip-common \
framework_docs_LOCAL_MODULE_CLASS := JAVA_LIBRARIES
framework_docs_LOCAL_DROIDDOC_HTML_DIR := docs/html
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 06f7c54..2c7d16f 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -149,6 +149,11 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayMagnificationController.P)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayMagnificationMediator.java)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayMagnificationMediator.P)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/voip)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/librtp_jni_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/librtp_jni.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/librtp_jni.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/librtp_jni.so)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/api/current.txt b/api/current.txt
index fc60f94..fab07f6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2104,6 +2104,7 @@
field public static final int FEEDBACK_SPOKEN = 1; // 0x1
field public static final int FEEDBACK_VISUAL = 8; // 0x8
field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
+ field public static final int FLAG_REPORT_VIEW_IDS = 8; // 0x8
field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
field public int eventTypes;
field public int feedbackType;
@@ -3587,6 +3588,7 @@
method public android.content.ComponentName getComponentName();
method public android.content.Context getContext();
method public android.content.Context getTargetContext();
+ method public android.app.UiAutomation getUiAutomation();
method public boolean invokeContextMenuAction(android.app.Activity, int, int);
method public boolean invokeMenuActionSync(android.app.Activity, int, int);
method public boolean isProfiling();
@@ -4144,6 +4146,26 @@
method public abstract void onTimeSet(android.widget.TimePicker, int, int);
}
+ public final class UiAutomation {
+ method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(java.lang.Runnable, com.android.internal.util.Predicate<android.view.accessibility.AccessibilityEvent>, long) throws java.util.concurrent.TimeoutException;
+ method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
+ method public boolean injectInputEvent(android.view.InputEvent, boolean);
+ method public void setOnAccessibilityEventListener(android.app.UiAutomation.OnAccessibilityEventListener);
+ method public boolean setRotation(int);
+ method public android.graphics.Bitmap takeScreenshot();
+ method public void waitForIdle(long, long) throws java.util.concurrent.TimeoutException;
+ field public static final int ROTATION_FREEZE_0 = 0; // 0x0
+ field public static final int ROTATION_FREEZE_180 = 2; // 0x2
+ field public static final int ROTATION_FREEZE_270 = 3; // 0x3
+ field public static final int ROTATION_FREEZE_90 = 1; // 0x1
+ field public static final int ROTATION_FREEZE_CURRENT = -1; // 0xffffffff
+ field public static final int ROTATION_UNFREEZE = -2; // 0xfffffffe
+ }
+
+ public static abstract interface UiAutomation.OnAccessibilityEventListener {
+ method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
+ }
+
public class UiModeManager {
method public void disableCarMode(int);
method public void enableCarMode(int);
@@ -21308,6 +21330,7 @@
ctor public InstrumentationTestRunner();
method public junit.framework.TestSuite getAllTests();
method protected android.test.AndroidTestRunner getAndroidTestRunner();
+ method public android.os.Bundle getArguments();
method public java.lang.ClassLoader getLoader();
method public junit.framework.TestSuite getTestSuite();
field public static final java.lang.String REPORT_KEY_NAME_CLASS = "class";
@@ -26254,6 +26277,7 @@
method public void addChild(android.view.View, int);
method public int describeContents();
method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String);
+ method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(java.lang.String);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public android.view.accessibility.AccessibilityNodeInfo focusSearch(int);
method public int getActions();
@@ -26269,6 +26293,7 @@
method public java.lang.CharSequence getPackageName();
method public android.view.accessibility.AccessibilityNodeInfo getParent();
method public java.lang.CharSequence getText();
+ method public java.lang.CharSequence getViewId();
method public int getWindowId();
method public boolean isAccessibilityFocused();
method public boolean isCheckable();
@@ -26316,6 +26341,7 @@
method public void setSource(android.view.View);
method public void setSource(android.view.View, int);
method public void setText(java.lang.CharSequence);
+ method public void setViewId(java.lang.CharSequence);
method public void setVisibleToUser(boolean);
method public void writeToParcel(android.os.Parcel, int);
field public static final int ACTION_ACCESSIBILITY_FOCUS = 64; // 0x40
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index add7a23..b5574cf 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -24,6 +24,7 @@
import android.app.IActivityManager;
import android.app.IInstrumentationWatcher;
import android.app.Instrumentation;
+import android.app.UiAutomationConnection;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -661,10 +662,13 @@
if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
InstrumentationWatcher watcher = null;
+ UiAutomationConnection connection = null;
if (wait) {
watcher = new InstrumentationWatcher();
watcher.setRawOutput(rawMode);
+ connection = new UiAutomationConnection();
}
+
float[] oldAnims = null;
if (no_window_animation) {
oldAnims = wm.getAnimationScales();
@@ -672,7 +676,7 @@
wm.setAnimationScale(1, 0.0f);
}
- if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, userId)) {
+ if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId)) {
throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 7efe189..8dddbc1 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -339,7 +339,10 @@
private static final String LOG_TAG = "AccessibilityService";
- interface Callbacks {
+ /**
+ * @hide
+ */
+ public interface Callbacks {
public void onAccessibilityEvent(AccessibilityEvent event);
public void onInterrupt();
public void onServiceConnected();
@@ -538,8 +541,10 @@
/**
* Implements the internal {@link IAccessibilityServiceClient} interface to convert
* incoming calls to it back to calls on an {@link AccessibilityService}.
+ *
+ * @hide
*/
- static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
+ public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
implements HandlerCaller.Callback {
static final int NO_ID = -1;
@@ -610,6 +615,7 @@
mCallback.onServiceConnected();
} else {
AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
+ AccessibilityInteractionClient.getInstance().clearCache();
mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID);
}
return;
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 75a4f83..2006bc7 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -33,6 +33,7 @@
import android.util.Xml;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -152,6 +153,15 @@
public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE= 0x0000004;
/**
+ * This flag requests that the {@link AccessibilityNodeInfo}s obtained
+ * by an {@link AccessibilityService} contain the id of the source view.
+ * The source view id will be a fully qualified resource name of the
+ * form "package:id/name", for example "foo.bar:id/my_list", and it is
+ * useful for UI test automation. This flag is not set by default.
+ */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000008;
+
+ /**
* The event types an {@link AccessibilityService} is interested in.
* <p>
* <strong>Can be dynamically set at runtime.</strong>
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index f33f503..7a29f35 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -85,15 +85,15 @@
* where to start the search. Use
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
- * @param id The id of the node.
+ * @param viewId The fully qualified resource name of the view id to find.
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
* @param threadId The id of the calling thread.
* @return Whether the call succeeded.
*/
- boolean findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId,
- int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- long threadId);
+ boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, String viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long threadId);
/**
* Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the specified
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
deleted file mode 100644
index 6837386..0000000
--- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accessibilityservice;
-
-import android.accessibilityservice.AccessibilityService.Callbacks;
-import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.IAccessibilityManager;
-
-import com.android.internal.util.Predicate;
-
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * This class represents a bridge that can be used for UI test
- * automation. It is responsible for connecting to the system,
- * keeping track of the last accessibility event, and exposing
- * window content querying APIs. This class is designed to be
- * used from both an Android application and a Java program
- * run from the shell.
- *
- * @hide
- */
-public class UiTestAutomationBridge {
-
- private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName();
-
- private static final int TIMEOUT_REGISTER_SERVICE = 5000;
-
- public static final int ACTIVE_WINDOW_ID = AccessibilityNodeInfo.ACTIVE_WINDOW_ID;
-
- public static final long ROOT_NODE_ID = AccessibilityNodeInfo.ROOT_NODE_ID;
-
- public static final int UNDEFINED = -1;
-
- private static final int FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS =
- AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
- | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
- | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
-
- private final Object mLock = new Object();
-
- private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID;
-
- private IAccessibilityServiceClientWrapper mListener;
-
- private AccessibilityEvent mLastEvent;
-
- private volatile boolean mWaitingForEventDelivery;
-
- private volatile boolean mUnprocessedEventAvailable;
-
- private HandlerThread mHandlerThread;
-
- /**
- * Gets the last received {@link AccessibilityEvent}.
- *
- * @return The event.
- */
- public AccessibilityEvent getLastAccessibilityEvent() {
- return mLastEvent;
- }
-
- /**
- * Callback for receiving an {@link AccessibilityEvent}.
- *
- * <strong>Note:</strong> This method is <strong>NOT</strong>
- * executed on the application main thread. The client are
- * responsible for proper synchronization.
- *
- * @param event The received event.
- */
- public void onAccessibilityEvent(AccessibilityEvent event) {
- /* hook - do nothing */
- }
-
- /**
- * Callback for requests to stop feedback.
- *
- * <strong>Note:</strong> This method is <strong>NOT</strong>
- * executed on the application main thread. The client are
- * responsible for proper synchronization.
- */
- public void onInterrupt() {
- /* hook - do nothing */
- }
-
- /**
- * Connects this service.
- *
- * @throws IllegalStateException If already connected.
- */
- public void connect() {
- if (isConnected()) {
- throw new IllegalStateException("Already connected.");
- }
-
- // Serialize binder calls to a handler on a dedicated thread
- // different from the main since we expose APIs that block
- // the main thread waiting for a result the deliver of which
- // on the main thread will prevent that thread from waking up.
- // The serialization is needed also to ensure that events are
- // examined in delivery order. Otherwise, a fair locking
- // is needed for making sure the binder calls are interleaved
- // with check for the expected event and also to make sure the
- // binder threads are allowed to proceed in the received order.
- mHandlerThread = new HandlerThread("UiTestAutomationBridge");
- mHandlerThread.setDaemon(true);
- mHandlerThread.start();
- Looper looper = mHandlerThread.getLooper();
-
- mListener = new IAccessibilityServiceClientWrapper(null, looper, new Callbacks() {
- @Override
- public void onServiceConnected() {
- /* do nothing */
- }
-
- @Override
- public void onInterrupt() {
- UiTestAutomationBridge.this.onInterrupt();
- }
-
- @Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
- synchronized (mLock) {
- while (true) {
- mLastEvent = AccessibilityEvent.obtain(event);
- if (!mWaitingForEventDelivery) {
- mLock.notifyAll();
- break;
- }
- if (!mUnprocessedEventAvailable) {
- mUnprocessedEventAvailable = true;
- mLock.notifyAll();
- break;
- }
- try {
- mLock.wait();
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- UiTestAutomationBridge.this.onAccessibilityEvent(event);
- }
-
- @Override
- public void onSetConnectionId(int connectionId) {
- synchronized (mLock) {
- mConnectionId = connectionId;
- mLock.notifyAll();
- }
- }
-
- @Override
- public boolean onGesture(int gestureId) {
- return false;
- }
- });
-
- final IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
-
- final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
- info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
- info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
- info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-
- try {
- manager.registerUiTestAutomationService(mListener, info);
- } catch (RemoteException re) {
- throw new IllegalStateException("Cound not register UiAutomationService.", re);
- }
-
- synchronized (mLock) {
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- if (isConnected()) {
- return;
- }
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = TIMEOUT_REGISTER_SERVICE - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- throw new IllegalStateException("Cound not register UiAutomationService.");
- }
- try {
- mLock.wait(remainingTimeMillis);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Disconnects this service.
- *
- * @throws IllegalStateException If already disconnected.
- */
- public void disconnect() {
- if (!isConnected()) {
- throw new IllegalStateException("Already disconnected.");
- }
-
- mHandlerThread.quit();
-
- IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
-
- try {
- manager.unregisterUiTestAutomationService(mListener);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while unregistering UiTestAutomationService", re);
- }
- }
-
- /**
- * Gets whether this service is connected.
- *
- * @return True if connected.
- */
- public boolean isConnected() {
- return (mConnectionId != AccessibilityInteractionClient.NO_ID);
- }
-
- /**
- * Executes a command and waits for a specific accessibility event type up
- * to a given timeout.
- *
- * @param command The command to execute before starting to wait for the event.
- * @param predicate Predicate for recognizing the awaited event.
- * @param timeoutMillis The max wait time in milliseconds.
- */
- public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
- Predicate<AccessibilityEvent> predicate, long timeoutMillis)
- throws TimeoutException, Exception {
- // TODO: This is broken - remove from here when finalizing this as public APIs.
- synchronized (mLock) {
- // Prepare to wait for an event.
- mWaitingForEventDelivery = true;
- mUnprocessedEventAvailable = false;
- if (mLastEvent != null) {
- mLastEvent.recycle();
- mLastEvent = null;
- }
- // Execute the command.
- command.run();
- // Wait for the event.
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- // If the expected event is received, that's it.
- if ((mUnprocessedEventAvailable && predicate.apply(mLastEvent))) {
- mWaitingForEventDelivery = false;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- return mLastEvent;
- }
- // Ask for another event.
- mWaitingForEventDelivery = true;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- // Check if timed out and if not wait.
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- mWaitingForEventDelivery = false;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- throw new TimeoutException("Expacted event not received within: "
- + timeoutMillis + " ms.");
- }
- try {
- mLock.wait(remainingTimeMillis);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Waits for the accessibility event stream to become idle, which is not to
- * have received a new accessibility event within <code>idleTimeout</code>,
- * and do so within a maximal global timeout as specified by
- * <code>globalTimeout</code>.
- *
- * @param idleTimeout The timeout between two event to consider the device idle.
- * @param globalTimeout The maximal global timeout in which to wait for idle.
- */
- public void waitForIdle(long idleTimeout, long globalTimeout) {
- final long startTimeMillis = SystemClock.uptimeMillis();
- long lastEventTime = (mLastEvent != null)
- ? mLastEvent.getEventTime() : SystemClock.uptimeMillis();
- synchronized (mLock) {
- while (true) {
- final long currentTimeMillis = SystemClock.uptimeMillis();
- final long sinceLastEventTimeMillis = currentTimeMillis - lastEventTime;
- if (sinceLastEventTimeMillis > idleTimeout) {
- return;
- }
- if (mLastEvent != null) {
- lastEventTime = mLastEvent.getEventTime();
- }
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = globalTimeout - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- return;
- }
- try {
- mLock.wait(idleTimeout);
- } catch (InterruptedException e) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by accessibility id in the active
- * window. The search is performed from the root node.
- *
- * @param accessibilityNodeId A unique view id or virtual descendant id for
- * which to search.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityIdInActiveWindow(
- long accessibilityNodeId) {
- return findAccessibilityNodeInfoByAccessibilityId(ACTIVE_WINDOW_ID, accessibilityNodeId);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by accessibility id.
- *
- * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} to query
- * the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id for
- * which to search.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
- int accessibilityWindowId, long accessibilityNodeId) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- accessibilityWindowId, accessibilityNodeId,
- FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by View id in the active
- * window. The search is performed from the root node.
- *
- * @param viewId The id of a View.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) {
- return findAccessibilityNodeInfoByViewId(ACTIVE_WINDOW_ID, ROOT_NODE_ID, viewId);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
- * the window whose id is specified and starts from the node whose accessibility
- * id is specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
- * @param viewId The id of a View.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
- long accessibilityNodeId, int viewId) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewId(connectionId, accessibilityWindowId,
- accessibilityNodeId, viewId);
- }
-
- /**
- * Finds {@link AccessibilityNodeInfo}s by View text in the active
- * window. The search is performed from the root node.
- *
- * @param text The searched text.
- * @return The current window scale, where zero means a failure.
- */
- public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(String text) {
- return findAccessibilityNodeInfosByText(ACTIVE_WINDOW_ID, ROOT_NODE_ID, text);
- }
-
- /**
- * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
- * insensitive containment. The search is performed in the window whose
- * id is specified and starts from the node whose accessibility id is
- * specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
- * @param text The searched text.
- * @return The current window scale, where zero means a failure.
- */
- public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int accessibilityWindowId,
- long accessibilityNodeId, String text) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfosByText(connectionId, accessibilityWindowId,
- accessibilityNodeId, text);
- }
-
- /**
- * Performs an accessibility action on an {@link AccessibilityNodeInfo}
- * in the active window.
- *
- * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
- * @param action The action to perform.
- * @param arguments Optional action arguments.
- * @return Whether the action was performed.
- */
- public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId, int action,
- Bundle arguments) {
- return performAccessibilityAction(ACTIVE_WINDOW_ID, accessibilityNodeId, action, arguments);
- }
-
- /**
- * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
- * @param action The action to perform.
- * @param arguments Optional action arguments.
- * @return Whether the action was performed.
- */
- public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
- int action, Bundle arguments) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance().performAccessibilityAction(connectionId,
- accessibilityWindowId, accessibilityNodeId, action, arguments);
- }
-
- /**
- * Gets the root {@link AccessibilityNodeInfo} in the active window.
- *
- * @return The root info.
- */
- public AccessibilityNodeInfo getRootAccessibilityNodeInfoInActiveWindow() {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByAccessibilityId(connectionId, ACTIVE_WINDOW_ID,
- ROOT_NODE_ID, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
- }
-
- private void ensureValidConnection(int connectionId) {
- if (connectionId == UNDEFINED) {
- throw new IllegalStateException("UiAutomationService not connected."
- + " Did you call #register()?");
- }
- }
-}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index bc27a57..d8e2239 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -39,7 +39,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
-import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Singleton;
@@ -836,8 +835,10 @@
Bundle arguments = data.readBundle();
IBinder b = data.readStrongBinder();
IInstrumentationWatcher w = IInstrumentationWatcher.Stub.asInterface(b);
+ b = data.readStrongBinder();
+ IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b);
int userId = data.readInt();
- boolean res = startInstrumentation(className, profileFile, fl, arguments, w, userId);
+ boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -2874,8 +2875,8 @@
}
public boolean startInstrumentation(ComponentName className, String profileFile,
- int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
- throws RemoteException {
+ int flags, Bundle arguments, IInstrumentationWatcher watcher,
+ IUiAutomationConnection connection, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -2884,6 +2885,7 @@
data.writeInt(flags);
data.writeBundle(arguments);
data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
+ data.writeStrongBinder(connection != null ? connection.asBinder() : null);
data.writeInt(userId);
mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
reply.readException();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 570fb80..bb73cf4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,7 +43,6 @@
import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.net.IConnectivityManager;
import android.net.Proxy;
@@ -102,7 +101,6 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
-import java.security.Security;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -420,6 +418,7 @@
ComponentName instrumentationName;
Bundle instrumentationArgs;
IInstrumentationWatcher instrumentationWatcher;
+ IUiAutomationConnection instrumentationUiAutomationConnection;
int debugMode;
boolean enableOpenGlTrace;
boolean restrictedBackupMode;
@@ -730,9 +729,10 @@
ComponentName instrumentationName, String profileFile,
ParcelFileDescriptor profileFd, boolean autoStopProfiler,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- int debugMode, boolean enableOpenGlTrace, boolean isRestrictedBackupMode,
- boolean persistent, Configuration config, CompatibilityInfo compatInfo,
- Map<String, IBinder> services, Bundle coreSettings) {
+ IUiAutomationConnection instrumentationUiConnection, int debugMode,
+ boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
+ Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
+ Bundle coreSettings) {
if (services != null) {
// Setup the service cache in the ServiceManager
@@ -748,6 +748,7 @@
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
+ data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableOpenGlTrace = enableOpenGlTrace;
data.restrictedBackupMode = isRestrictedBackupMode;
@@ -4375,7 +4376,8 @@
}
mInstrumentation.init(this, instrContext, appContext,
- new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher);
+ new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
+ data.instrumentationUiAutomationConnection);
if (mProfiler.profileFile != null && !ii.handleProfiling
&& mProfiler.profileFd == null) {
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index f0e367c..b1c58f2 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -267,6 +267,9 @@
Bundle testArgs = data.readBundle();
IBinder binder = data.readStrongBinder();
IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
+ binder = data.readStrongBinder();
+ IUiAutomationConnection uiAutomationConnection =
+ IUiAutomationConnection.Stub.asInterface(binder);
int testMode = data.readInt();
boolean openGlTrace = data.readInt() != 0;
boolean restrictedBackupMode = (data.readInt() != 0);
@@ -277,8 +280,9 @@
Bundle coreSettings = data.readBundle();
bindApplication(packageName, info,
providers, testName, profileName, profileFd, autoStopProfiler,
- testArgs, testWatcher, testMode, openGlTrace, restrictedBackupMode,
- persistent, config, compatInfo, services, coreSettings);
+ testArgs, testWatcher, uiAutomationConnection, testMode,
+ openGlTrace, restrictedBackupMode, persistent, config, compatInfo,
+ services, coreSettings);
return true;
}
@@ -874,10 +878,11 @@
public final void bindApplication(String packageName, ApplicationInfo info,
List<ProviderInfo> providers, ComponentName testName, String profileName,
ParcelFileDescriptor profileFd, boolean autoStopProfiler, Bundle testArgs,
- IInstrumentationWatcher testWatcher, int debugMode, boolean openGlTrace,
- boolean restrictedBackupMode, boolean persistent,
- Configuration config, CompatibilityInfo compatInfo,
- Map<String, IBinder> services, Bundle coreSettings) throws RemoteException {
+ IInstrumentationWatcher testWatcher,
+ IUiAutomationConnection uiAutomationConnection, int debugMode,
+ boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
+ Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
+ Bundle coreSettings) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeString(packageName);
@@ -899,6 +904,7 @@
data.writeInt(autoStopProfiler ? 1 : 0);
data.writeBundle(testArgs);
data.writeStrongInterface(testWatcher);
+ data.writeStrongInterface(uiAutomationConnection);
data.writeInt(debugMode);
data.writeInt(openGlTrace ? 1 : 0);
data.writeInt(restrictedBackupMode ? 1 : 0);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index fd4389e..e03d3fd 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1475,7 +1475,7 @@
arguments.setAllowFds(false);
}
return ActivityManagerNative.getDefault().startInstrumentation(
- className, profileFile, 0, arguments, null, getUserId());
+ className, profileFile, 0, arguments, null, null, getUserId());
} catch (RemoteException e) {
// System has crashed, nothing we can do.
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index baac07f..5a49329 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -158,8 +158,8 @@
public void killApplicationProcess(String processName, int uid) throws RemoteException;
public boolean startInstrumentation(ComponentName className, String profileFile,
- int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
- throws RemoteException;
+ int flags, Bundle arguments, IInstrumentationWatcher watcher,
+ IUiAutomationConnection connection, int userId) throws RemoteException;
public void finishInstrumentation(IApplicationThread target,
int resultCode, Bundle results) throws RemoteException;
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 8516694..3189b31 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -90,7 +90,8 @@
void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
ComponentName testName, String profileName, ParcelFileDescriptor profileFd,
boolean autoStopProfiler, Bundle testArguments, IInstrumentationWatcher testWatcher,
- int debugMode, boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
+ IUiAutomationConnection uiAutomationConnection, int debugMode,
+ boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
Bundle coreSettings) throws RemoteException;
void scheduleExit() throws RemoteException;
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
new file mode 100644
index 0000000..09bf829
--- /dev/null
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.graphics.Bitmap;
+import android.view.InputEvent;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * This interface contains privileged operations a shell program can perform
+ * on behalf of an instrumentation that it runs. These operations require
+ * special permissions which the shell user has but the instrumentation does
+ * not. Running privileged operations by the shell user on behalf of an
+ * instrumentation is needed for running UiTestCases.
+ *
+ * {@hide}
+ */
+interface IUiAutomationConnection {
+ void connect(IAccessibilityServiceClient client);
+ void disconnect();
+ boolean injectInputEvent(in InputEvent event, boolean sync);
+ boolean setRotation(int rotation);
+ Bitmap takeScreenshot(int width, int height);
+ void shutdown();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 39186c6..a2eeddd 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -49,7 +49,6 @@
import java.util.ArrayList;
import java.util.List;
-
/**
* Base class for implementing application instrumentation code. When running
* with instrumentation turned on, this class will be instantiated for you
@@ -59,6 +58,7 @@
* <instrumentation> tag.
*/
public class Instrumentation {
+
/**
* If included in the status or final bundle sent to an IInstrumentationWatcher, this key
* identifies the class that is writing the report. This can be used to provide more structured
@@ -73,7 +73,7 @@
* instrumentation can also be launched, and results collected, by an automated system.
*/
public static final String REPORT_KEY_STREAMRESULT = "stream";
-
+
private static final String TAG = "Instrumentation";
private final Object mSync = new Object();
@@ -86,9 +86,11 @@
private List<ActivityWaiter> mWaitingActivities;
private List<ActivityMonitor> mActivityMonitors;
private IInstrumentationWatcher mWatcher;
+ private IUiAutomationConnection mUiAutomationConnection;
private boolean mAutomaticPerformanceSnapshots = false;
private PerformanceCollector mPerformanceCollector;
private Bundle mPerfMetrics = new Bundle();
+ private UiAutomation mUiAutomation;
public Instrumentation() {
}
@@ -1598,13 +1600,14 @@
/*package*/ final void init(ActivityThread thread,
Context instrContext, Context appContext, ComponentName component,
- IInstrumentationWatcher watcher) {
+ IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) {
mThread = thread;
mMessageQueue = mThread.getLooper().myQueue();
mInstrContext = instrContext;
mAppContext = appContext;
mComponent = component;
mWatcher = watcher;
+ mUiAutomationConnection = uiAutomationConnection;
}
/*package*/ static void checkStartActivityResult(int res, Object intent) {
@@ -1644,12 +1647,42 @@
}
}
+ /**
+ * Gets the {@link UiAutomation} instance.
+ * <p>
+ * <strong>Note:</strong> The APIs exposed via the returned {@link UiAutomation}
+ * work across application boundaries while the APIs exposed by the instrumentation
+ * do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will
+ * not allow you to inject the event in an app different from the instrumentation
+ * target, while {@link UiAutomation#injectInputEvent(android.view.InputEvent, boolean)}
+ * will work regardless of the current application.
+ * </p>
+ * <p>
+ * A typical test case should be using either the {@link UiAutomation} or
+ * {@link Instrumentation} APIs. Using both APIs at the same time is not
+ * a mistake by itself but a client has to be aware of the APIs limitations.
+ * </p>
+ * @return The UI automation instance.
+ *
+ * @see UiAutomation
+ */
+ public UiAutomation getUiAutomation() {
+ if (mUiAutomationConnection != null) {
+ if (mUiAutomation == null) {
+ mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
+ mUiAutomationConnection);
+ mUiAutomation.connect();
+ }
+ return mUiAutomation;
+ }
+ return null;
+ }
+
private final class InstrumentationThread extends Thread {
public InstrumentationThread(String name) {
super(name);
}
public void run() {
- IActivityManager am = ActivityManagerNative.getDefault();
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
} catch (RuntimeException e) {
@@ -1660,9 +1693,13 @@
startPerformanceSnapshot();
}
onStart();
+ if (mUiAutomation != null) {
+ mUiAutomation.disconnect();
+ mUiAutomation = null;
+ }
}
}
-
+
private static final class EmptyRunnable implements Runnable {
public void run() {
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
new file mode 100644
index 0000000..e611f6d
--- /dev/null
+++ b/core/java/android/app/UiAutomation.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.accessibilityservice.AccessibilityService.Callbacks;
+import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+import android.view.InputEvent;
+import android.view.Surface;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+
+import com.android.internal.util.Predicate;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Class for interacting with the device's UI by simulation user actions and
+ * introspection of the screen content. It relies on the platform accessibility
+ * APIs to introspect the screen and to perform some actions on the remote view
+ * tree. It also allows injecting of arbitrary raw input events simulating user
+ * interaction with keyboards and touch devices.
+ * <p>
+ * The APIs exposed by this class are low-level to maximize flexibility when
+ * developing UI test automation tools and libraries. Generally, a UiAutomation
+ * client should be using a higher-level library or implement high-level functions.
+ * For example, performing a tap on the screen requires construction and injecting
+ * of a touch down and up events which have to be delivered to the system by a
+ * call to {@link #injectInputEvent(InputEvent, boolean)}.
+ * </p>
+ * <p>
+ * The APIs exposed by this class operate across applications enabling a client
+ * to write tests that cover use cases spanning over multiple applications. For
+ * example, going to the settings application to change a setting and then
+ * interacting with another application whose behavior depends on that setting.
+ * </p>
+ */
+public final class UiAutomation {
+
+ private static final String LOG_TAG = UiAutomation.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ private static final int CONNECTION_ID_UNDEFINED = -1;
+
+ private static final long CONNECT_TIMEOUT_MILLIS = 5000;
+
+ /** Rotation constant: Unfreeze rotation (rotating the device changes its rotation state). */
+ public static final int ROTATION_UNFREEZE = -2;
+
+ /** Rotation constant: Freeze rotation to its current state. */
+ public static final int ROTATION_FREEZE_CURRENT = -1;
+
+ /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */
+ public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0;
+
+ /** Rotation constant: Freeze rotation to 90 degrees . */
+ public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90;
+
+ /** Rotation constant: Freeze rotation to 180 degrees . */
+ public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180;
+
+ /** Rotation constant: Freeze rotation to 270 degrees . */
+ public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;
+
+ private final Object mLock = new Object();
+
+ private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>();
+
+ private final IAccessibilityServiceClient mClient;
+
+ private final IUiAutomationConnection mUiAutomationConnection;
+
+ private int mConnectionId = CONNECTION_ID_UNDEFINED;
+
+ private OnAccessibilityEventListener mOnAccessibilityEventListener;
+
+ private boolean mWaitingForEventDelivery;
+
+ private long mLastEventTimeMillis;
+
+ private boolean mIsConnecting;
+
+ /**
+ * Listener for observing the {@link AccessibilityEvent} stream.
+ */
+ public static interface OnAccessibilityEventListener {
+
+ /**
+ * Callback for receiving an {@link AccessibilityEvent}.
+ * <p>
+ * <strong>Note:</strong> This method is <strong>NOT</strong> executed
+ * on the main test thread. The client is responsible for proper
+ * synchronization.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> It is responsibility of the client
+ * to recycle the received events to minimize object creation.
+ * </p>
+ *
+ * @param event The received event.
+ */
+ public void onAccessibilityEvent(AccessibilityEvent event);
+ }
+
+ /**
+ * Creates a new instance that will handle callbacks from the accessibility
+ * layer on the thread of the provided looper and perform requests for privileged
+ * operations on the provided connection.
+ *
+ * @param looper The looper on which to execute accessibility callbacks.
+ * @param connection The connection for performing privileged operations.
+ *
+ * @hide
+ */
+ public UiAutomation(Looper looper, IUiAutomationConnection connection) {
+ if (looper == null) {
+ throw new IllegalArgumentException("Looper cannot be null!");
+ }
+ if (connection == null) {
+ throw new IllegalArgumentException("Connection cannot be null!");
+ }
+ mUiAutomationConnection = connection;
+ mClient = new IAccessibilityServiceClientImpl(looper);
+ }
+
+ /**
+ * Connects this UiAutomation to the accessibility introspection APIs.
+ *
+ * @hide
+ */
+ public void connect() {
+ synchronized (mLock) {
+ throwIfConnectedLocked();
+ if (mIsConnecting) {
+ return;
+ }
+ mIsConnecting = true;
+ }
+
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.connect(mClient);
+ } catch (RemoteException re) {
+ throw new RuntimeException("Error while connecting UiAutomation", re);
+ }
+
+ synchronized (mLock) {
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ try {
+ while (true) {
+ if (isConnectedLocked()) {
+ break;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new RuntimeException("Error while connecting UiAutomation");
+ }
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ } finally {
+ mIsConnecting = false;
+ }
+ }
+ }
+
+ /**
+ * Disconnects this UiAutomation from the accessibility introspection APIs.
+ *
+ * @hide
+ */
+ public void disconnect() {
+ synchronized (mLock) {
+ if (mIsConnecting) {
+ throw new IllegalStateException(
+ "Cannot call disconnect() while connecting!");
+ }
+ throwIfNotConnectedLocked();
+ mConnectionId = CONNECTION_ID_UNDEFINED;
+ }
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.disconnect();
+ } catch (RemoteException re) {
+ throw new RuntimeException("Error while disconnecting UiAutomation", re);
+ }
+ }
+
+ /**
+ * The id of the {@link IAccessibilityInteractionConnection} for querying
+ * the screen content. This is here for legacy purposes since some tools use
+ * hidden APIs to introspect the screen.
+ *
+ * @hide
+ */
+ public int getConnectionId() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ return mConnectionId;
+ }
+ }
+
+ /**
+ * Sets a callback for observing the stream of {@link AccessibilityEvent}s.
+ *
+ * @param listener The callback.
+ */
+ public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) {
+ synchronized (mLock) {
+ mOnAccessibilityEventListener = listener;
+ }
+ }
+
+ /**
+ * Gets the root {@link AccessibilityNodeInfo} in the active window.
+ *
+ * @return The root info.
+ */
+ public AccessibilityNodeInfo getRootInActiveWindow() {
+ final int connectionId;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ connectionId = mConnectionId;
+ }
+ // Calling out without a lock held.
+ return AccessibilityInteractionClient.getInstance()
+ .getRootInActiveWindow(connectionId);
+ }
+
+ /**
+ * A method for injecting an arbitrary input event.
+ * <p>
+ * <strong>Note:</strong> It is caller's responsibility to recycle the event.
+ * </p>
+ * @param event The event to inject.
+ * @param sync Whether to inject the event synchronously.
+ * @return Whether event injection succeeded.
+ */
+ public boolean injectInputEvent(InputEvent event, boolean sync) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
+ }
+ // Calling out without a lock held.
+ return mUiAutomationConnection.injectInputEvent(event, sync);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while injecting input event!", re);
+ }
+ return false;
+ }
+
+ /**
+ * Sets the device rotation. A client can freeze the rotation in
+ * desired state or freeze the rotation to its current state or
+ * unfreeze the rotation (rotating the device changes its rotation
+ * state).
+ *
+ * @param rotation The desired rotation.
+ * @return Whether the rotation was set successfully.
+ *
+ * @see #ROTATION_FREEZE_0
+ * @see #ROTATION_FREEZE_90
+ * @see #ROTATION_FREEZE_180
+ * @see #ROTATION_FREEZE_270
+ * @see #ROTATION_FREEZE_CURRENT
+ * @see #ROTATION_UNFREEZE
+ */
+ public boolean setRotation(int rotation) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ switch (rotation) {
+ case ROTATION_FREEZE_0:
+ case ROTATION_FREEZE_90:
+ case ROTATION_FREEZE_180:
+ case ROTATION_FREEZE_270:
+ case ROTATION_UNFREEZE:
+ case ROTATION_FREEZE_CURRENT: {
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.setRotation(rotation);
+ return true;
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while setting rotation!", re);
+ }
+ } return false;
+ default: {
+ throw new IllegalArgumentException("Invalid rotation.");
+ }
+ }
+ }
+
+ /**
+ * Executes a command and waits for a specific accessibility event up to a
+ * given wait timeout. To detect a sequence of events one can implement a
+ * filter that keeps track of seen events of the expected sequence and
+ * returns true after the last event of that sequence is received.
+ * <p>
+ * <strong>Note:</strong> It is caller's responsibility to recycle the returned event.
+ * </p>
+ * @param command The command to execute.
+ * @param filter Filter that recognizes the expected event.
+ * @param timeoutMillis The wait timeout in milliseconds.
+ *
+ * @throws TimeoutException If the expected event is not received within the timeout.
+ */
+ public AccessibilityEvent executeAndWaitForEvent(Runnable command,
+ Predicate<AccessibilityEvent> filter, long timeoutMillis) throws TimeoutException {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+
+ mEventQueue.clear();
+ // Prepare to wait for an event.
+ mWaitingForEventDelivery = true;
+
+ // We will ignore events from previous interactions.
+ final long executionStartTimeMillis = SystemClock.uptimeMillis();
+
+ // Execute the command.
+ command.run();
+ try {
+ // Wait for the event.
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (true) {
+ // Drain the event queue
+ while (!mEventQueue.isEmpty()) {
+ AccessibilityEvent event = mEventQueue.remove(0);
+ // Ignore events from previous interactions.
+ if (event.getEventTime() <= executionStartTimeMillis) {
+ continue;
+ }
+ if (filter.apply(event)) {
+ return event;
+ }
+ event.recycle();
+ }
+ // Check if timed out and if not wait.
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new TimeoutException("Expected event not received within: "
+ + timeoutMillis + " ms.");
+ }
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ } finally {
+ mWaitingForEventDelivery = false;
+ mEventQueue.clear();
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Waits for the accessibility event stream to become idle, which is not to
+ * have received an accessibility event within <code>idleTimeoutMillis</code>.
+ * The total time spent to wait for an idle accessibility event stream is bounded
+ * by the <code>globalTimeoutMillis</code>.
+ *
+ * @param idleTimeoutMillis The timeout in milliseconds between two events
+ * to consider the device idle.
+ * @param globalTimeoutMillis The maximal global timeout in milliseconds in
+ * which to wait for an idle state.
+ *
+ * @throws TimeoutException If no idle state was detected within
+ * <code>globalTimeoutMillis.</code>
+ */
+ public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
+ throws TimeoutException {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ if (mLastEventTimeMillis <= 0) {
+ mLastEventTimeMillis = startTimeMillis;
+ }
+
+ while (true) {
+ final long currentTimeMillis = SystemClock.uptimeMillis();
+ // Did we get idle state within the global timeout?
+ final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
+ final long remainingGlobalTimeMillis =
+ globalTimeoutMillis - elapsedGlobalTimeMillis;
+ if (remainingGlobalTimeMillis <= 0) {
+ throw new TimeoutException("No idle state with idle timeout: "
+ + idleTimeoutMillis + " within global timeout: "
+ + globalTimeoutMillis);
+ }
+ // Did we get an idle state within the idle timeout?
+ final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
+ final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
+ if (remainingIdleTimeMillis <= 0) {
+ return;
+ }
+ try {
+ mLock.wait(remainingIdleTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ /**
+ * Takes a screenshot.
+ *
+ * @return The screenshot bitmap on success, null otherwise.
+ */
+ public Bitmap takeScreenshot() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ Display display = DisplayManagerGlobal.getInstance()
+ .getRealDisplay(Display.DEFAULT_DISPLAY);
+ Point displaySize = new Point();
+ display.getRealSize(displaySize);
+ final int displayWidth = displaySize.x;
+ final int displayHeight = displaySize.y;
+
+ final float screenshotWidth;
+ final float screenshotHeight;
+
+ final int rotation = display.getRotation();
+ switch (rotation) {
+ case ROTATION_FREEZE_0: {
+ screenshotWidth = displayWidth;
+ screenshotHeight = displayHeight;
+ } break;
+ case ROTATION_FREEZE_90: {
+ screenshotWidth = displayHeight;
+ screenshotHeight = displayWidth;
+ } break;
+ case ROTATION_FREEZE_180: {
+ screenshotWidth = displayWidth;
+ screenshotHeight = displayHeight;
+ } break;
+ case ROTATION_FREEZE_270: {
+ screenshotWidth = displayHeight;
+ screenshotHeight = displayWidth;
+ } break;
+ default: {
+ throw new IllegalArgumentException("Invalid rotation: "
+ + rotation);
+ }
+ }
+
+ // Take the screenshot
+ Bitmap screenShot = null;
+ try {
+ // Calling out without a lock held.
+ screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
+ (int) screenshotHeight);
+ if (screenShot == null) {
+ return null;
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while taking screnshot!", re);
+ return null;
+ }
+
+ // Rotate the screenshot to the current orientation
+ if (rotation != ROTATION_FREEZE_0) {
+ Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(unrotatedScreenShot);
+ canvas.translate(unrotatedScreenShot.getWidth() / 2,
+ unrotatedScreenShot.getHeight() / 2);
+ canvas.rotate(getDegreesForRotation(rotation));
+ canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
+ canvas.drawBitmap(screenShot, 0, 0, null);
+ canvas.setBitmap(null);
+ screenShot = unrotatedScreenShot;
+ }
+
+ // Optimization
+ screenShot.setHasAlpha(false);
+
+ return screenShot;
+ }
+
+ private static float getDegreesForRotation(int value) {
+ switch (value) {
+ case Surface.ROTATION_90: {
+ return 360f - 90f;
+ }
+ case Surface.ROTATION_180: {
+ return 360f - 180f;
+ }
+ case Surface.ROTATION_270: {
+ return 360f - 270f;
+ } default: {
+ return 0;
+ }
+ }
+ }
+
+ private boolean isConnectedLocked() {
+ return mConnectionId != CONNECTION_ID_UNDEFINED;
+ }
+
+ private void throwIfConnectedLocked() {
+ if (mConnectionId != CONNECTION_ID_UNDEFINED) {
+ throw new IllegalStateException("UiAutomation not connected!");
+ }
+ }
+
+ private void throwIfNotConnectedLocked() {
+ if (!isConnectedLocked()) {
+ throw new IllegalStateException("UiAutomation not connected!");
+ }
+ }
+
+ private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
+
+ public IAccessibilityServiceClientImpl(Looper looper) {
+ super(null, looper, new Callbacks() {
+ @Override
+ public void onSetConnectionId(int connectionId) {
+ synchronized (mLock) {
+ mConnectionId = connectionId;
+ mLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void onServiceConnected() {
+ /* do nothing */
+ }
+
+ @Override
+ public void onInterrupt() {
+ /* do nothing */
+ }
+
+ @Override
+ public boolean onGesture(int gestureId) {
+ /* do nothing */
+ return false;
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ mLastEventTimeMillis = event.getEventTime();
+ if (mWaitingForEventDelivery) {
+ mEventQueue.add(AccessibilityEvent.obtain(event));
+ }
+ mLock.notifyAll();
+ }
+ // Calling out only without a lock held.
+ final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
+ if (listener != null) {
+ listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
new file mode 100644
index 0000000..9b5857f
--- /dev/null
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.hardware.input.InputManager;
+import android.os.Binder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.view.IWindowManager;
+import android.view.InputEvent;
+import android.view.Surface;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.IAccessibilityManager;
+
+/**
+ * This is a remote object that is passed from the shell to an instrumentation
+ * for enabling access to privileged operations which the shell can do and the
+ * instrumentation cannot. These privileged operations are needed for implementing
+ * a {@link UiAutomation} that enables across application testing by simulating
+ * user actions and performing screen introspection.
+ *
+ * @hide
+ */
+public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
+
+ private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
+
+ private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService(Service.WINDOW_SERVICE));
+
+ private final Object mLock = new Object();
+
+ private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
+
+ private IAccessibilityServiceClient mClient;
+
+ private boolean mIsShutdown;
+
+ private int mOwningUid;
+
+ public void connect(IAccessibilityServiceClient client) {
+ if (client == null) {
+ throw new IllegalArgumentException("Client cannot be null!");
+ }
+ synchronized (mLock) {
+ throwIfShutdownLocked();
+ if (isConnectedLocked()) {
+ throw new IllegalStateException("Already connected.");
+ }
+ mOwningUid = Binder.getCallingUid();
+ registerUiTestAutomationServiceLocked(client);
+ storeRotationStateLocked();
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ if (!isConnectedLocked()) {
+ throw new IllegalStateException("Already disconnected.");
+ }
+ mOwningUid = -1;
+ unregisterUiTestAutomationServiceLocked();
+ restoreRotationStateLocked();
+ }
+ }
+
+ @Override
+ public boolean injectInputEvent(InputEvent event, boolean sync) {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
+ : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return InputManager.getInstance().injectInputEvent(event, mode);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean setRotation(int rotation) {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (rotation == UiAutomation.ROTATION_UNFREEZE) {
+ mWindowManager.thawRotation();
+ } else {
+ mWindowManager.freezeRotation(rotation);
+ }
+ return true;
+ } catch (RemoteException re) {
+ /* ignore */
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return false;
+ }
+
+ @Override
+ public Bitmap takeScreenshot(int width, int height) {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return Surface.screenshot(width, height);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ mIsShutdown = true;
+ if (isConnectedLocked()) {
+ disconnect();
+ }
+ }
+ }
+
+ private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) {
+ IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
+ info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
+ try {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ manager.registerUiTestAutomationService(client, info);
+ mClient = client;
+ } catch (RemoteException re) {
+ throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
+ }
+ }
+
+ private void unregisterUiTestAutomationServiceLocked() {
+ IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+ try {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ manager.unregisterUiTestAutomationService(mClient);
+ mClient = null;
+ } catch (RemoteException re) {
+ throw new IllegalStateException("Error while unregistering UiTestAutomationService",
+ re);
+ }
+ }
+
+ private void storeRotationStateLocked() {
+ try {
+ if (mWindowManager.isRotationFrozen()) {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ mInitialFrozenRotation = mWindowManager.getRotation();
+ }
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ private void restoreRotationStateLocked() {
+ try {
+ if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ mWindowManager.freezeRotation(mInitialFrozenRotation);
+ } else {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ mWindowManager.thawRotation();
+ }
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ private boolean isConnectedLocked() {
+ return mClient != null;
+ }
+
+ private void throwIfShutdownLocked() {
+ if (mIsShutdown) {
+ throw new IllegalStateException("Connection shutdown!");
+ }
+ }
+
+ private void throwIfNotConnectedLocked() {
+ if (!isConnectedLocked()) {
+ throw new IllegalStateException("Not connected!");
+ }
+ }
+
+ private void throwIfCalledByNotTrustedUidLocked() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
+ && callingUid != 0 /*root*/) {
+ throw new SecurityException("Calling from not trusted UID!");
+ }
+ }
+}
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 5ad60ec..222578a 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -48,10 +48,10 @@
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken;
- private native void nativeInit();
- private native void nativeDestroy();
- private native void nativePollOnce(int ptr, int timeoutMillis);
- private native void nativeWake(int ptr);
+ private native static int nativeInit();
+ private native static void nativeDestroy(int ptr);
+ private native static void nativePollOnce(int ptr, int timeoutMillis);
+ private native static void nativeWake(int ptr);
/**
* Callback interface for discovering when a thread is going to block
@@ -102,18 +102,25 @@
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
- nativeInit();
+ mPtr = nativeInit();
}
@Override
protected void finalize() throws Throwable {
try {
- nativeDestroy();
+ dispose();
} finally {
super.finalize();
}
}
+ private void dispose() {
+ if (mPtr != 0) {
+ nativeDestroy(mPtr);
+ mPtr = 0;
+ }
+ }
+
final Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
@@ -126,6 +133,7 @@
synchronized (this) {
if (mQuiting) {
+ dispose();
return null;
}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index ba82d79..81c25d8 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -16,8 +16,8 @@
package android.view;
-import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
-
+import android.app.ActivityThread;
+import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
@@ -34,6 +34,7 @@
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Predicate;
import java.util.ArrayList;
import java.util.HashMap;
@@ -49,7 +50,7 @@
*/
final class AccessibilityInteractionController {
- private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
+ private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
private final Handler mHandler;
@@ -69,6 +70,8 @@
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
+ private AddNodeInfosForViewId mAddNodeInfosForViewId;
+
public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
Looper looper = viewRootImpl.mHandler.getLooper();
mMyLooperThreadId = looper.getThread().getId();
@@ -135,8 +138,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
root = mViewRootImpl.mView;
@@ -148,7 +150,7 @@
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
if (spec != null) {
spec.recycle();
@@ -161,19 +163,19 @@
}
}
- public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
- int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
+ String viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
- message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID;
message.arg1 = flags;
message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
SomeArgs args = SomeArgs.obtain();
- args.argi1 = viewId;
- args.argi2 = interactionId;
+ args.argi1 = interactionId;
args.arg1 = callback;
args.arg2 = spec;
+ args.arg3 = viewId;
message.obj = args;
@@ -189,26 +191,26 @@
}
}
- private void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
+ private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
final int flags = message.arg1;
final int accessibilityViewId = message.arg2;
SomeArgs args = (SomeArgs) message.obj;
- final int viewId = args.argi1;
- final int interactionId = args.argi2;
+ final int interactionId = args.argi1;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+ final String viewId = (String) args.arg3;
args.recycle();
- AccessibilityNodeInfo info = null;
+ final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
try {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -216,19 +218,31 @@
root = mViewRootImpl.mView;
}
if (root != null) {
- View target = root.findViewById(viewId);
- if (target != null && isShown(target)) {
- info = target.createAccessibilityNodeInfo();
+ int resolvedViewId = root.getContext().getResources().getIdentifier(
+ viewId, "id", root.getContext().getPackageName());
+ if (resolvedViewId <= 0) {
+ resolvedViewId = ((Context) ActivityThread.currentActivityThread()
+ .getSystemContext()).getResources()
+ .getIdentifier(viewId, "id", "android");
}
+ if (resolvedViewId <= 0) {
+ return;
+ }
+ if (mAddNodeInfosForViewId == null) {
+ mAddNodeInfosForViewId = new AddNodeInfosForViewId();
+ }
+ mAddNodeInfosForViewId.init(resolvedViewId, infos);
+ root.findViewByPredicate(mAddNodeInfosForViewId);
+ mAddNodeInfosForViewId.reset();
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
if (spec != null) {
spec.recycle();
}
- callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
}
@@ -281,8 +295,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -325,7 +338,7 @@
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
if (spec != null) {
spec.recycle();
@@ -384,8 +397,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -426,7 +438,7 @@
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
applyAppScaleAndMagnificationSpecIfNeeded(focused, spec);
if (spec != null) {
spec.recycle();
@@ -484,8 +496,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -500,7 +511,7 @@
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
applyAppScaleAndMagnificationSpecIfNeeded(next, spec);
if (spec != null) {
spec.recycle();
@@ -561,8 +572,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View target = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
target = findViewByAccessibilityId(accessibilityViewId);
@@ -580,7 +590,7 @@
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
callback.setPerformAccessibilityActionResult(succeeded, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -690,20 +700,20 @@
private final ArrayList<View> mTempViewList = new ArrayList<View>();
- public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
+ public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
List<AccessibilityNodeInfo> outInfos) {
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
if (provider == null) {
AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
if (root != null) {
outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
prefetchPredecessorsOfRealNode(view, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
prefetchSiblingsOfRealNode(view, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
prefetchDescendantsOfRealNode(view, outInfos);
}
}
@@ -711,13 +721,13 @@
AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
if (root != null) {
outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
prefetchDescendantsOfVirtualNode(root, provider, outInfos);
}
}
@@ -920,7 +930,7 @@
private class PrivateHandler extends Handler {
private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
- private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID = 3;
private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
private final static int MSG_FIND_FOCUS = 5;
private final static int MSG_FOCUS_SEARCH = 6;
@@ -937,8 +947,8 @@
return "MSG_PERFORM_ACCESSIBILITY_ACTION";
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
- return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID";
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
case MSG_FIND_FOCUS:
@@ -960,8 +970,8 @@
case MSG_PERFORM_ACCESSIBILITY_ACTION: {
perfromAccessibilityActionUiThread(message);
} break;
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
- findAccessibilityNodeInfoByViewIdUiThread(message);
+ case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: {
+ findAccessibilityNodeInfosByViewIdUiThread(message);
} break;
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
findAccessibilityNodeInfosByTextUiThread(message);
@@ -977,4 +987,27 @@
}
}
}
+
+ private final class AddNodeInfosForViewId implements Predicate<View> {
+ private int mViewId = View.NO_ID;
+ private List<AccessibilityNodeInfo> mInfos;
+
+ public void init(int viewId, List<AccessibilityNodeInfo> infos) {
+ mViewId = viewId;
+ mInfos = infos;
+ }
+
+ public void reset() {
+ mViewId = View.NO_ID;
+ mInfos = null;
+ }
+
+ @Override
+ public boolean apply(View view) {
+ if (view.getId() == mViewId && isShown(view)) {
+ mInfos.add(view.createAccessibilityNodeInfo());
+ }
+ return false;
+ }
+ }
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 1ee2bb3..a9ad97f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -190,6 +190,13 @@
void thawRotation();
/**
+ * Gets whether the rotation is frozen.
+ *
+ * @return Whether the rotation is frozen.
+ */
+ boolean isRotationFrozen();
+
+ /**
* Create a screenshot of the applications currently displayed.
*/
Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a4e4f37..b9babdc 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,7 @@
package android.view;
+import android.app.ActivityThread;
import android.content.ClipData;
import android.content.Context;
import android.content.res.Configuration;
@@ -5007,6 +5008,25 @@
if (label != null) {
info.setLabeledBy(label);
}
+
+ if ((mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0) {
+ String viewId = null;
+ try {
+ viewId = getResources().getResourceName(mID);
+ } catch (Resources.NotFoundException nfe) {
+ /* ignore */
+ }
+ if (viewId == null) {
+ try {
+ viewId = ((Context) ActivityThread.currentActivityThread()
+ .getSystemContext()).getResources().getResourceName(mID);
+ } catch (Resources.NotFoundException nfe) {
+ /* ignore */
+ }
+ }
+ info.setViewId(viewId);
+ }
}
if (mLabelForId != View.NO_ID) {
@@ -6838,7 +6858,9 @@
*/
public boolean includeForAccessibility() {
if (mAttachInfo != null) {
- return mAttachInfo.mIncludeNotImportantViews || isImportantForAccessibility();
+ return (mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+ || isImportantForAccessibility();
}
return false;
}
@@ -18004,10 +18026,12 @@
int mAccessibilityWindowId = View.NO_ID;
/**
- * Whether to ingore not exposed for accessibility Views when
- * reporting the view tree to accessibility services.
+ * Flags related to accessibility processing.
+ *
+ * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS
*/
- boolean mIncludeNotImportantViews;
+ int mAccessibilityFetchFlags;
/**
* The drawable for highlighting accessibility focus.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9f075c4..ba9eb89 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5482,15 +5482,16 @@
}
@Override
- public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId,
- int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
+ String viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId,
- interactionId, callback, flags, interrogatingPid, interrogatingTid,
- spec);
+ .findAccessibilityNodeInfosByViewIdClientThread(accessibilityNodeId,
+ viewId, interactionId, callback, flags, interrogatingPid,
+ interrogatingTid, spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 9377cfa..02be4db 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1035,6 +1035,16 @@
public void keepScreenOnStoppedLw();
/**
+ * Gets the current user rotation mode.
+ *
+ * @return The rotation mode.
+ *
+ * @see WindowManagerPolicy#USER_ROTATION_LOCKED
+ * @see WindowManagerPolicy#USER_ROTATION_FREE
+ */
+ public int getUserRotationMode();
+
+ /**
* Inform the policy that the user has chosen a preferred orientation ("rotation lock").
*
* @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 67df684..84d7e72 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -230,23 +230,25 @@
* where to start the search. Use
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
- * @param viewId The id of the view.
- * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return An list of {@link AccessibilityNodeInfo} if found, empty list otherwise.
*/
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int connectionId,
- int accessibilityWindowId, long accessibilityNodeId, int viewId) {
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(int connectionId,
+ int accessibilityWindowId, long accessibilityNodeId, String viewId) {
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final boolean success =connection.findAccessibilityNodeInfoByViewId(
+ final boolean success = connection.findAccessibilityNodeInfosByViewId(
accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
Thread.currentThread().getId());
if (success) {
- AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
- return info;
+ if (infos != null) {
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ return infos;
+ }
}
} else {
if (DEBUG) {
@@ -259,7 +261,7 @@
+ " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
}
}
- return null;
+ return Collections.emptyList();
}
/**
@@ -291,8 +293,10 @@
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
- return infos;
+ if (infos != null) {
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ return infos;
+ }
}
} else {
if (DEBUG) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 6c03280..6d0a237 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -16,6 +16,7 @@
package android.view.accessibility;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
@@ -78,7 +79,10 @@
public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
/** @hide */
- public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+
+ /** @hide */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
// Actions.
@@ -375,6 +379,7 @@
private CharSequence mClassName;
private CharSequence mText;
private CharSequence mContentDescription;
+ private CharSequence mViewId;
private final SparseLongArray mChildNodeIds = new SparseLongArray();
private int mActions;
@@ -729,6 +734,37 @@
}
/**
+ * Finds {@link AccessibilityNodeInfo}s by the fully qualified view id's resource
+ * name where a fully qualified id is of the from "package:id/id_resource_name".
+ * For example, if the target application's package is "foo.bar" and the id
+ * resource name is "baz", the fully qualified resource id is "foo.bar:id/baz".
+ *
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the fully qualified view id if an {@link AccessibilityNodeInfo}
+ * the client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ *
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return A list of node info.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return Collections.emptyList();
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfosByViewId(mConnectionId, mWindowId, mSourceNodeId,
+ viewId);
+ }
+
+ /**
* Gets the parent.
* <p>
* <strong>Note:</strong> It is a client responsibility to recycle the
@@ -1373,6 +1409,38 @@
}
/**
+ * Sets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param viewId The id resource name.
+ */
+ public void setViewId(CharSequence viewId) {
+ enforceNotSealed();
+ mViewId = viewId;
+ }
+
+ /**
+ * Gets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the source view id of an {@link AccessibilityNodeInfo} the
+ * client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+
+ * @return The id resource name.
+ */
+ public CharSequence getViewId() {
+ return mViewId;
+ }
+
+ /**
* Gets the value of a boolean property.
*
* @param property The property.
@@ -1614,6 +1682,7 @@
parcel.writeCharSequence(mClassName);
parcel.writeCharSequence(mText);
parcel.writeCharSequence(mContentDescription);
+ parcel.writeCharSequence(mViewId);
// Since instances of this class are fetched via synchronous i.e. blocking
// calls in IPCs we always recycle as soon as the instance is marshaled.
@@ -1639,6 +1708,7 @@
mClassName = other.mClassName;
mText = other.mText;
mContentDescription = other.mContentDescription;
+ mViewId = other.mViewId;
mActions= other.mActions;
mBooleanProperties = other.mBooleanProperties;
mMovementGranularities = other.mMovementGranularities;
@@ -1689,6 +1759,7 @@
mClassName = parcel.readCharSequence();
mText = parcel.readCharSequence();
mContentDescription = parcel.readCharSequence();
+ mViewId = parcel.readCharSequence();
}
/**
@@ -1711,6 +1782,7 @@
mClassName = null;
mText = null;
mContentDescription = null;
+ mViewId = null;
mActions = 0;
}
@@ -1855,6 +1927,7 @@
builder.append("; className: ").append(mClassName);
builder.append("; text: ").append(mText);
builder.append("; contentDescription: ").append(mContentDescription);
+ builder.append("; viewId: ").append(mViewId);
builder.append("; checkable: ").append(isCheckable());
builder.append("; checked: ").append(isChecked());
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index c313b07..8d15472 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -33,9 +33,9 @@
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
long interrogatingTid, in MagnificationSpec spec);
- void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid, in MagnificationSpec spec);
+ void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, String viewId,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, in MagnificationSpec spec);
void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 57bf0d3..396fd68 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1374,9 +1374,11 @@
if (isEnabled()) {
if (getFirstVisiblePosition() > 0) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ info.setScrollable(true);
}
if (getLastVisiblePosition() < getCount() - 1) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.setScrollable(true);
}
}
}
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index a4dcac6..7540645 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -109,55 +109,34 @@
// ----------------------------------------------------------------------------
-static NativeMessageQueue* android_os_MessageQueue_getNativeMessageQueue(JNIEnv* env,
- jobject messageQueueObj) {
+sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) {
jint intPtr = env->GetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr);
return reinterpret_cast<NativeMessageQueue*>(intPtr);
}
-static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
- NativeMessageQueue* nativeMessageQueue) {
- env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,
- reinterpret_cast<jint>(nativeMessageQueue));
-}
-
-sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) {
- NativeMessageQueue* nativeMessageQueue =
- android_os_MessageQueue_getNativeMessageQueue(env, messageQueueObj);
- return nativeMessageQueue;
-}
-
-static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
+static jint android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
- return;
+ return 0;
}
nativeMessageQueue->incStrong(env);
- android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
+ return reinterpret_cast<jint>(nativeMessageQueue);
}
-static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jobject obj) {
- NativeMessageQueue* nativeMessageQueue =
- android_os_MessageQueue_getNativeMessageQueue(env, obj);
- if (nativeMessageQueue) {
- android_os_MessageQueue_setNativeMessageQueue(env, obj, NULL);
- nativeMessageQueue->decStrong(env);
- }
+static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jclass clazz, jint ptr) {
+ NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
+ nativeMessageQueue->decStrong(env);
}
-static void throwQueueNotInitialized(JNIEnv* env) {
- jniThrowException(env, "java/lang/IllegalStateException", "Message queue not initialized");
-}
-
-static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
+static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
jint ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, timeoutMillis);
}
-static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj, jint ptr) {
+static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jint ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
return nativeMessageQueue->wake();
}
@@ -166,8 +145,8 @@
static JNINativeMethod gMessageQueueMethods[] = {
/* name, signature, funcPtr */
- { "nativeInit", "()V", (void*)android_os_MessageQueue_nativeInit },
- { "nativeDestroy", "()V", (void*)android_os_MessageQueue_nativeDestroy },
+ { "nativeInit", "()I", (void*)android_os_MessageQueue_nativeInit },
+ { "nativeDestroy", "(I)V", (void*)android_os_MessageQueue_nativeDestroy },
{ "nativePollOnce", "(II)V", (void*)android_os_MessageQueue_nativePollOnce },
{ "nativeWake", "(I)V", (void*)android_os_MessageQueue_nativeWake }
};
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 41f8536..f8b26bc 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1241,13 +1241,6 @@
</intent-filter>
</activity>
- <activity android:name="android.accessibilityservice.InterrogationActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
<activity android:name="android.animation.BasicAnimatorActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java
deleted file mode 100644
index a9f144b..0000000
--- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivity.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accessibilityservice;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.View;
-
-import com.android.frameworks.coretests.R;
-
-/**
- * Activity for testing the accessibility APIs for "interrogation" of
- * the screen content. These APIs allow exploring the screen and
- * requesting an action to be performed on a given view from an
- * AccessiiblityService.
- */
-public class InterrogationActivity extends Activity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.interrogation_activity);
-
- findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- /* do nothing */
- }
- });
- findViewById(R.id.button5).setOnLongClickListener(new View.OnLongClickListener() {
- public boolean onLongClick(View v) {
- return true;
- }
- });
- }
-}
diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
deleted file mode 100644
index 3dc140b..0000000
--- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java
+++ /dev/null
@@ -1,472 +0,0 @@
-/**
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accessibilityservice;
-
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT;
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
-
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import com.android.frameworks.coretests.R;
-import com.android.internal.util.Predicate;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-
-/**
- * Activity for testing the accessibility APIs for "interrogation" of
- * the screen content. These APIs allow exploring the screen and
- * requesting an action to be performed on a given view from an
- * AccessiiblityService.
- */
-public class InterrogationActivityTest
- extends ActivityInstrumentationTestCase2<InterrogationActivity> {
- private static final boolean DEBUG = false;
-
- private static String LOG_TAG = "InterrogationActivityTest";
-
- // Timeout for the accessibility state of an Activity to be fully initialized.
- private static final int TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS = 5000;
-
- // Timeout for which non getting accessibility events considers the app idle.
- private static final long IDLE_EVENT_TIME_DELTA_MILLIS = 200;
-
- // Timeout in which to wait for idle device.
- private static final long GLOBAL_IDLE_DETECTION_TIMEOUT_MILLIS = 1000;
-
- // Handle to a connection to the AccessibilityManagerService
- private UiTestAutomationBridge mUiTestAutomationBridge;
-
- public InterrogationActivityTest() {
- super(InterrogationActivity.class);
- }
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mUiTestAutomationBridge = new UiTestAutomationBridge();
- mUiTestAutomationBridge.connect();
- mUiTestAutomationBridge.waitForIdle(IDLE_EVENT_TIME_DELTA_MILLIS,
- GLOBAL_IDLE_DETECTION_TIMEOUT_MILLIS);
- mUiTestAutomationBridge.executeCommandAndWaitForAccessibilityEvent(new Runnable() {
- // wait for the first accessibility event
- @Override
- public void run() {
- // bring up the activity
- getActivity();
- }
- },
- new Predicate<AccessibilityEvent>() {
- @Override
- public boolean apply(AccessibilityEvent event) {
- return (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- && event.getPackageName().equals(getActivity().getPackageName()));
- }
- },
- TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS);
- }
-
- @Override
- public void tearDown() throws Exception {
- mUiTestAutomationBridge.disconnect();
- super.tearDown();
- }
-
- @LargeTest
- public void testFindAccessibilityNodeInfoByViewId() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- AccessibilityNodeInfo button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertNotNull(button);
- assertEquals(0, button.getChildCount());
-
- // bounds
- Rect bounds = new Rect();
- button.getBoundsInParent(bounds);
- assertEquals(0, bounds.left);
- assertEquals(0, bounds.top);
- assertEquals(160, bounds.right);
- assertEquals(100, bounds.bottom);
-
- // char sequence attributes
- assertEquals("com.android.frameworks.coretests", button.getPackageName());
- assertEquals("android.widget.Button", button.getClassName());
- assertEquals("Button5", button.getText());
- assertNull(button.getContentDescription());
-
- // boolean attributes
- assertTrue(button.isFocusable());
- assertTrue(button.isClickable());
- assertTrue(button.isEnabled());
- assertFalse(button.isFocused());
- assertTrue(button.isClickable());
- assertFalse(button.isPassword());
- assertFalse(button.isSelected());
- assertFalse(button.isCheckable());
- assertFalse(button.isChecked());
-
- // actions
- assertEquals(ACTION_FOCUS | ACTION_SELECT | ACTION_CLEAR_SELECTION,
- button.getActions());
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewId: "
- + elapsedTimeMillis + "ms");
- }
- }
- }
-
- @LargeTest
- public void testFindAccessibilityNodeInfoByViewText() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- // find a view by text
- List<AccessibilityNodeInfo> buttons = mUiTestAutomationBridge
- .findAccessibilityNodeInfosByTextInActiveWindow("butto");
- assertEquals(9, buttons.size());
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewText: "
- + elapsedTimeMillis + "ms");
- }
- }
- }
-
- @LargeTest
- public void testFindAccessibilityNodeInfoByViewTextContentDescription() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- // find a view by text
- List<AccessibilityNodeInfo> buttons = mUiTestAutomationBridge
- .findAccessibilityNodeInfosByTextInActiveWindow("contentDescription");
- assertEquals(1, buttons.size());
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewTextContentDescription: "
- + elapsedTimeMillis + "ms");
- }
- }
- }
-
- @LargeTest
- public void testTraverseAllViews() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- // make list of expected nodes
- List<String> classNameAndTextList = new ArrayList<String>();
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.LinearLayout");
- classNameAndTextList.add("android.widget.ButtonButton1");
- classNameAndTextList.add("android.widget.ButtonButton2");
- classNameAndTextList.add("android.widget.ButtonButton3");
- classNameAndTextList.add("android.widget.ButtonButton4");
- classNameAndTextList.add("android.widget.ButtonButton5");
- classNameAndTextList.add("android.widget.ButtonButton6");
- classNameAndTextList.add("android.widget.ButtonButton7");
- classNameAndTextList.add("android.widget.ButtonButton8");
- classNameAndTextList.add("android.widget.ButtonButton9");
-
- AccessibilityNodeInfo root = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root);
- assertNotNull("We must find the existing root.", root);
-
- Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
- fringe.add(root);
-
- // do a BFS traversal and check nodes
- while (!fringe.isEmpty()) {
- AccessibilityNodeInfo current = fringe.poll();
-
- CharSequence className = current.getClassName();
- CharSequence text = current.getText();
- String receivedClassNameAndText = className.toString()
- + ((text != null) ? text.toString() : "");
- String expectedClassNameAndText = classNameAndTextList.remove(0);
-
- assertEquals("Did not get the expected node info",
- expectedClassNameAndText, receivedClassNameAndText);
-
- final int childCount = current.getChildCount();
- for (int i = 0; i < childCount; i++) {
- AccessibilityNodeInfo child = current.getChild(i);
- fringe.add(child);
- }
- }
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testTraverseAllViews: " + elapsedTimeMillis + "ms");
- }
- }
- }
-
- @LargeTest
- public void testPerformAccessibilityActionFocus() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- // find a view and make sure it is not focused
- AccessibilityNodeInfo button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertFalse(button.isFocused());
-
- // focus the view
- assertTrue(button.performAction(ACTION_FOCUS));
-
- // find the view again and make sure it is focused
- button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertTrue(button.isFocused());
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testPerformAccessibilityActionFocus: " + elapsedTimeMillis + "ms");
- }
- }
- }
-
- @LargeTest
- public void testPerformAccessibilityActionClearFocus() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- // find a view and make sure it is not focused
- AccessibilityNodeInfo button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertFalse(button.isFocused());
-
- // focus the view
- assertTrue(button.performAction(ACTION_FOCUS));
-
- // find the view again and make sure it is focused
- button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertTrue(button.isFocused());
-
- // unfocus the view
- assertTrue(button.performAction(ACTION_CLEAR_FOCUS));
-
- // find the view again and make sure it is not focused
- button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertFalse(button.isFocused());
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testPerformAccessibilityActionClearFocus: "
- + elapsedTimeMillis + "ms");
- }
- }
- }
-
- @LargeTest
- public void testPerformAccessibilityActionSelect() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- // find a view and make sure it is not selected
- AccessibilityNodeInfo button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertFalse(button.isSelected());
-
- // select the view
- assertTrue(button.performAction(ACTION_SELECT));
-
- // find the view again and make sure it is selected
- button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertTrue(button.isSelected());
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testPerformAccessibilityActionSelect: " + elapsedTimeMillis + "ms");
- }
- }
- }
-
- @LargeTest
- public void testPerformAccessibilityActionClearSelection() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- // find a view and make sure it is not selected
- AccessibilityNodeInfo button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertFalse(button.isSelected());
-
- // select the view
- assertTrue(button.performAction(ACTION_SELECT));
-
- // find the view again and make sure it is selected
- button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertTrue(button.isSelected());
-
- // unselect the view
- assertTrue(button.performAction(ACTION_CLEAR_SELECTION));
-
- // find the view again and make sure it is not selected
- button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertFalse(button.isSelected());
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testPerformAccessibilityActionClearSelection: "
- + elapsedTimeMillis + "ms");
- }
- }
- }
-
- @LargeTest
- public void testAccessibilityEventGetSource() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- // find a view and make sure it is not focused
- final AccessibilityNodeInfo button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertFalse(button.isFocused());
-
- AccessibilityEvent event = mUiTestAutomationBridge
- .executeCommandAndWaitForAccessibilityEvent(new Runnable() {
- @Override
- public void run() {
- // focus the view
- assertTrue(button.performAction(ACTION_FOCUS));
- }
- },
- new Predicate<AccessibilityEvent>() {
- @Override
- public boolean apply(AccessibilityEvent event) {
- return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED
- && event.getPackageName().equals(getActivity().getPackageName())
- && event.getText().get(0).equals(button.getText()));
- }
- },
- TIMEOUT_PROPAGATE_ACCESSIBILITY_EVENT_MILLIS);
-
- // check the last event
- assertNotNull(event);
-
- // check that last event source
- AccessibilityNodeInfo source = event.getSource();
- assertNotNull(source);
-
- // bounds
- Rect buttonBounds = new Rect();
- button.getBoundsInParent(buttonBounds);
- Rect sourceBounds = new Rect();
- source.getBoundsInParent(sourceBounds);
-
- assertEquals(buttonBounds.left, sourceBounds.left);
- assertEquals(buttonBounds.right, sourceBounds.right);
- assertEquals(buttonBounds.top, sourceBounds.top);
- assertEquals(buttonBounds.bottom, sourceBounds.bottom);
-
- // char sequence attributes
- assertEquals(button.getPackageName(), source.getPackageName());
- assertEquals(button.getClassName(), source.getClassName());
- assertEquals(button.getText(), source.getText());
- assertSame(button.getContentDescription(), source.getContentDescription());
-
- // boolean attributes
- assertSame(button.isFocusable(), source.isFocusable());
- assertSame(button.isClickable(), source.isClickable());
- assertSame(button.isEnabled(), source.isEnabled());
- assertNotSame(button.isFocused(), source.isFocused());
- assertSame(button.isLongClickable(), source.isLongClickable());
- assertSame(button.isPassword(), source.isPassword());
- assertSame(button.isSelected(), source.isSelected());
- assertSame(button.isCheckable(), source.isCheckable());
- assertSame(button.isChecked(), source.isChecked());
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testAccessibilityEventGetSource: " + elapsedTimeMillis + "ms");
- }
- }
- }
-
- @LargeTest
- public void testObjectContract() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- // find a view and make sure it is not focused
- AccessibilityNodeInfo button = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.button5);
- assertNotNull(button);
- AccessibilityNodeInfo parent = button.getParent();
- final int childCount = parent.getChildCount();
- for (int i = 0; i < childCount; i++) {
- AccessibilityNodeInfo child = parent.getChild(i);
- assertNotNull(child);
- if (child.equals(button)) {
- assertEquals("Equal objects must have same hasCode.", button.hashCode(),
- child.hashCode());
- return;
- }
- }
- fail("Parent's children do not have the info whose parent is the parent.");
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testObjectContract: " + elapsedTimeMillis + "ms");
- }
- }
- }
-
- @LargeTest
- public void testGetRootAccessibilityNodeInfoInActiveWindow() throws Exception {
- final long startTimeMillis = SystemClock.uptimeMillis();
- try {
- // get the root via the designated API
- AccessibilityNodeInfo fetched = mUiTestAutomationBridge
- .getRootAccessibilityNodeInfoInActiveWindow();
- assertNotNull(fetched);
-
- // get the root via traversal
- AccessibilityNodeInfo expected = mUiTestAutomationBridge
- .findAccessibilityNodeInfoByViewIdInActiveWindow(R.id.root);
- while (true) {
- AccessibilityNodeInfo parent = expected.getParent();
- if (parent == null) {
- break;
- }
- expected = parent;
- }
- assertNotNull(expected);
-
- assertEquals("The node with id \"root\" should be the root.", expected, fetched);
- } finally {
- if (DEBUG) {
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- Log.i(LOG_TAG, "testGetRootAccessibilityNodeInfoInActiveWindow: "
- + elapsedTimeMillis + "ms");
- }
- }
- }
-}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 0d409d5..d1fb2b0 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -4141,6 +4141,11 @@
return rotation == mPortraitRotation || rotation == mUpsideDownRotation;
}
+ public int getUserRotationMode() {
+ return Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.USER_ROTATION, WindowManagerPolicy.USER_ROTATION_FREE,
+ UserHandle.USER_CURRENT);
+ }
// User rotation: to be used when all else fails in assigning an orientation to the device
public void setUserRotationMode(int mode, int rot) {
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0725df0..b7c3450 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.accessibility;
import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
-import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -518,6 +517,10 @@
ComponentName componentName = new ComponentName("foo.bar",
"AutomationAccessibilityService");
synchronized (mLock) {
+ if (mUiAutomationService != null) {
+ throw new IllegalStateException("UiAutomationService " + serviceClient
+ + "already registered!");
+ }
// If an automation services is connected to the system all services are stopped
// so the automation one is the only one running. Settings are not changed so when
// the automation service goes away the state is restored from the settings.
@@ -556,7 +559,8 @@
synchronized (mLock) {
UserState userState = getCurrentUserStateLocked();
// Stash the old state so we can restore it when the keyguard is gone.
- mTempStateChangeForCurrentUserMemento.initialize(mCurrentUserId, getCurrentUserStateLocked());
+ mTempStateChangeForCurrentUserMemento.initialize(mCurrentUserId,
+ getCurrentUserStateLocked());
// Set the temporary state.
userState.mIsAccessibilityEnabled = true;
userState.mIsTouchExplorationEnabled= touchExplorationEnabled;
@@ -579,6 +583,9 @@
&& serviceClient != null && mUiAutomationService.mServiceInterface
.asBinder() == serviceClient.asBinder()) {
mUiAutomationService.binderDied();
+ } else {
+ throw new IllegalStateException("UiAutomationService " + serviceClient
+ + " not registered!");
}
}
}
@@ -935,7 +942,8 @@
}
if (!event.isImportantForAccessibility()
- && !service.mIncludeNotImportantViews) {
+ && (service.mFetchFlags
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
return false;
}
@@ -1486,7 +1494,7 @@
boolean mRequestTouchExplorationMode;
- boolean mIncludeNotImportantViews;
+ int mFetchFlags;
long mNotificationTimeout;
@@ -1565,10 +1573,15 @@
if (mIsAutomation || info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion
>= Build.VERSION_CODES.JELLY_BEAN) {
- mIncludeNotImportantViews =
- (info.flags & FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mFetchFlags |= (info.flags
+ & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0 ?
+ AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
}
+ mFetchFlags |= (info.flags
+ & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0 ?
+ AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS : 0;
+
mRequestTouchExplorationMode = (info.flags
& AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
@@ -1664,8 +1677,8 @@
}
@Override
- public boolean findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
- long accessibilityNodeId, int viewId, int interactionId,
+ public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, String viewIdResName, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
final int resolvedWindowId;
@@ -1689,14 +1702,13 @@
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
- connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId,
- interactionId, callback, flags, interrogatingPid, interrogatingTid, spec);
+ connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId,
+ viewIdResName, interactionId, callback, mFetchFlags, interrogatingPid,
+ interrogatingTid, spec);
return true;
} catch (RemoteException re) {
if (DEBUG) {
@@ -1735,14 +1747,13 @@
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,
- interactionId, callback, flags, interrogatingPid, interrogatingTid, spec);
+ interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid,
+ spec);
return true;
} catch (RemoteException re) {
if (DEBUG) {
@@ -1781,15 +1792,13 @@
}
}
}
- final int allFlags = flags | ((mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0);
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
- interactionId, callback, allFlags, interrogatingPid, interrogatingTid,
- spec);
+ interactionId, callback, mFetchFlags | flags, interrogatingPid,
+ interrogatingTid, spec);
return true;
} catch (RemoteException re) {
if (DEBUG) {
@@ -1828,14 +1837,12 @@
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.findFocus(accessibilityNodeId, focusType, interactionId, callback,
- flags, interrogatingPid, interrogatingTid, spec);
+ mFetchFlags, interrogatingPid, interrogatingTid, spec);
return true;
} catch (RemoteException re) {
if (DEBUG) {
@@ -1874,14 +1881,12 @@
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.focusSearch(accessibilityNodeId, direction, interactionId, callback,
- flags, interrogatingPid, interrogatingTid, spec);
+ mFetchFlags, interrogatingPid, interrogatingTid, spec);
return true;
} catch (RemoteException re) {
if (DEBUG) {
@@ -1920,13 +1925,11 @@
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.performAccessibilityAction(accessibilityNodeId, action, arguments,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling performAccessibilityAction()");
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index e0046ad..ca60a93 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -51,6 +51,7 @@
import android.app.IServiceConnection;
import android.app.IStopUserCallback;
import android.app.IThumbnailReceiver;
+import android.app.IUiAutomationConnection;
import android.app.IUserSwitchObserver;
import android.app.Instrumentation;
import android.app.Notification;
@@ -162,7 +163,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
-public final class ActivityManagerService extends ActivityManagerNative
+public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
private static final String USER_DATA_DIR = "/data/user/";
static final String TAG = "ActivityManager";
@@ -4283,8 +4284,9 @@
}
thread.bindApplication(processName, appInfo, providers,
app.instrumentationClass, profileFile, profileFd, profileAutoStop,
- app.instrumentationArguments, app.instrumentationWatcher, testMode,
- enableOpenGlTrace, isRestrictedBackupMode || !normalMode, app.persistent,
+ app.instrumentationArguments, app.instrumentationWatcher,
+ app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
+ isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
mCoreSettingsObserver.getCoreSettingsLocked());
updateLruProcessLocked(app, false);
@@ -12228,7 +12230,8 @@
public boolean startInstrumentation(ComponentName className,
String profileFile, int flags, Bundle arguments,
- IInstrumentationWatcher watcher, int userId) {
+ IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
+ int userId) {
enforceNotIsolatedCaller("startInstrumentation");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, true, "startInstrumentation", null);
@@ -12282,6 +12285,7 @@
app.instrumentationProfileFile = profileFile;
app.instrumentationArguments = arguments;
app.instrumentationWatcher = watcher;
+ app.instrumentationUiAutomationConnection = uiAutomationConnection;
app.instrumentationResultClass = className;
Binder.restoreCallingIdentity(origId);
}
@@ -12324,7 +12328,15 @@
} catch (RemoteException e) {
}
}
+ if (app.instrumentationUiAutomationConnection != null) {
+ try {
+ app.instrumentationUiAutomationConnection.shutdown();
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
app.instrumentationWatcher = null;
+ app.instrumentationUiAutomationConnection = null;
app.instrumentationClass = null;
app.instrumentationInfo = null;
app.instrumentationProfileFile = null;
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 7fbab04..a32af2f 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -22,6 +22,7 @@
import android.app.Dialog;
import android.app.IApplicationThread;
import android.app.IInstrumentationWatcher;
+import android.app.IUiAutomationConnection;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -95,6 +96,7 @@
ApplicationInfo instrumentationInfo; // the application being instrumented
String instrumentationProfileFile; // where to save profiling
IInstrumentationWatcher instrumentationWatcher; // who is waiting
+ IUiAutomationConnection instrumentationUiAutomationConnection; // Connection to use the UI introspection APIs.
Bundle instrumentationArguments;// as given to us
ComponentName instrumentationResultClass;// copy of instrumentationClass
boolean usingWrapper; // Set to true when process was launched with a wrapper attached
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index c7c2c62..8fa6de5 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -169,7 +169,9 @@
startAccessoryMode();
}
- if ("1".equals(SystemProperties.get("ro.adb.secure"))) {
+ boolean secureAdbEnabled = SystemProperties.getBoolean("ro.adb.secure", false);
+ boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt"));
+ if (secureAdbEnabled && !dataEncrypted) {
mDebuggingManager = new UsbDebuggingManager(context);
}
}
diff --git a/services/java/com/android/server/wm/DisplayMagnifier.java b/services/java/com/android/server/wm/DisplayMagnifier.java
index d3c01f0..6e876f6 100644
--- a/services/java/com/android/server/wm/DisplayMagnifier.java
+++ b/services/java/com/android/server/wm/DisplayMagnifier.java
@@ -228,6 +228,10 @@
return spec;
}
+ public void destroyLocked() {
+ mMagnifedViewport.destroyWindow();
+ }
+
/** NOTE: This has to be called within a surface transaction. */
public void drawMagnifiedRegionBorderIfNeededLocked() {
mMagnifedViewport.drawWindowIfNeededLocked();
@@ -258,7 +262,7 @@
private final int mBorderWidth;
private final int mHalfBorderWidth;
- private ViewportWindow mWindow;
+ private final ViewportWindow mWindow;
private boolean mFullRedrawNeeded;
@@ -459,6 +463,10 @@
mWindow.drawIfNeeded();
}
+ public void destroyWindow() {
+ mWindow.releaseSurface();
+ }
+
private final class ViewportWindow {
private static final String SURFACE_TITLE = "Magnification Overlay";
@@ -640,6 +648,10 @@
}
}
}
+
+ public void releaseSurface() {
+ mSurface.release();
+ }
}
}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index ff2dc0f..1e5cd54 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -3039,7 +3039,10 @@
mDisplayMagnifier = new DisplayMagnifier(this, callbacks);
} else {
if (callbacks == null) {
- mDisplayMagnifier = null;
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.destroyLocked();
+ mDisplayMagnifier = null;
+ }
} else {
throw new IllegalStateException("Magnification callbacks already set!");
}
@@ -5598,6 +5601,11 @@
}
@Override
+ public boolean isRotationFrozen() {
+ return mPolicy.getUserRotationMode() == WindowManagerPolicy.USER_ROTATION_LOCKED;
+ }
+
+ @Override
public int watchRotation(IRotationWatcher watcher) {
final IBinder watcherBinder = watcher.asBinder();
IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp
index a97becf..57803e3 100644
--- a/services/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/jni/com_android_server_input_InputManagerService.cpp
@@ -975,6 +975,11 @@
static jint nativeInit(JNIEnv* env, jclass clazz,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
+ if (messageQueue == NULL) {
+ jniThrowRuntimeException(env, "MessageQueue is not initialized.");
+ return 0;
+ }
+
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(serviceObj);
diff --git a/test-runner/src/android/test/AndroidTestRunner.java b/test-runner/src/android/test/AndroidTestRunner.java
index 30876d0..aa7c677 100644
--- a/test-runner/src/android/test/AndroidTestRunner.java
+++ b/test-runner/src/android/test/AndroidTestRunner.java
@@ -21,6 +21,7 @@
import android.os.PerformanceCollector.PerformanceResultsWriter;
import com.google.android.collect.Lists;
+
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestListener;
diff --git a/test-runner/src/android/test/InstrumentationTestRunner.java b/test-runner/src/android/test/InstrumentationTestRunner.java
index 8e30875..91d04da 100644
--- a/test-runner/src/android/test/InstrumentationTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationTestRunner.java
@@ -390,12 +390,11 @@
}
/**
- * Get the Bundle object that contains the arguments
+ * Get the arguments passed to this instrumentation.
*
* @return the Bundle object
- * @hide
*/
- public Bundle getBundle(){
+ public Bundle getArguments() {
return mArguments;
}
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index e2cb65d..6b9f4c3 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -60,7 +60,7 @@
public void testMeasureStartUpTime() throws RemoteException {
InstrumentationTestRunner instrumentation =
(InstrumentationTestRunner)getInstrumentation();
- Bundle args = instrumentation.getBundle();
+ Bundle args = instrumentation.getArguments();
mAm = ActivityManagerNative.getDefault();
createMappings();
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index f012bcb..9f116fc 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -487,4 +487,10 @@
// TODO Auto-generated method stub
return null;
}
+
+ @Override
+ public boolean isRotationFrozen() throws RemoteException {
+ // TODO Auto-generated method stub
+ return false;
+ }
}
diff --git a/voip/java/android/net/rtp/AudioCodec.java b/voip/java/android/net/rtp/AudioCodec.java
deleted file mode 100644
index 85255c8..0000000
--- a/voip/java/android/net/rtp/AudioCodec.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.rtp;
-
-import java.util.Arrays;
-
-/**
- * This class defines a collection of audio codecs to be used with
- * {@link AudioStream}s. Their parameters are designed to be exchanged using
- * Session Description Protocol (SDP). Most of the values listed here can be
- * found in RFC 3551, while others are described in separated standards.
- *
- * <p>Few simple configurations are defined as public static instances for the
- * convenience of direct uses. More complicated ones could be obtained using
- * {@link #getCodec(int, String, String)}. For example, one can use the
- * following snippet to create a mode-1-only AMR codec.</p>
- * <pre>
- * AudioCodec codec = AudioCodec.getCodec(100, "AMR/8000", "mode-set=1");
- * </pre>
- *
- * @see AudioStream
- */
-public class AudioCodec {
- /**
- * The RTP payload type of the encoding.
- */
- public final int type;
-
- /**
- * The encoding parameters to be used in the corresponding SDP attribute.
- */
- public final String rtpmap;
-
- /**
- * The format parameters to be used in the corresponding SDP attribute.
- */
- public final String fmtp;
-
- /**
- * G.711 u-law audio codec.
- */
- public static final AudioCodec PCMU = new AudioCodec(0, "PCMU/8000", null);
-
- /**
- * G.711 a-law audio codec.
- */
- public static final AudioCodec PCMA = new AudioCodec(8, "PCMA/8000", null);
-
- /**
- * GSM Full-Rate audio codec, also known as GSM-FR, GSM 06.10, GSM, or
- * simply FR.
- */
- public static final AudioCodec GSM = new AudioCodec(3, "GSM/8000", null);
-
- /**
- * GSM Enhanced Full-Rate audio codec, also known as GSM-EFR, GSM 06.60, or
- * simply EFR.
- */
- public static final AudioCodec GSM_EFR = new AudioCodec(96, "GSM-EFR/8000", null);
-
- /**
- * Adaptive Multi-Rate narrowband audio codec, also known as AMR or AMR-NB.
- * Currently CRC, robust sorting, and interleaving are not supported. See
- * more details about these features in RFC 4867.
- */
- public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null);
-
- private static final AudioCodec[] sCodecs = {GSM_EFR, AMR, GSM, PCMU, PCMA};
-
- private AudioCodec(int type, String rtpmap, String fmtp) {
- this.type = type;
- this.rtpmap = rtpmap;
- this.fmtp = fmtp;
- }
-
- /**
- * Returns system supported audio codecs.
- */
- public static AudioCodec[] getCodecs() {
- return Arrays.copyOf(sCodecs, sCodecs.length);
- }
-
- /**
- * Creates an AudioCodec according to the given configuration.
- *
- * @param type The payload type of the encoding defined in RTP/AVP.
- * @param rtpmap The encoding parameters specified in the corresponding SDP
- * attribute, or null if it is not available.
- * @param fmtp The format parameters specified in the corresponding SDP
- * attribute, or null if it is not available.
- * @return The configured AudioCodec or {@code null} if it is not supported.
- */
- public static AudioCodec getCodec(int type, String rtpmap, String fmtp) {
- if (type < 0 || type > 127) {
- return null;
- }
-
- AudioCodec hint = null;
- if (rtpmap != null) {
- String clue = rtpmap.trim().toUpperCase();
- for (AudioCodec codec : sCodecs) {
- if (clue.startsWith(codec.rtpmap)) {
- String channels = clue.substring(codec.rtpmap.length());
- if (channels.length() == 0 || channels.equals("/1")) {
- hint = codec;
- }
- break;
- }
- }
- } else if (type < 96) {
- for (AudioCodec codec : sCodecs) {
- if (type == codec.type) {
- hint = codec;
- rtpmap = codec.rtpmap;
- break;
- }
- }
- }
-
- if (hint == null) {
- return null;
- }
- if (hint == AMR && fmtp != null) {
- String clue = fmtp.toLowerCase();
- if (clue.contains("crc=1") || clue.contains("robust-sorting=1") ||
- clue.contains("interleaving=")) {
- return null;
- }
- }
- return new AudioCodec(type, rtpmap, fmtp);
- }
-}
diff --git a/voip/java/android/net/rtp/AudioGroup.java b/voip/java/android/net/rtp/AudioGroup.java
deleted file mode 100644
index 8faeb88..0000000
--- a/voip/java/android/net/rtp/AudioGroup.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.rtp;
-
-import android.media.AudioManager;
-
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-
-/**
- * An AudioGroup is an audio hub for the speaker, the microphone, and
- * {@link AudioStream}s. Each of these components can be logically turned on
- * or off by calling {@link #setMode(int)} or {@link RtpStream#setMode(int)}.
- * The AudioGroup will go through these components and process them one by one
- * within its execution loop. The loop consists of four steps. First, for each
- * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming
- * packets and stores in its buffer. Then, if the microphone is enabled,
- * processes the recorded audio and stores in its buffer. Third, if the speaker
- * is enabled, mixes all AudioStream buffers and plays back. Finally, for each
- * AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other
- * buffers and sends back the encoded packets. An AudioGroup does nothing if
- * there is no AudioStream in it.
- *
- * <p>Few things must be noticed before using these classes. The performance is
- * highly related to the system load and the network bandwidth. Usually a
- * simpler {@link AudioCodec} costs fewer CPU cycles but requires more network
- * bandwidth, and vise versa. Using two AudioStreams at the same time doubles
- * not only the load but also the bandwidth. The condition varies from one
- * device to another, and developers should choose the right combination in
- * order to get the best result.</p>
- *
- * <p>It is sometimes useful to keep multiple AudioGroups at the same time. For
- * example, a Voice over IP (VoIP) application might want to put a conference
- * call on hold in order to make a new call but still allow people in the
- * conference call talking to each other. This can be done easily using two
- * AudioGroups, but there are some limitations. Since the speaker and the
- * microphone are globally shared resources, only one AudioGroup at a time is
- * allowed to run in a mode other than {@link #MODE_ON_HOLD}. The others will
- * be unable to acquire these resources and fail silently.</p>
- *
- * <p class="note">Using this class requires
- * {@link android.Manifest.permission#RECORD_AUDIO} permission. Developers
- * should set the audio mode to {@link AudioManager#MODE_IN_COMMUNICATION}
- * using {@link AudioManager#setMode(int)} and change it back when none of
- * the AudioGroups is in use.</p>
- *
- * @see AudioStream
- */
-public class AudioGroup {
- /**
- * This mode is similar to {@link #MODE_NORMAL} except the speaker and
- * the microphone are both disabled.
- */
- public static final int MODE_ON_HOLD = 0;
-
- /**
- * This mode is similar to {@link #MODE_NORMAL} except the microphone is
- * disabled.
- */
- public static final int MODE_MUTED = 1;
-
- /**
- * This mode indicates that the speaker, the microphone, and all
- * {@link AudioStream}s in the group are enabled. First, the packets
- * received from the streams are decoded and mixed with the audio recorded
- * from the microphone. Then, the results are played back to the speaker,
- * encoded and sent back to each stream.
- */
- public static final int MODE_NORMAL = 2;
-
- /**
- * This mode is similar to {@link #MODE_NORMAL} except the echo suppression
- * is enabled. It should be only used when the speaker phone is on.
- */
- public static final int MODE_ECHO_SUPPRESSION = 3;
-
- private static final int MODE_LAST = 3;
-
- private final Map<AudioStream, Integer> mStreams;
- private int mMode = MODE_ON_HOLD;
-
- private int mNative;
- static {
- System.loadLibrary("rtp_jni");
- }
-
- /**
- * Creates an empty AudioGroup.
- */
- public AudioGroup() {
- mStreams = new HashMap<AudioStream, Integer>();
- }
-
- /**
- * Returns the {@link AudioStream}s in this group.
- */
- public AudioStream[] getStreams() {
- synchronized (this) {
- return mStreams.keySet().toArray(new AudioStream[mStreams.size()]);
- }
- }
-
- /**
- * Returns the current mode.
- */
- public int getMode() {
- return mMode;
- }
-
- /**
- * Changes the current mode. It must be one of {@link #MODE_ON_HOLD},
- * {@link #MODE_MUTED}, {@link #MODE_NORMAL}, and
- * {@link #MODE_ECHO_SUPPRESSION}.
- *
- * @param mode The mode to change to.
- * @throws IllegalArgumentException if the mode is invalid.
- */
- public void setMode(int mode) {
- if (mode < 0 || mode > MODE_LAST) {
- throw new IllegalArgumentException("Invalid mode");
- }
- synchronized (this) {
- nativeSetMode(mode);
- mMode = mode;
- }
- }
-
- private native void nativeSetMode(int mode);
-
- // Package-private method used by AudioStream.join().
- synchronized void add(AudioStream stream) {
- if (!mStreams.containsKey(stream)) {
- try {
- AudioCodec codec = stream.getCodec();
- String codecSpec = String.format(Locale.US, "%d %s %s", codec.type,
- codec.rtpmap, codec.fmtp);
- int id = nativeAdd(stream.getMode(), stream.getSocket(),
- stream.getRemoteAddress().getHostAddress(),
- stream.getRemotePort(), codecSpec, stream.getDtmfType());
- mStreams.put(stream, id);
- } catch (NullPointerException e) {
- throw new IllegalStateException(e);
- }
- }
- }
-
- private native int nativeAdd(int mode, int socket, String remoteAddress,
- int remotePort, String codecSpec, int dtmfType);
-
- // Package-private method used by AudioStream.join().
- synchronized void remove(AudioStream stream) {
- Integer id = mStreams.remove(stream);
- if (id != null) {
- nativeRemove(id);
- }
- }
-
- private native void nativeRemove(int id);
-
- /**
- * Sends a DTMF digit to every {@link AudioStream} in this group. Currently
- * only event {@code 0} to {@code 15} are supported.
- *
- * @throws IllegalArgumentException if the event is invalid.
- */
- public void sendDtmf(int event) {
- if (event < 0 || event > 15) {
- throw new IllegalArgumentException("Invalid event");
- }
- synchronized (this) {
- nativeSendDtmf(event);
- }
- }
-
- private native void nativeSendDtmf(int event);
-
- /**
- * Removes every {@link AudioStream} in this group.
- */
- public void clear() {
- for (AudioStream stream : getStreams()) {
- stream.join(null);
- }
- }
-
- @Override
- protected void finalize() throws Throwable {
- nativeRemove(0);
- super.finalize();
- }
-}
diff --git a/voip/java/android/net/rtp/AudioStream.java b/voip/java/android/net/rtp/AudioStream.java
deleted file mode 100644
index 5cd1abc..0000000
--- a/voip/java/android/net/rtp/AudioStream.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.rtp;
-
-import java.net.InetAddress;
-import java.net.SocketException;
-
-/**
- * An AudioStream is a {@link RtpStream} which carrys audio payloads over
- * Real-time Transport Protocol (RTP). Two different classes are developed in
- * order to support various usages such as audio conferencing. An AudioStream
- * represents a remote endpoint which consists of a network mapping and a
- * configured {@link AudioCodec}. On the other side, An {@link AudioGroup}
- * represents a local endpoint which mixes all the AudioStreams and optionally
- * interacts with the speaker and the microphone at the same time. The simplest
- * usage includes one for each endpoints. For other combinations, developers
- * should be aware of the limitations described in {@link AudioGroup}.
- *
- * <p>An AudioStream becomes busy when it joins an AudioGroup. In this case most
- * of the setter methods are disabled. This is designed to ease the task of
- * managing native resources. One can always make an AudioStream leave its
- * AudioGroup by calling {@link #join(AudioGroup)} with {@code null} and put it
- * back after the modification is done.</p>
- *
- * <p class="note">Using this class requires
- * {@link android.Manifest.permission#INTERNET} permission.</p>
- *
- * @see RtpStream
- * @see AudioGroup
- */
-public class AudioStream extends RtpStream {
- private AudioCodec mCodec;
- private int mDtmfType = -1;
- private AudioGroup mGroup;
-
- /**
- * Creates an AudioStream on the given local address. Note that the local
- * port is assigned automatically to conform with RFC 3550.
- *
- * @param address The network address of the local host to bind to.
- * @throws SocketException if the address cannot be bound or a problem
- * occurs during binding.
- */
- public AudioStream(InetAddress address) throws SocketException {
- super(address);
- }
-
- /**
- * Returns {@code true} if the stream has already joined an
- * {@link AudioGroup}.
- */
- @Override
- public final boolean isBusy() {
- return mGroup != null;
- }
-
- /**
- * Returns the joined {@link AudioGroup}.
- */
- public AudioGroup getGroup() {
- return mGroup;
- }
-
- /**
- * Joins an {@link AudioGroup}. Each stream can join only one group at a
- * time. The group can be changed by passing a different one or removed
- * by calling this method with {@code null}.
- *
- * @param group The AudioGroup to join or {@code null} to leave.
- * @throws IllegalStateException if the stream is not properly configured.
- * @see AudioGroup
- */
- public void join(AudioGroup group) {
- synchronized (this) {
- if (mGroup == group) {
- return;
- }
- if (mGroup != null) {
- mGroup.remove(this);
- mGroup = null;
- }
- if (group != null) {
- group.add(this);
- mGroup = group;
- }
- }
- }
-
- /**
- * Returns the {@link AudioCodec}, or {@code null} if it is not set.
- *
- * @see #setCodec(AudioCodec)
- */
- public AudioCodec getCodec() {
- return mCodec;
- }
-
- /**
- * Sets the {@link AudioCodec}.
- *
- * @param codec The AudioCodec to be used.
- * @throws IllegalArgumentException if its type is used by DTMF.
- * @throws IllegalStateException if the stream is busy.
- */
- public void setCodec(AudioCodec codec) {
- if (isBusy()) {
- throw new IllegalStateException("Busy");
- }
- if (codec.type == mDtmfType) {
- throw new IllegalArgumentException("The type is used by DTMF");
- }
- mCodec = codec;
- }
-
- /**
- * Returns the RTP payload type for dual-tone multi-frequency (DTMF) digits,
- * or {@code -1} if it is not enabled.
- *
- * @see #setDtmfType(int)
- */
- public int getDtmfType() {
- return mDtmfType;
- }
-
- /**
- * Sets the RTP payload type for dual-tone multi-frequency (DTMF) digits.
- * The primary usage is to send digits to the remote gateway to perform
- * certain tasks, such as second-stage dialing. According to RFC 2833, the
- * RTP payload type for DTMF is assigned dynamically, so it must be in the
- * range of 96 and 127. One can use {@code -1} to disable DTMF and free up
- * the previous assigned type. This method cannot be called when the stream
- * already joined an {@link AudioGroup}.
- *
- * @param type The RTP payload type to be used or {@code -1} to disable it.
- * @throws IllegalArgumentException if the type is invalid or used by codec.
- * @throws IllegalStateException if the stream is busy.
- * @see AudioGroup#sendDtmf(int)
- */
- public void setDtmfType(int type) {
- if (isBusy()) {
- throw new IllegalStateException("Busy");
- }
- if (type != -1) {
- if (type < 96 || type > 127) {
- throw new IllegalArgumentException("Invalid type");
- }
- if (mCodec != null && type == mCodec.type) {
- throw new IllegalArgumentException("The type is used by codec");
- }
- }
- mDtmfType = type;
- }
-}
diff --git a/voip/java/android/net/rtp/RtpStream.java b/voip/java/android/net/rtp/RtpStream.java
deleted file mode 100644
index b9d75cd..0000000
--- a/voip/java/android/net/rtp/RtpStream.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.rtp;
-
-import java.net.InetAddress;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.SocketException;
-
-/**
- * RtpStream represents the base class of streams which send and receive network
- * packets with media payloads over Real-time Transport Protocol (RTP).
- *
- * <p class="note">Using this class requires
- * {@link android.Manifest.permission#INTERNET} permission.</p>
- */
-public class RtpStream {
- /**
- * This mode indicates that the stream sends and receives packets at the
- * same time. This is the initial mode for new streams.
- */
- public static final int MODE_NORMAL = 0;
-
- /**
- * This mode indicates that the stream only sends packets.
- */
- public static final int MODE_SEND_ONLY = 1;
-
- /**
- * This mode indicates that the stream only receives packets.
- */
- public static final int MODE_RECEIVE_ONLY = 2;
-
- private static final int MODE_LAST = 2;
-
- private final InetAddress mLocalAddress;
- private final int mLocalPort;
-
- private InetAddress mRemoteAddress;
- private int mRemotePort = -1;
- private int mMode = MODE_NORMAL;
-
- private int mSocket = -1;
- static {
- System.loadLibrary("rtp_jni");
- }
-
- /**
- * Creates a RtpStream on the given local address. Note that the local
- * port is assigned automatically to conform with RFC 3550.
- *
- * @param address The network address of the local host to bind to.
- * @throws SocketException if the address cannot be bound or a problem
- * occurs during binding.
- */
- RtpStream(InetAddress address) throws SocketException {
- mLocalPort = create(address.getHostAddress());
- mLocalAddress = address;
- }
-
- private native int create(String address) throws SocketException;
-
- /**
- * Returns the network address of the local host.
- */
- public InetAddress getLocalAddress() {
- return mLocalAddress;
- }
-
- /**
- * Returns the network port of the local host.
- */
- public int getLocalPort() {
- return mLocalPort;
- }
-
- /**
- * Returns the network address of the remote host or {@code null} if the
- * stream is not associated.
- */
- public InetAddress getRemoteAddress() {
- return mRemoteAddress;
- }
-
- /**
- * Returns the network port of the remote host or {@code -1} if the stream
- * is not associated.
- */
- public int getRemotePort() {
- return mRemotePort;
- }
-
- /**
- * Returns {@code true} if the stream is busy. In this case most of the
- * setter methods are disabled. This method is intended to be overridden
- * by subclasses.
- */
- public boolean isBusy() {
- return false;
- }
-
- /**
- * Returns the current mode.
- */
- public int getMode() {
- return mMode;
- }
-
- /**
- * Changes the current mode. It must be one of {@link #MODE_NORMAL},
- * {@link #MODE_SEND_ONLY}, and {@link #MODE_RECEIVE_ONLY}.
- *
- * @param mode The mode to change to.
- * @throws IllegalArgumentException if the mode is invalid.
- * @throws IllegalStateException if the stream is busy.
- * @see #isBusy()
- */
- public void setMode(int mode) {
- if (isBusy()) {
- throw new IllegalStateException("Busy");
- }
- if (mode < 0 || mode > MODE_LAST) {
- throw new IllegalArgumentException("Invalid mode");
- }
- mMode = mode;
- }
-
- /**
- * Associates with a remote host. This defines the destination of the
- * outgoing packets.
- *
- * @param address The network address of the remote host.
- * @param port The network port of the remote host.
- * @throws IllegalArgumentException if the address is not supported or the
- * port is invalid.
- * @throws IllegalStateException if the stream is busy.
- * @see #isBusy()
- */
- public void associate(InetAddress address, int port) {
- if (isBusy()) {
- throw new IllegalStateException("Busy");
- }
- if (!(address instanceof Inet4Address && mLocalAddress instanceof Inet4Address) &&
- !(address instanceof Inet6Address && mLocalAddress instanceof Inet6Address)) {
- throw new IllegalArgumentException("Unsupported address");
- }
- if (port < 0 || port > 65535) {
- throw new IllegalArgumentException("Invalid port");
- }
- mRemoteAddress = address;
- mRemotePort = port;
- }
-
- int getSocket() {
- return mSocket;
- }
-
- /**
- * Releases allocated resources. The stream becomes inoperable after calling
- * this method.
- *
- * @throws IllegalStateException if the stream is busy.
- * @see #isBusy()
- */
- public void release() {
- synchronized (this) {
- if (isBusy()) {
- throw new IllegalStateException("Busy");
- }
- close();
- }
- }
-
- private native void close();
-
- @Override
- protected void finalize() throws Throwable {
- close();
- super.finalize();
- }
-}
diff --git a/voip/java/android/net/rtp/package.html b/voip/java/android/net/rtp/package.html
deleted file mode 100644
index 4506b09..0000000
--- a/voip/java/android/net/rtp/package.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<html>
-<body>
-<p>Provides APIs for RTP (Real-time Transport Protocol), allowing applications to manage on-demand
-or interactive data streaming. In particular, apps that provide VOIP, push-to-talk, conferencing,
-and audio streaming can use these APIs to initiate sessions and transmit or receive data streams
-over any available network.</p>
-
-<p>To support audio conferencing and similar usages, you need to instantiate two classes as
-endpoints for the stream:</p>
-
-<ul>
-<li>{@link android.net.rtp.AudioStream} specifies a remote endpoint and consists of network mapping
-and a configured {@link android.net.rtp.AudioCodec}.</li>
-
-<li>{@link android.net.rtp.AudioGroup} represents the local endpoint for one or more {@link
-android.net.rtp.AudioStream}s. The {@link android.net.rtp.AudioGroup} mixes all the {@link
-android.net.rtp.AudioStream}s and optionally interacts with the device speaker and the microphone at
-the same time.</li>
-</ul>
-
-<p>The simplest usage involves a single remote endpoint and local endpoint. For more complex usages,
-refer to the limitations described for {@link android.net.rtp.AudioGroup}.</p>
-
-<p class="note"><strong>Note:</strong> To use the RTP APIs, you must request the {@link
-android.Manifest.permission#INTERNET} and {@link
-android.Manifest.permission#RECORD_AUDIO} permissions in your manifest file.</p>
-</body>
-</html>
\ No newline at end of file
diff --git a/voip/java/android/net/sip/ISipService.aidl b/voip/java/android/net/sip/ISipService.aidl
deleted file mode 100644
index 3250bf9..0000000
--- a/voip/java/android/net/sip/ISipService.aidl
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.app.PendingIntent;
-import android.net.sip.ISipSession;
-import android.net.sip.ISipSessionListener;
-import android.net.sip.SipProfile;
-
-/**
- * {@hide}
- */
-interface ISipService {
- void open(in SipProfile localProfile);
- void open3(in SipProfile localProfile,
- in PendingIntent incomingCallPendingIntent,
- in ISipSessionListener listener);
- void close(in String localProfileUri);
- boolean isOpened(String localProfileUri);
- boolean isRegistered(String localProfileUri);
- void setRegistrationListener(String localProfileUri,
- ISipSessionListener listener);
-
- ISipSession createSession(in SipProfile localProfile,
- in ISipSessionListener listener);
- ISipSession getPendingSession(String callId);
-
- SipProfile[] getListOfProfiles();
-}
diff --git a/voip/java/android/net/sip/ISipSession.aidl b/voip/java/android/net/sip/ISipSession.aidl
deleted file mode 100644
index 2d515db..0000000
--- a/voip/java/android/net/sip/ISipSession.aidl
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.net.sip.ISipSessionListener;
-import android.net.sip.SipProfile;
-
-/**
- * A SIP session that is associated with a SIP dialog or a transaction that is
- * not within a dialog.
- * @hide
- */
-interface ISipSession {
- /**
- * Gets the IP address of the local host on which this SIP session runs.
- *
- * @return the IP address of the local host
- */
- String getLocalIp();
-
- /**
- * Gets the SIP profile that this session is associated with.
- *
- * @return the SIP profile that this session is associated with
- */
- SipProfile getLocalProfile();
-
- /**
- * Gets the SIP profile that this session is connected to. Only available
- * when the session is associated with a SIP dialog.
- *
- * @return the SIP profile that this session is connected to
- */
- SipProfile getPeerProfile();
-
- /**
- * Gets the session state. The value returned must be one of the states in
- * {@link SipSessionState}.
- *
- * @return the session state
- */
- int getState();
-
- /**
- * Checks if the session is in a call.
- *
- * @return true if the session is in a call
- */
- boolean isInCall();
-
- /**
- * Gets the call ID of the session.
- *
- * @return the call ID
- */
- String getCallId();
-
-
- /**
- * Sets the listener to listen to the session events. A {@link ISipSession}
- * can only hold one listener at a time. Subsequent calls to this method
- * override the previous listener.
- *
- * @param listener to listen to the session events of this object
- */
- void setListener(in ISipSessionListener listener);
-
-
- /**
- * Performs registration to the server specified by the associated local
- * profile. The session listener is called back upon success or failure of
- * registration. The method is only valid to call when the session state is
- * in {@link SipSessionState#READY_TO_CALL}.
- *
- * @param duration duration in second before the registration expires
- * @see ISipSessionListener
- */
- void register(int duration);
-
- /**
- * Performs unregistration to the server specified by the associated local
- * profile. Unregistration is technically the same as registration with zero
- * expiration duration. The session listener is called back upon success or
- * failure of unregistration. The method is only valid to call when the
- * session state is in {@link SipSessionState#READY_TO_CALL}.
- *
- * @see ISipSessionListener
- */
- void unregister();
-
- /**
- * Initiates a call to the specified profile. The session listener is called
- * back upon defined session events. The method is only valid to call when
- * the session state is in {@link SipSessionState#READY_TO_CALL}.
- *
- * @param callee the SIP profile to make the call to
- * @param sessionDescription the session description of this call
- * @param timeout the session will be timed out if the call is not
- * established within {@code timeout} seconds
- * @see ISipSessionListener
- */
- void makeCall(in SipProfile callee, String sessionDescription, int timeout);
-
- /**
- * Answers an incoming call with the specified session description. The
- * method is only valid to call when the session state is in
- * {@link SipSessionState#INCOMING_CALL}.
- *
- * @param sessionDescription the session description to answer this call
- * @param timeout the session will be timed out if the call is not
- * established within {@code timeout} seconds
- */
- void answerCall(String sessionDescription, int timeout);
-
- /**
- * Ends an established call, terminates an outgoing call or rejects an
- * incoming call. The method is only valid to call when the session state is
- * in {@link SipSessionState#IN_CALL},
- * {@link SipSessionState#INCOMING_CALL},
- * {@link SipSessionState#OUTGOING_CALL} or
- * {@link SipSessionState#OUTGOING_CALL_RING_BACK}.
- */
- void endCall();
-
- /**
- * Changes the session description during a call. The method is only valid
- * to call when the session state is in {@link SipSessionState#IN_CALL}.
- *
- * @param sessionDescription the new session description
- * @param timeout the session will be timed out if the call is not
- * established within {@code timeout} seconds
- */
- void changeCall(String sessionDescription, int timeout);
-}
diff --git a/voip/java/android/net/sip/ISipSessionListener.aidl b/voip/java/android/net/sip/ISipSessionListener.aidl
deleted file mode 100644
index 690700c..0000000
--- a/voip/java/android/net/sip/ISipSessionListener.aidl
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.net.sip.ISipSession;
-import android.net.sip.SipProfile;
-
-/**
- * Listener class to listen to SIP session events.
- * @hide
- */
-interface ISipSessionListener {
- /**
- * Called when an INVITE request is sent to initiate a new call.
- *
- * @param session the session object that carries out the transaction
- */
- void onCalling(in ISipSession session);
-
- /**
- * Called when an INVITE request is received.
- *
- * @param session the session object that carries out the transaction
- * @param caller the SIP profile of the caller
- * @param sessionDescription the caller's session description
- */
- void onRinging(in ISipSession session, in SipProfile caller,
- String sessionDescription);
-
- /**
- * Called when a RINGING response is received for the INVITE request sent
- *
- * @param session the session object that carries out the transaction
- */
- void onRingingBack(in ISipSession session);
-
- /**
- * Called when the session is established.
- *
- * @param session the session object that is associated with the dialog
- * @param sessionDescription the peer's session description
- */
- void onCallEstablished(in ISipSession session,
- String sessionDescription);
-
- /**
- * Called when the session is terminated.
- *
- * @param session the session object that is associated with the dialog
- */
- void onCallEnded(in ISipSession session);
-
- /**
- * Called when the peer is busy during session initialization.
- *
- * @param session the session object that carries out the transaction
- */
- void onCallBusy(in ISipSession session);
-
- /**
- * Called when the call is being transferred to a new one.
- *
- * @param newSession the new session that the call will be transferred to
- * @param sessionDescription the new peer's session description
- */
- void onCallTransferring(in ISipSession newSession, String sessionDescription);
-
- /**
- * Called when an error occurs during session initialization and
- * termination.
- *
- * @param session the session object that carries out the transaction
- * @param errorCode error code defined in {@link SipErrorCode}
- * @param errorMessage error message
- */
- void onError(in ISipSession session, int errorCode, String errorMessage);
-
- /**
- * Called when an error occurs during session modification negotiation.
- *
- * @param session the session object that carries out the transaction
- * @param errorCode error code defined in {@link SipErrorCode}
- * @param errorMessage error message
- */
- void onCallChangeFailed(in ISipSession session, int errorCode,
- String errorMessage);
-
- /**
- * Called when a registration request is sent.
- *
- * @param session the session object that carries out the transaction
- */
- void onRegistering(in ISipSession session);
-
- /**
- * Called when registration is successfully done.
- *
- * @param session the session object that carries out the transaction
- * @param duration duration in second before the registration expires
- */
- void onRegistrationDone(in ISipSession session, int duration);
-
- /**
- * Called when the registration fails.
- *
- * @param session the session object that carries out the transaction
- * @param errorCode error code defined in {@link SipErrorCode}
- * @param errorMessage error message
- */
- void onRegistrationFailed(in ISipSession session, int errorCode,
- String errorMessage);
-
- /**
- * Called when the registration gets timed out.
- *
- * @param session the session object that carries out the transaction
- */
- void onRegistrationTimeout(in ISipSession session);
-}
diff --git a/voip/java/android/net/sip/SimpleSessionDescription.java b/voip/java/android/net/sip/SimpleSessionDescription.java
deleted file mode 100644
index 9fcd21d..0000000
--- a/voip/java/android/net/sip/SimpleSessionDescription.java
+++ /dev/null
@@ -1,613 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-
-/**
- * An object used to manipulate messages of Session Description Protocol (SDP).
- * It is mainly designed for the uses of Session Initiation Protocol (SIP).
- * Therefore, it only handles connection addresses ("c="), bandwidth limits,
- * ("b="), encryption keys ("k="), and attribute fields ("a="). Currently this
- * implementation does not support multicast sessions.
- *
- * <p>Here is an example code to create a session description.</p>
- * <pre>
- * SimpleSessionDescription description = new SimpleSessionDescription(
- * System.currentTimeMillis(), "1.2.3.4");
- * Media media = description.newMedia("audio", 56789, 1, "RTP/AVP");
- * media.setRtpPayload(0, "PCMU/8000", null);
- * media.setRtpPayload(8, "PCMA/8000", null);
- * media.setRtpPayload(127, "telephone-event/8000", "0-15");
- * media.setAttribute("sendrecv", "");
- * </pre>
- * <p>Invoking <code>description.encode()</code> will produce a result like the
- * one below.</p>
- * <pre>
- * v=0
- * o=- 1284970442706 1284970442709 IN IP4 1.2.3.4
- * s=-
- * c=IN IP4 1.2.3.4
- * t=0 0
- * m=audio 56789 RTP/AVP 0 8 127
- * a=rtpmap:0 PCMU/8000
- * a=rtpmap:8 PCMA/8000
- * a=rtpmap:127 telephone-event/8000
- * a=fmtp:127 0-15
- * a=sendrecv
- * </pre>
- * @hide
- */
-public class SimpleSessionDescription {
- private final Fields mFields = new Fields("voscbtka");
- private final ArrayList<Media> mMedia = new ArrayList<Media>();
-
- /**
- * Creates a minimal session description from the given session ID and
- * unicast address. The address is used in the origin field ("o=") and the
- * connection field ("c="). See {@link SimpleSessionDescription} for an
- * example of its usage.
- */
- public SimpleSessionDescription(long sessionId, String address) {
- address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") + address;
- mFields.parse("v=0");
- mFields.parse(String.format(Locale.US, "o=- %d %d %s", sessionId,
- System.currentTimeMillis(), address));
- mFields.parse("s=-");
- mFields.parse("t=0 0");
- mFields.parse("c=" + address);
- }
-
- /**
- * Creates a session description from the given message.
- *
- * @throws IllegalArgumentException if message is invalid.
- */
- public SimpleSessionDescription(String message) {
- String[] lines = message.trim().replaceAll(" +", " ").split("[\r\n]+");
- Fields fields = mFields;
-
- for (String line : lines) {
- try {
- if (line.charAt(1) != '=') {
- throw new IllegalArgumentException();
- }
- if (line.charAt(0) == 'm') {
- String[] parts = line.substring(2).split(" ", 4);
- String[] ports = parts[1].split("/", 2);
- Media media = newMedia(parts[0], Integer.parseInt(ports[0]),
- (ports.length < 2) ? 1 : Integer.parseInt(ports[1]),
- parts[2]);
- for (String format : parts[3].split(" ")) {
- media.setFormat(format, null);
- }
- fields = media;
- } else {
- fields.parse(line);
- }
- } catch (Exception e) {
- throw new IllegalArgumentException("Invalid SDP: " + line);
- }
- }
- }
-
- /**
- * Creates a new media description in this session description.
- *
- * @param type The media type, e.g. {@code "audio"}.
- * @param port The first transport port used by this media.
- * @param portCount The number of contiguous ports used by this media.
- * @param protocol The transport protocol, e.g. {@code "RTP/AVP"}.
- */
- public Media newMedia(String type, int port, int portCount,
- String protocol) {
- Media media = new Media(type, port, portCount, protocol);
- mMedia.add(media);
- return media;
- }
-
- /**
- * Returns all the media descriptions in this session description.
- */
- public Media[] getMedia() {
- return mMedia.toArray(new Media[mMedia.size()]);
- }
-
- /**
- * Encodes the session description and all its media descriptions in a
- * string. Note that the result might be incomplete if a required field
- * has never been added before.
- */
- public String encode() {
- StringBuilder buffer = new StringBuilder();
- mFields.write(buffer);
- for (Media media : mMedia) {
- media.write(buffer);
- }
- return buffer.toString();
- }
-
- /**
- * Returns the connection address or {@code null} if it is not present.
- */
- public String getAddress() {
- return mFields.getAddress();
- }
-
- /**
- * Sets the connection address. The field will be removed if the address
- * is {@code null}.
- */
- public void setAddress(String address) {
- mFields.setAddress(address);
- }
-
- /**
- * Returns the encryption method or {@code null} if it is not present.
- */
- public String getEncryptionMethod() {
- return mFields.getEncryptionMethod();
- }
-
- /**
- * Returns the encryption key or {@code null} if it is not present.
- */
- public String getEncryptionKey() {
- return mFields.getEncryptionKey();
- }
-
- /**
- * Sets the encryption method and the encryption key. The field will be
- * removed if the method is {@code null}.
- */
- public void setEncryption(String method, String key) {
- mFields.setEncryption(method, key);
- }
-
- /**
- * Returns the types of the bandwidth limits.
- */
- public String[] getBandwidthTypes() {
- return mFields.getBandwidthTypes();
- }
-
- /**
- * Returns the bandwidth limit of the given type or {@code -1} if it is not
- * present.
- */
- public int getBandwidth(String type) {
- return mFields.getBandwidth(type);
- }
-
- /**
- * Sets the bandwith limit for the given type. The field will be removed if
- * the value is negative.
- */
- public void setBandwidth(String type, int value) {
- mFields.setBandwidth(type, value);
- }
-
- /**
- * Returns the names of all the attributes.
- */
- public String[] getAttributeNames() {
- return mFields.getAttributeNames();
- }
-
- /**
- * Returns the attribute of the given name or {@code null} if it is not
- * present.
- */
- public String getAttribute(String name) {
- return mFields.getAttribute(name);
- }
-
- /**
- * Sets the attribute for the given name. The field will be removed if
- * the value is {@code null}. To set a binary attribute, use an empty
- * string as the value.
- */
- public void setAttribute(String name, String value) {
- mFields.setAttribute(name, value);
- }
-
- /**
- * This class represents a media description of a session description. It
- * can only be created by {@link SimpleSessionDescription#newMedia}. Since
- * the syntax is more restricted for RTP based protocols, two sets of access
- * methods are implemented. See {@link SimpleSessionDescription} for an
- * example of its usage.
- */
- public static class Media extends Fields {
- private final String mType;
- private final int mPort;
- private final int mPortCount;
- private final String mProtocol;
- private ArrayList<String> mFormats = new ArrayList<String>();
-
- private Media(String type, int port, int portCount, String protocol) {
- super("icbka");
- mType = type;
- mPort = port;
- mPortCount = portCount;
- mProtocol = protocol;
- }
-
- /**
- * Returns the media type.
- */
- public String getType() {
- return mType;
- }
-
- /**
- * Returns the first transport port used by this media.
- */
- public int getPort() {
- return mPort;
- }
-
- /**
- * Returns the number of contiguous ports used by this media.
- */
- public int getPortCount() {
- return mPortCount;
- }
-
- /**
- * Returns the transport protocol.
- */
- public String getProtocol() {
- return mProtocol;
- }
-
- /**
- * Returns the media formats.
- */
- public String[] getFormats() {
- return mFormats.toArray(new String[mFormats.size()]);
- }
-
- /**
- * Returns the {@code fmtp} attribute of the given format or
- * {@code null} if it is not present.
- */
- public String getFmtp(String format) {
- return super.get("a=fmtp:" + format, ' ');
- }
-
- /**
- * Sets a format and its {@code fmtp} attribute. If the attribute is
- * {@code null}, the corresponding field will be removed.
- */
- public void setFormat(String format, String fmtp) {
- mFormats.remove(format);
- mFormats.add(format);
- super.set("a=rtpmap:" + format, ' ', null);
- super.set("a=fmtp:" + format, ' ', fmtp);
- }
-
- /**
- * Removes a format and its {@code fmtp} attribute.
- */
- public void removeFormat(String format) {
- mFormats.remove(format);
- super.set("a=rtpmap:" + format, ' ', null);
- super.set("a=fmtp:" + format, ' ', null);
- }
-
- /**
- * Returns the RTP payload types.
- */
- public int[] getRtpPayloadTypes() {
- int[] types = new int[mFormats.size()];
- int length = 0;
- for (String format : mFormats) {
- try {
- types[length] = Integer.parseInt(format);
- ++length;
- } catch (NumberFormatException e) { }
- }
- return Arrays.copyOf(types, length);
- }
-
- /**
- * Returns the {@code rtpmap} attribute of the given RTP payload type
- * or {@code null} if it is not present.
- */
- public String getRtpmap(int type) {
- return super.get("a=rtpmap:" + type, ' ');
- }
-
- /**
- * Returns the {@code fmtp} attribute of the given RTP payload type or
- * {@code null} if it is not present.
- */
- public String getFmtp(int type) {
- return super.get("a=fmtp:" + type, ' ');
- }
-
- /**
- * Sets a RTP payload type and its {@code rtpmap} and {@code fmtp}
- * attributes. If any of the attributes is {@code null}, the
- * corresponding field will be removed. See
- * {@link SimpleSessionDescription} for an example of its usage.
- */
- public void setRtpPayload(int type, String rtpmap, String fmtp) {
- String format = String.valueOf(type);
- mFormats.remove(format);
- mFormats.add(format);
- super.set("a=rtpmap:" + format, ' ', rtpmap);
- super.set("a=fmtp:" + format, ' ', fmtp);
- }
-
- /**
- * Removes a RTP payload and its {@code rtpmap} and {@code fmtp}
- * attributes.
- */
- public void removeRtpPayload(int type) {
- removeFormat(String.valueOf(type));
- }
-
- private void write(StringBuilder buffer) {
- buffer.append("m=").append(mType).append(' ').append(mPort);
- if (mPortCount != 1) {
- buffer.append('/').append(mPortCount);
- }
- buffer.append(' ').append(mProtocol);
- for (String format : mFormats) {
- buffer.append(' ').append(format);
- }
- buffer.append("\r\n");
- super.write(buffer);
- }
- }
-
- /**
- * This class acts as a set of fields, and the size of the set is expected
- * to be small. Therefore, it uses a simple list instead of maps. Each field
- * has three parts: a key, a delimiter, and a value. Delimiters are special
- * because they are not included in binary attributes. As a result, the
- * private methods, which are the building blocks of this class, all take
- * the delimiter as an argument.
- */
- private static class Fields {
- private final String mOrder;
- private final ArrayList<String> mLines = new ArrayList<String>();
-
- Fields(String order) {
- mOrder = order;
- }
-
- /**
- * Returns the connection address or {@code null} if it is not present.
- */
- public String getAddress() {
- String address = get("c", '=');
- if (address == null) {
- return null;
- }
- String[] parts = address.split(" ");
- if (parts.length != 3) {
- return null;
- }
- int slash = parts[2].indexOf('/');
- return (slash < 0) ? parts[2] : parts[2].substring(0, slash);
- }
-
- /**
- * Sets the connection address. The field will be removed if the address
- * is {@code null}.
- */
- public void setAddress(String address) {
- if (address != null) {
- address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") +
- address;
- }
- set("c", '=', address);
- }
-
- /**
- * Returns the encryption method or {@code null} if it is not present.
- */
- public String getEncryptionMethod() {
- String encryption = get("k", '=');
- if (encryption == null) {
- return null;
- }
- int colon = encryption.indexOf(':');
- return (colon == -1) ? encryption : encryption.substring(0, colon);
- }
-
- /**
- * Returns the encryption key or {@code null} if it is not present.
- */
- public String getEncryptionKey() {
- String encryption = get("k", '=');
- if (encryption == null) {
- return null;
- }
- int colon = encryption.indexOf(':');
- return (colon == -1) ? null : encryption.substring(0, colon + 1);
- }
-
- /**
- * Sets the encryption method and the encryption key. The field will be
- * removed if the method is {@code null}.
- */
- public void setEncryption(String method, String key) {
- set("k", '=', (method == null || key == null) ?
- method : method + ':' + key);
- }
-
- /**
- * Returns the types of the bandwidth limits.
- */
- public String[] getBandwidthTypes() {
- return cut("b=", ':');
- }
-
- /**
- * Returns the bandwidth limit of the given type or {@code -1} if it is
- * not present.
- */
- public int getBandwidth(String type) {
- String value = get("b=" + type, ':');
- if (value != null) {
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) { }
- setBandwidth(type, -1);
- }
- return -1;
- }
-
- /**
- * Sets the bandwith limit for the given type. The field will be removed
- * if the value is negative.
- */
- public void setBandwidth(String type, int value) {
- set("b=" + type, ':', (value < 0) ? null : String.valueOf(value));
- }
-
- /**
- * Returns the names of all the attributes.
- */
- public String[] getAttributeNames() {
- return cut("a=", ':');
- }
-
- /**
- * Returns the attribute of the given name or {@code null} if it is not
- * present.
- */
- public String getAttribute(String name) {
- return get("a=" + name, ':');
- }
-
- /**
- * Sets the attribute for the given name. The field will be removed if
- * the value is {@code null}. To set a binary attribute, use an empty
- * string as the value.
- */
- public void setAttribute(String name, String value) {
- set("a=" + name, ':', value);
- }
-
- private void write(StringBuilder buffer) {
- for (int i = 0; i < mOrder.length(); ++i) {
- char type = mOrder.charAt(i);
- for (String line : mLines) {
- if (line.charAt(0) == type) {
- buffer.append(line).append("\r\n");
- }
- }
- }
- }
-
- /**
- * Invokes {@link #set} after splitting the line into three parts.
- */
- private void parse(String line) {
- char type = line.charAt(0);
- if (mOrder.indexOf(type) == -1) {
- return;
- }
- char delimiter = '=';
- if (line.startsWith("a=rtpmap:") || line.startsWith("a=fmtp:")) {
- delimiter = ' ';
- } else if (type == 'b' || type == 'a') {
- delimiter = ':';
- }
- int i = line.indexOf(delimiter);
- if (i == -1) {
- set(line, delimiter, "");
- } else {
- set(line.substring(0, i), delimiter, line.substring(i + 1));
- }
- }
-
- /**
- * Finds the key with the given prefix and returns its suffix.
- */
- private String[] cut(String prefix, char delimiter) {
- String[] names = new String[mLines.size()];
- int length = 0;
- for (String line : mLines) {
- if (line.startsWith(prefix)) {
- int i = line.indexOf(delimiter);
- if (i == -1) {
- i = line.length();
- }
- names[length] = line.substring(prefix.length(), i);
- ++length;
- }
- }
- return Arrays.copyOf(names, length);
- }
-
- /**
- * Returns the index of the key.
- */
- private int find(String key, char delimiter) {
- int length = key.length();
- for (int i = mLines.size() - 1; i >= 0; --i) {
- String line = mLines.get(i);
- if (line.startsWith(key) && (line.length() == length ||
- line.charAt(length) == delimiter)) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Sets the key with the value or removes the key if the value is
- * {@code null}.
- */
- private void set(String key, char delimiter, String value) {
- int index = find(key, delimiter);
- if (value != null) {
- if (value.length() != 0) {
- key = key + delimiter + value;
- }
- if (index == -1) {
- mLines.add(key);
- } else {
- mLines.set(index, key);
- }
- } else if (index != -1) {
- mLines.remove(index);
- }
- }
-
- /**
- * Returns the value of the key.
- */
- private String get(String key, char delimiter) {
- int index = find(key, delimiter);
- if (index == -1) {
- return null;
- }
- String line = mLines.get(index);
- int length = key.length();
- return (line.length() == length) ? "" : line.substring(length + 1);
- }
- }
-}
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
deleted file mode 100644
index ea943e9..0000000
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ /dev/null
@@ -1,1143 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.net.rtp.AudioCodec;
-import android.net.rtp.AudioGroup;
-import android.net.rtp.AudioStream;
-import android.net.rtp.RtpStream;
-import android.net.sip.SimpleSessionDescription.Media;
-import android.net.wifi.WifiManager;
-import android.os.Message;
-import android.telephony.Rlog;
-import android.text.TextUtils;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-/**
- * Handles an Internet audio call over SIP. You can instantiate this class with {@link SipManager},
- * using {@link SipManager#makeAudioCall makeAudioCall()} and {@link SipManager#takeAudioCall
- * takeAudioCall()}.
- *
- * <p class="note"><strong>Note:</strong> Using this class require the
- * {@link android.Manifest.permission#INTERNET} and
- * {@link android.Manifest.permission#USE_SIP} permissions. In addition, {@link
- * #startAudio} requires the
- * {@link android.Manifest.permission#RECORD_AUDIO},
- * {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and
- * {@link android.Manifest.permission#WAKE_LOCK} permissions; and {@link #setSpeakerMode
- * setSpeakerMode()} requires the
- * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For more information about using SIP, read the
- * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
- * developer guide.</p>
- * </div>
- */
-public class SipAudioCall {
- private static final String LOG_TAG = SipAudioCall.class.getSimpleName();
- private static final boolean DBG = true;
- private static final boolean RELEASE_SOCKET = true;
- private static final boolean DONT_RELEASE_SOCKET = false;
- private static final int SESSION_TIMEOUT = 5; // in seconds
- private static final int TRANSFER_TIMEOUT = 15; // in seconds
-
- /** Listener for events relating to a SIP call, such as when a call is being
- * recieved ("on ringing") or a call is outgoing ("on calling").
- * <p>Many of these events are also received by {@link SipSession.Listener}.</p>
- */
- public static class Listener {
- /**
- * Called when the call object is ready to make another call.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that is ready to make another call
- */
- public void onReadyToCall(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when a request is sent out to initiate a new call.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onCalling(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when a new call comes in.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- * @param caller the SIP profile of the caller
- */
- public void onRinging(SipAudioCall call, SipProfile caller) {
- onChanged(call);
- }
-
- /**
- * Called when a RINGING response is received for the INVITE request
- * sent. The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onRingingBack(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when the session is established.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onCallEstablished(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when the session is terminated.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onCallEnded(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when the peer is busy during session initialization.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onCallBusy(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when the call is on hold.
- * The default implementation calls {@link #onChanged}.
- *
- * @param call the call object that carries out the audio call
- */
- public void onCallHeld(SipAudioCall call) {
- onChanged(call);
- }
-
- /**
- * Called when an error occurs. The default implementation is no op.
- *
- * @param call the call object that carries out the audio call
- * @param errorCode error code of this error
- * @param errorMessage error message
- * @see SipErrorCode
- */
- public void onError(SipAudioCall call, int errorCode,
- String errorMessage) {
- // no-op
- }
-
- /**
- * Called when an event occurs and the corresponding callback is not
- * overridden. The default implementation is no op. Error events are
- * not re-directed to this callback and are handled in {@link #onError}.
- */
- public void onChanged(SipAudioCall call) {
- // no-op
- }
- }
-
- private Context mContext;
- private SipProfile mLocalProfile;
- private SipAudioCall.Listener mListener;
- private SipSession mSipSession;
- private SipSession mTransferringSession;
-
- private long mSessionId = System.currentTimeMillis();
- private String mPeerSd;
-
- private AudioStream mAudioStream;
- private AudioGroup mAudioGroup;
-
- private boolean mInCall = false;
- private boolean mMuted = false;
- private boolean mHold = false;
-
- private WifiManager mWm;
- private WifiManager.WifiLock mWifiHighPerfLock;
-
- private int mErrorCode = SipErrorCode.NO_ERROR;
- private String mErrorMessage;
-
- /**
- * Creates a call object with the local SIP profile.
- * @param context the context for accessing system services such as
- * ringtone, audio, WIFI etc
- */
- public SipAudioCall(Context context, SipProfile localProfile) {
- mContext = context;
- mLocalProfile = localProfile;
- mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- }
-
- /**
- * Sets the listener to listen to the audio call events. The method calls
- * {@link #setListener setListener(listener, false)}.
- *
- * @param listener to listen to the audio call events of this object
- * @see #setListener(Listener, boolean)
- */
- public void setListener(SipAudioCall.Listener listener) {
- setListener(listener, false);
- }
-
- /**
- * Sets the listener to listen to the audio call events. A
- * {@link SipAudioCall} can only hold one listener at a time. Subsequent
- * calls to this method override the previous listener.
- *
- * @param listener to listen to the audio call events of this object
- * @param callbackImmediately set to true if the caller wants to be called
- * back immediately on the current state
- */
- public void setListener(SipAudioCall.Listener listener,
- boolean callbackImmediately) {
- mListener = listener;
- try {
- if ((listener == null) || !callbackImmediately) {
- // do nothing
- } else if (mErrorCode != SipErrorCode.NO_ERROR) {
- listener.onError(this, mErrorCode, mErrorMessage);
- } else if (mInCall) {
- if (mHold) {
- listener.onCallHeld(this);
- } else {
- listener.onCallEstablished(this);
- }
- } else {
- int state = getState();
- switch (state) {
- case SipSession.State.READY_TO_CALL:
- listener.onReadyToCall(this);
- break;
- case SipSession.State.INCOMING_CALL:
- listener.onRinging(this, getPeerProfile());
- break;
- case SipSession.State.OUTGOING_CALL:
- listener.onCalling(this);
- break;
- case SipSession.State.OUTGOING_CALL_RING_BACK:
- listener.onRingingBack(this);
- break;
- }
- }
- } catch (Throwable t) {
- loge("setListener()", t);
- }
- }
-
- /**
- * Checks if the call is established.
- *
- * @return true if the call is established
- */
- public boolean isInCall() {
- synchronized (this) {
- return mInCall;
- }
- }
-
- /**
- * Checks if the call is on hold.
- *
- * @return true if the call is on hold
- */
- public boolean isOnHold() {
- synchronized (this) {
- return mHold;
- }
- }
-
- /**
- * Closes this object. This object is not usable after being closed.
- */
- public void close() {
- close(true);
- }
-
- private synchronized void close(boolean closeRtp) {
- if (closeRtp) stopCall(RELEASE_SOCKET);
-
- mInCall = false;
- mHold = false;
- mSessionId = System.currentTimeMillis();
- mErrorCode = SipErrorCode.NO_ERROR;
- mErrorMessage = null;
-
- if (mSipSession != null) {
- mSipSession.setListener(null);
- mSipSession = null;
- }
- }
-
- /**
- * Gets the local SIP profile.
- *
- * @return the local SIP profile
- */
- public SipProfile getLocalProfile() {
- synchronized (this) {
- return mLocalProfile;
- }
- }
-
- /**
- * Gets the peer's SIP profile.
- *
- * @return the peer's SIP profile
- */
- public SipProfile getPeerProfile() {
- synchronized (this) {
- return (mSipSession == null) ? null : mSipSession.getPeerProfile();
- }
- }
-
- /**
- * Gets the state of the {@link SipSession} that carries this call.
- * The value returned must be one of the states in {@link SipSession.State}.
- *
- * @return the session state
- */
- public int getState() {
- synchronized (this) {
- if (mSipSession == null) return SipSession.State.READY_TO_CALL;
- return mSipSession.getState();
- }
- }
-
-
- /**
- * Gets the {@link SipSession} that carries this call.
- *
- * @return the session object that carries this call
- * @hide
- */
- public SipSession getSipSession() {
- synchronized (this) {
- return mSipSession;
- }
- }
-
- private synchronized void transferToNewSession() {
- if (mTransferringSession == null) return;
- SipSession origin = mSipSession;
- mSipSession = mTransferringSession;
- mTransferringSession = null;
-
- // stop the replaced call.
- if (mAudioStream != null) {
- mAudioStream.join(null);
- } else {
- try {
- mAudioStream = new AudioStream(InetAddress.getByName(
- getLocalIp()));
- } catch (Throwable t) {
- loge("transferToNewSession():", t);
- }
- }
- if (origin != null) origin.endCall();
- startAudio();
- }
-
- private SipSession.Listener createListener() {
- return new SipSession.Listener() {
- @Override
- public void onCalling(SipSession session) {
- if (DBG) log("onCalling: session=" + session);
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onCalling(SipAudioCall.this);
- } catch (Throwable t) {
- loge("onCalling():", t);
- }
- }
- }
-
- @Override
- public void onRingingBack(SipSession session) {
- if (DBG) log("onRingingBackk: " + session);
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onRingingBack(SipAudioCall.this);
- } catch (Throwable t) {
- loge("onRingingBack():", t);
- }
- }
- }
-
- @Override
- public void onRinging(SipSession session,
- SipProfile peerProfile, String sessionDescription) {
- // this callback is triggered only for reinvite.
- synchronized (SipAudioCall.this) {
- if ((mSipSession == null) || !mInCall
- || !session.getCallId().equals(
- mSipSession.getCallId())) {
- // should not happen
- session.endCall();
- return;
- }
-
- // session changing request
- try {
- String answer = createAnswer(sessionDescription).encode();
- mSipSession.answerCall(answer, SESSION_TIMEOUT);
- } catch (Throwable e) {
- loge("onRinging():", e);
- session.endCall();
- }
- }
- }
-
- @Override
- public void onCallEstablished(SipSession session,
- String sessionDescription) {
- mPeerSd = sessionDescription;
- if (DBG) log("onCallEstablished(): " + mPeerSd);
-
- // TODO: how to notify the UI that the remote party is changed
- if ((mTransferringSession != null)
- && (session == mTransferringSession)) {
- transferToNewSession();
- return;
- }
-
- Listener listener = mListener;
- if (listener != null) {
- try {
- if (mHold) {
- listener.onCallHeld(SipAudioCall.this);
- } else {
- listener.onCallEstablished(SipAudioCall.this);
- }
- } catch (Throwable t) {
- loge("onCallEstablished(): ", t);
- }
- }
- }
-
- @Override
- public void onCallEnded(SipSession session) {
- if (DBG) log("onCallEnded: " + session + " mSipSession:" + mSipSession);
- // reset the trasnferring session if it is the one.
- if (session == mTransferringSession) {
- mTransferringSession = null;
- return;
- }
- // or ignore the event if the original session is being
- // transferred to the new one.
- if ((mTransferringSession != null) ||
- (session != mSipSession)) return;
-
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onCallEnded(SipAudioCall.this);
- } catch (Throwable t) {
- loge("onCallEnded(): ", t);
- }
- }
- close();
- }
-
- @Override
- public void onCallBusy(SipSession session) {
- if (DBG) log("onCallBusy: " + session);
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onCallBusy(SipAudioCall.this);
- } catch (Throwable t) {
- loge("onCallBusy(): ", t);
- }
- }
- close(false);
- }
-
- @Override
- public void onCallChangeFailed(SipSession session, int errorCode,
- String message) {
- if (DBG) log("onCallChangedFailed: " + message);
- mErrorCode = errorCode;
- mErrorMessage = message;
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onError(SipAudioCall.this, mErrorCode,
- message);
- } catch (Throwable t) {
- loge("onCallBusy():", t);
- }
- }
- }
-
- @Override
- public void onError(SipSession session, int errorCode,
- String message) {
- SipAudioCall.this.onError(errorCode, message);
- }
-
- @Override
- public void onRegistering(SipSession session) {
- // irrelevant
- }
-
- @Override
- public void onRegistrationTimeout(SipSession session) {
- // irrelevant
- }
-
- @Override
- public void onRegistrationFailed(SipSession session, int errorCode,
- String message) {
- // irrelevant
- }
-
- @Override
- public void onRegistrationDone(SipSession session, int duration) {
- // irrelevant
- }
-
- @Override
- public void onCallTransferring(SipSession newSession,
- String sessionDescription) {
- if (DBG) log("onCallTransferring: mSipSession="
- + mSipSession + " newSession=" + newSession);
- mTransferringSession = newSession;
- try {
- if (sessionDescription == null) {
- newSession.makeCall(newSession.getPeerProfile(),
- createOffer().encode(), TRANSFER_TIMEOUT);
- } else {
- String answer = createAnswer(sessionDescription).encode();
- newSession.answerCall(answer, SESSION_TIMEOUT);
- }
- } catch (Throwable e) {
- loge("onCallTransferring()", e);
- newSession.endCall();
- }
- }
- };
- }
-
- private void onError(int errorCode, String message) {
- if (DBG) log("onError: "
- + SipErrorCode.toString(errorCode) + ": " + message);
- mErrorCode = errorCode;
- mErrorMessage = message;
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onError(this, errorCode, message);
- } catch (Throwable t) {
- loge("onError():", t);
- }
- }
- synchronized (this) {
- if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
- || !isInCall()) {
- close(true);
- }
- }
- }
-
- /**
- * Attaches an incoming call to this call object.
- *
- * @param session the session that receives the incoming call
- * @param sessionDescription the session description of the incoming call
- * @throws SipException if the SIP service fails to attach this object to
- * the session or VOIP API is not supported by the device
- * @see SipManager#isVoipSupported
- */
- public void attachCall(SipSession session, String sessionDescription)
- throws SipException {
- if (!SipManager.isVoipSupported(mContext)) {
- throw new SipException("VOIP API is not supported");
- }
-
- synchronized (this) {
- mSipSession = session;
- mPeerSd = sessionDescription;
- if (DBG) log("attachCall(): " + mPeerSd);
- try {
- session.setListener(createListener());
- } catch (Throwable e) {
- loge("attachCall()", e);
- throwSipException(e);
- }
- }
- }
-
- /**
- * Initiates an audio call to the specified profile. The attempt will be
- * timed out if the call is not established within {@code timeout} seconds
- * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
- * will be called.
- *
- * @param peerProfile the SIP profile to make the call to
- * @param sipSession the {@link SipSession} for carrying out the call
- * @param timeout the timeout value in seconds. Default value (defined by
- * SIP protocol) is used if {@code timeout} is zero or negative.
- * @see Listener#onError
- * @throws SipException if the SIP service fails to create a session for the
- * call or VOIP API is not supported by the device
- * @see SipManager#isVoipSupported
- */
- public void makeCall(SipProfile peerProfile, SipSession sipSession,
- int timeout) throws SipException {
- if (DBG) log("makeCall: " + peerProfile + " session=" + sipSession + " timeout=" + timeout);
- if (!SipManager.isVoipSupported(mContext)) {
- throw new SipException("VOIP API is not supported");
- }
-
- synchronized (this) {
- mSipSession = sipSession;
- try {
- mAudioStream = new AudioStream(InetAddress.getByName(
- getLocalIp()));
- sipSession.setListener(createListener());
- sipSession.makeCall(peerProfile, createOffer().encode(),
- timeout);
- } catch (IOException e) {
- loge("makeCall:", e);
- throw new SipException("makeCall()", e);
- }
- }
- }
-
- /**
- * Ends a call.
- * @throws SipException if the SIP service fails to end the call
- */
- public void endCall() throws SipException {
- if (DBG) log("endCall: mSipSession" + mSipSession);
- synchronized (this) {
- stopCall(RELEASE_SOCKET);
- mInCall = false;
-
- // perform the above local ops first and then network op
- if (mSipSession != null) mSipSession.endCall();
- }
- }
-
- /**
- * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is
- * called. The attempt will be timed out if the call is not established
- * within {@code timeout} seconds and
- * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
- * will be called.
- *
- * @param timeout the timeout value in seconds. Default value (defined by
- * SIP protocol) is used if {@code timeout} is zero or negative.
- * @see Listener#onError
- * @throws SipException if the SIP service fails to hold the call
- */
- public void holdCall(int timeout) throws SipException {
- if (DBG) log("holdCall: mSipSession" + mSipSession + " timeout=" + timeout);
- synchronized (this) {
- if (mHold) return;
- if (mSipSession == null) {
- loge("holdCall:");
- throw new SipException("Not in a call to hold call");
- }
- mSipSession.changeCall(createHoldOffer().encode(), timeout);
- mHold = true;
- setAudioGroupMode();
- }
- }
-
- /**
- * Answers a call. The attempt will be timed out if the call is not
- * established within {@code timeout} seconds and
- * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
- * will be called.
- *
- * @param timeout the timeout value in seconds. Default value (defined by
- * SIP protocol) is used if {@code timeout} is zero or negative.
- * @see Listener#onError
- * @throws SipException if the SIP service fails to answer the call
- */
- public void answerCall(int timeout) throws SipException {
- if (DBG) log("answerCall: mSipSession" + mSipSession + " timeout=" + timeout);
- synchronized (this) {
- if (mSipSession == null) {
- throw new SipException("No call to answer");
- }
- try {
- mAudioStream = new AudioStream(InetAddress.getByName(
- getLocalIp()));
- mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
- } catch (IOException e) {
- loge("answerCall:", e);
- throw new SipException("answerCall()", e);
- }
- }
- }
-
- /**
- * Continues a call that's on hold. When succeeds,
- * {@link Listener#onCallEstablished} is called. The attempt will be timed
- * out if the call is not established within {@code timeout} seconds and
- * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
- * will be called.
- *
- * @param timeout the timeout value in seconds. Default value (defined by
- * SIP protocol) is used if {@code timeout} is zero or negative.
- * @see Listener#onError
- * @throws SipException if the SIP service fails to unhold the call
- */
- public void continueCall(int timeout) throws SipException {
- if (DBG) log("continueCall: mSipSession" + mSipSession + " timeout=" + timeout);
- synchronized (this) {
- if (!mHold) return;
- mSipSession.changeCall(createContinueOffer().encode(), timeout);
- mHold = false;
- setAudioGroupMode();
- }
- }
-
- private SimpleSessionDescription createOffer() {
- SimpleSessionDescription offer =
- new SimpleSessionDescription(mSessionId, getLocalIp());
- AudioCodec[] codecs = AudioCodec.getCodecs();
- Media media = offer.newMedia(
- "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
- for (AudioCodec codec : AudioCodec.getCodecs()) {
- media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
- }
- media.setRtpPayload(127, "telephone-event/8000", "0-15");
- if (DBG) log("createOffer: offer=" + offer);
- return offer;
- }
-
- private SimpleSessionDescription createAnswer(String offerSd) {
- if (TextUtils.isEmpty(offerSd)) return createOffer();
- SimpleSessionDescription offer =
- new SimpleSessionDescription(offerSd);
- SimpleSessionDescription answer =
- new SimpleSessionDescription(mSessionId, getLocalIp());
- AudioCodec codec = null;
- for (Media media : offer.getMedia()) {
- if ((codec == null) && (media.getPort() > 0)
- && "audio".equals(media.getType())
- && "RTP/AVP".equals(media.getProtocol())) {
- // Find the first audio codec we supported.
- for (int type : media.getRtpPayloadTypes()) {
- codec = AudioCodec.getCodec(type, media.getRtpmap(type),
- media.getFmtp(type));
- if (codec != null) {
- break;
- }
- }
- if (codec != null) {
- Media reply = answer.newMedia(
- "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
- reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
-
- // Check if DTMF is supported in the same media.
- for (int type : media.getRtpPayloadTypes()) {
- String rtpmap = media.getRtpmap(type);
- if ((type != codec.type) && (rtpmap != null)
- && rtpmap.startsWith("telephone-event")) {
- reply.setRtpPayload(
- type, rtpmap, media.getFmtp(type));
- }
- }
-
- // Handle recvonly and sendonly.
- if (media.getAttribute("recvonly") != null) {
- answer.setAttribute("sendonly", "");
- } else if(media.getAttribute("sendonly") != null) {
- answer.setAttribute("recvonly", "");
- } else if(offer.getAttribute("recvonly") != null) {
- answer.setAttribute("sendonly", "");
- } else if(offer.getAttribute("sendonly") != null) {
- answer.setAttribute("recvonly", "");
- }
- continue;
- }
- }
- // Reject the media.
- Media reply = answer.newMedia(
- media.getType(), 0, 1, media.getProtocol());
- for (String format : media.getFormats()) {
- reply.setFormat(format, null);
- }
- }
- if (codec == null) {
- loge("createAnswer: no suitable codes");
- throw new IllegalStateException("Reject SDP: no suitable codecs");
- }
- if (DBG) log("createAnswer: answer=" + answer);
- return answer;
- }
-
- private SimpleSessionDescription createHoldOffer() {
- SimpleSessionDescription offer = createContinueOffer();
- offer.setAttribute("sendonly", "");
- if (DBG) log("createHoldOffer: offer=" + offer);
- return offer;
- }
-
- private SimpleSessionDescription createContinueOffer() {
- if (DBG) log("createContinueOffer");
- SimpleSessionDescription offer =
- new SimpleSessionDescription(mSessionId, getLocalIp());
- Media media = offer.newMedia(
- "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
- AudioCodec codec = mAudioStream.getCodec();
- media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
- int dtmfType = mAudioStream.getDtmfType();
- if (dtmfType != -1) {
- media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
- }
- return offer;
- }
-
- private void grabWifiHighPerfLock() {
- if (mWifiHighPerfLock == null) {
- if (DBG) log("grabWifiHighPerfLock:");
- mWifiHighPerfLock = ((WifiManager)
- mContext.getSystemService(Context.WIFI_SERVICE))
- .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, LOG_TAG);
- mWifiHighPerfLock.acquire();
- }
- }
-
- private void releaseWifiHighPerfLock() {
- if (mWifiHighPerfLock != null) {
- if (DBG) log("releaseWifiHighPerfLock:");
- mWifiHighPerfLock.release();
- mWifiHighPerfLock = null;
- }
- }
-
- private boolean isWifiOn() {
- return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
- }
-
- /** Toggles mute. */
- public void toggleMute() {
- synchronized (this) {
- mMuted = !mMuted;
- setAudioGroupMode();
- }
- }
-
- /**
- * Checks if the call is muted.
- *
- * @return true if the call is muted
- */
- public boolean isMuted() {
- synchronized (this) {
- return mMuted;
- }
- }
-
- /**
- * Puts the device to speaker mode.
- * <p class="note"><strong>Note:</strong> Requires the
- * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
- *
- * @param speakerMode set true to enable speaker mode; false to disable
- */
- public void setSpeakerMode(boolean speakerMode) {
- synchronized (this) {
- ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
- .setSpeakerphoneOn(speakerMode);
- setAudioGroupMode();
- }
- }
-
- private boolean isSpeakerOn() {
- return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
- .isSpeakerphoneOn();
- }
-
- /**
- * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
- * event 0--9 maps to decimal
- * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
- * flash to 16. Currently, event flash is not supported.
- *
- * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
- * inputs.
- */
- public void sendDtmf(int code) {
- sendDtmf(code, null);
- }
-
- /**
- * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
- * event 0--9 maps to decimal
- * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
- * flash to 16. Currently, event flash is not supported.
- *
- * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
- * inputs.
- * @param result the result message to send when done
- */
- public void sendDtmf(int code, Message result) {
- synchronized (this) {
- AudioGroup audioGroup = getAudioGroup();
- if ((audioGroup != null) && (mSipSession != null)
- && (SipSession.State.IN_CALL == getState())) {
- if (DBG) log("sendDtmf: code=" + code + " result=" + result);
- audioGroup.sendDtmf(code);
- }
- if (result != null) result.sendToTarget();
- }
- }
-
- /**
- * Gets the {@link AudioStream} object used in this call. The object
- * represents the RTP stream that carries the audio data to and from the
- * peer. The object may not be created before the call is established. And
- * it is undefined after the call ends or the {@link #close} method is
- * called.
- *
- * @return the {@link AudioStream} object or null if the RTP stream has not
- * yet been set up
- * @hide
- */
- public AudioStream getAudioStream() {
- synchronized (this) {
- return mAudioStream;
- }
- }
-
- /**
- * Gets the {@link AudioGroup} object which the {@link AudioStream} object
- * joins. The group object may not exist before the call is established.
- * Also, the {@code AudioStream} may change its group during a call (e.g.,
- * after the call is held/un-held). Finally, the {@code AudioGroup} object
- * returned by this method is undefined after the call ends or the
- * {@link #close} method is called. If a group object is set by
- * {@link #setAudioGroup(AudioGroup)}, then this method returns that object.
- *
- * @return the {@link AudioGroup} object or null if the RTP stream has not
- * yet been set up
- * @see #getAudioStream
- * @hide
- */
- public AudioGroup getAudioGroup() {
- synchronized (this) {
- if (mAudioGroup != null) return mAudioGroup;
- return ((mAudioStream == null) ? null : mAudioStream.getGroup());
- }
- }
-
- /**
- * Sets the {@link AudioGroup} object which the {@link AudioStream} object
- * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object
- * will be dynamically created when needed. Note that the mode of the
- * {@code AudioGroup} is not changed according to the audio settings (i.e.,
- * hold, mute, speaker phone) of this object. This is mainly used to merge
- * multiple {@code SipAudioCall} objects to form a conference call. The
- * settings of the first object (that merges others) override others'.
- *
- * @see #getAudioStream
- * @hide
- */
- public void setAudioGroup(AudioGroup group) {
- synchronized (this) {
- if (DBG) log("setAudioGroup: group=" + group);
- if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
- mAudioStream.join(group);
- }
- mAudioGroup = group;
- }
- }
-
- /**
- * Starts the audio for the established call. This method should be called
- * after {@link Listener#onCallEstablished} is called.
- * <p class="note"><strong>Note:</strong> Requires the
- * {@link android.Manifest.permission#RECORD_AUDIO},
- * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
- * {@link android.Manifest.permission#WAKE_LOCK} permissions.</p>
- */
- public void startAudio() {
- try {
- startAudioInternal();
- } catch (UnknownHostException e) {
- onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage());
- } catch (Throwable e) {
- onError(SipErrorCode.CLIENT_ERROR, e.getMessage());
- }
- }
-
- private synchronized void startAudioInternal() throws UnknownHostException {
- if (DBG) loge("startAudioInternal: mPeerSd=" + mPeerSd);
- if (mPeerSd == null) {
- throw new IllegalStateException("mPeerSd = null");
- }
-
- stopCall(DONT_RELEASE_SOCKET);
- mInCall = true;
-
- // Run exact the same logic in createAnswer() to setup mAudioStream.
- SimpleSessionDescription offer =
- new SimpleSessionDescription(mPeerSd);
- AudioStream stream = mAudioStream;
- AudioCodec codec = null;
- for (Media media : offer.getMedia()) {
- if ((codec == null) && (media.getPort() > 0)
- && "audio".equals(media.getType())
- && "RTP/AVP".equals(media.getProtocol())) {
- // Find the first audio codec we supported.
- for (int type : media.getRtpPayloadTypes()) {
- codec = AudioCodec.getCodec(
- type, media.getRtpmap(type), media.getFmtp(type));
- if (codec != null) {
- break;
- }
- }
-
- if (codec != null) {
- // Associate with the remote host.
- String address = media.getAddress();
- if (address == null) {
- address = offer.getAddress();
- }
- stream.associate(InetAddress.getByName(address),
- media.getPort());
-
- stream.setDtmfType(-1);
- stream.setCodec(codec);
- // Check if DTMF is supported in the same media.
- for (int type : media.getRtpPayloadTypes()) {
- String rtpmap = media.getRtpmap(type);
- if ((type != codec.type) && (rtpmap != null)
- && rtpmap.startsWith("telephone-event")) {
- stream.setDtmfType(type);
- }
- }
-
- // Handle recvonly and sendonly.
- if (mHold) {
- stream.setMode(RtpStream.MODE_NORMAL);
- } else if (media.getAttribute("recvonly") != null) {
- stream.setMode(RtpStream.MODE_SEND_ONLY);
- } else if(media.getAttribute("sendonly") != null) {
- stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
- } else if(offer.getAttribute("recvonly") != null) {
- stream.setMode(RtpStream.MODE_SEND_ONLY);
- } else if(offer.getAttribute("sendonly") != null) {
- stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
- } else {
- stream.setMode(RtpStream.MODE_NORMAL);
- }
- break;
- }
- }
- }
- if (codec == null) {
- throw new IllegalStateException("Reject SDP: no suitable codecs");
- }
-
- if (isWifiOn()) grabWifiHighPerfLock();
-
- // AudioGroup logic:
- AudioGroup audioGroup = getAudioGroup();
- if (mHold) {
- // don't create an AudioGroup here; doing so will fail if
- // there's another AudioGroup out there that's active
- } else {
- if (audioGroup == null) audioGroup = new AudioGroup();
- stream.join(audioGroup);
- }
- setAudioGroupMode();
- }
-
- // set audio group mode based on current audio configuration
- private void setAudioGroupMode() {
- AudioGroup audioGroup = getAudioGroup();
- if (DBG) log("setAudioGroupMode: audioGroup=" + audioGroup);
- if (audioGroup != null) {
- if (mHold) {
- audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
- } else if (mMuted) {
- audioGroup.setMode(AudioGroup.MODE_MUTED);
- } else if (isSpeakerOn()) {
- audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
- } else {
- audioGroup.setMode(AudioGroup.MODE_NORMAL);
- }
- }
- }
-
- private void stopCall(boolean releaseSocket) {
- if (DBG) log("stopCall: releaseSocket=" + releaseSocket);
- releaseWifiHighPerfLock();
- if (mAudioStream != null) {
- mAudioStream.join(null);
-
- if (releaseSocket) {
- mAudioStream.release();
- mAudioStream = null;
- }
- }
- }
-
- private String getLocalIp() {
- return mSipSession.getLocalIp();
- }
-
- private void throwSipException(Throwable throwable) throws SipException {
- if (throwable instanceof SipException) {
- throw (SipException) throwable;
- } else {
- throw new SipException("", throwable);
- }
- }
-
- private void log(String s) {
- Rlog.d(LOG_TAG, s);
- }
-
- private void loge(String s) {
- Rlog.e(LOG_TAG, s);
- }
-
- private void loge(String s, Throwable t) {
- Rlog.e(LOG_TAG, s, t);
- }
-}
diff --git a/voip/java/android/net/sip/SipErrorCode.java b/voip/java/android/net/sip/SipErrorCode.java
deleted file mode 100644
index 509728f..0000000
--- a/voip/java/android/net/sip/SipErrorCode.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-/**
- * Defines error codes returned during SIP actions. For example, during
- * {@link SipRegistrationListener#onRegistrationFailed onRegistrationFailed()},
- * {@link SipSession.Listener#onError onError()},
- * {@link SipSession.Listener#onCallChangeFailed onCallChangeFailed()} and
- * {@link SipSession.Listener#onRegistrationFailed onRegistrationFailed()}.
- */
-public class SipErrorCode {
- /** Not an error. */
- public static final int NO_ERROR = 0;
-
- /** When some socket error occurs. */
- public static final int SOCKET_ERROR = -1;
-
- /** When server responds with an error. */
- public static final int SERVER_ERROR = -2;
-
- /** When transaction is terminated unexpectedly. */
- public static final int TRANSACTION_TERMINTED = -3;
-
- /** When some error occurs on the device, possibly due to a bug. */
- public static final int CLIENT_ERROR = -4;
-
- /** When the transaction gets timed out. */
- public static final int TIME_OUT = -5;
-
- /** When the remote URI is not valid. */
- public static final int INVALID_REMOTE_URI = -6;
-
- /** When the peer is not reachable. */
- public static final int PEER_NOT_REACHABLE = -7;
-
- /** When invalid credentials are provided. */
- public static final int INVALID_CREDENTIALS = -8;
-
- /** The client is in a transaction and cannot initiate a new one. */
- public static final int IN_PROGRESS = -9;
-
- /** When data connection is lost. */
- public static final int DATA_CONNECTION_LOST = -10;
-
- /** Cross-domain authentication required. */
- public static final int CROSS_DOMAIN_AUTHENTICATION = -11;
-
- /** When the server is not reachable. */
- public static final int SERVER_UNREACHABLE = -12;
-
- public static String toString(int errorCode) {
- switch (errorCode) {
- case NO_ERROR:
- return "NO_ERROR";
- case SOCKET_ERROR:
- return "SOCKET_ERROR";
- case SERVER_ERROR:
- return "SERVER_ERROR";
- case TRANSACTION_TERMINTED:
- return "TRANSACTION_TERMINTED";
- case CLIENT_ERROR:
- return "CLIENT_ERROR";
- case TIME_OUT:
- return "TIME_OUT";
- case INVALID_REMOTE_URI:
- return "INVALID_REMOTE_URI";
- case PEER_NOT_REACHABLE:
- return "PEER_NOT_REACHABLE";
- case INVALID_CREDENTIALS:
- return "INVALID_CREDENTIALS";
- case IN_PROGRESS:
- return "IN_PROGRESS";
- case DATA_CONNECTION_LOST:
- return "DATA_CONNECTION_LOST";
- case CROSS_DOMAIN_AUTHENTICATION:
- return "CROSS_DOMAIN_AUTHENTICATION";
- case SERVER_UNREACHABLE:
- return "SERVER_UNREACHABLE";
- default:
- return "UNKNOWN";
- }
- }
-
- private SipErrorCode() {
- }
-}
diff --git a/voip/java/android/net/sip/SipException.java b/voip/java/android/net/sip/SipException.java
deleted file mode 100644
index 0339395..0000000
--- a/voip/java/android/net/sip/SipException.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-/**
- * Indicates a general SIP-related exception.
- */
-public class SipException extends Exception {
- public SipException() {
- }
-
- public SipException(String message) {
- super(message);
- }
-
- public SipException(String message, Throwable cause) {
- // we want to eliminate the dependency on javax.sip.SipException
- super(message, ((cause instanceof javax.sip.SipException)
- && (cause.getCause() != null))
- ? cause.getCause()
- : cause);
- }
-}
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
deleted file mode 100644
index a94232a..0000000
--- a/voip/java/android/net/sip/SipManager.java
+++ /dev/null
@@ -1,622 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.telephony.Rlog;
-
-import java.text.ParseException;
-
-/**
- * Provides APIs for SIP tasks, such as initiating SIP connections, and provides access to related
- * SIP services. This class is the starting point for any SIP actions. You can acquire an instance
- * of it with {@link #newInstance newInstance()}.</p>
- * <p>The APIs in this class allows you to:</p>
- * <ul>
- * <li>Create a {@link SipSession} to get ready for making calls or listen for incoming calls. See
- * {@link #createSipSession createSipSession()} and {@link #getSessionFor getSessionFor()}.</li>
- * <li>Initiate and receive generic SIP calls or audio-only SIP calls. Generic SIP calls may
- * be video, audio, or other, and are initiated with {@link #open open()}. Audio-only SIP calls
- * should be handled with a {@link SipAudioCall}, which you can acquire with {@link
- * #makeAudioCall makeAudioCall()} and {@link #takeAudioCall takeAudioCall()}.</li>
- * <li>Register and unregister with a SIP service provider, with
- * {@link #register register()} and {@link #unregister unregister()}.</li>
- * <li>Verify session connectivity, with {@link #isOpened isOpened()} and
- * {@link #isRegistered isRegistered()}.</li>
- * </ul>
- * <p class="note"><strong>Note:</strong> Not all Android-powered devices support VOIP calls using
- * SIP. You should always call {@link android.net.sip.SipManager#isVoipSupported
- * isVoipSupported()} to verify that the device supports VOIP calling and {@link
- * android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports
- * the SIP APIs. Your application must also request the {@link
- * android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP}
- * permissions.</p>
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For more information about using SIP, read the
- * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
- * developer guide.</p>
- * </div>
- */
-public class SipManager {
- /**
- * The result code to be sent back with the incoming call
- * {@link PendingIntent}.
- * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
- */
- public static final int INCOMING_CALL_RESULT_CODE = 101;
-
- /**
- * Key to retrieve the call ID from an incoming call intent.
- * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
- */
- public static final String EXTRA_CALL_ID = "android:sipCallID";
-
- /**
- * Key to retrieve the offered session description from an incoming call
- * intent.
- * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
- */
- public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
-
- /**
- * Action to broadcast when SipService is up.
- * Internal use only.
- * @hide
- */
- public static final String ACTION_SIP_SERVICE_UP =
- "android.net.sip.SIP_SERVICE_UP";
- /**
- * Action string for the incoming call intent for the Phone app.
- * Internal use only.
- * @hide
- */
- public static final String ACTION_SIP_INCOMING_CALL =
- "com.android.phone.SIP_INCOMING_CALL";
- /**
- * Action string for the add-phone intent.
- * Internal use only.
- * @hide
- */
- public static final String ACTION_SIP_ADD_PHONE =
- "com.android.phone.SIP_ADD_PHONE";
- /**
- * Action string for the remove-phone intent.
- * Internal use only.
- * @hide
- */
- public static final String ACTION_SIP_REMOVE_PHONE =
- "com.android.phone.SIP_REMOVE_PHONE";
- /**
- * Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents.
- * Internal use only.
- * @hide
- */
- public static final String EXTRA_LOCAL_URI = "android:localSipUri";
-
- private static final String TAG = "SipManager";
-
- private ISipService mSipService;
- private Context mContext;
-
- /**
- * Creates a manager instance. Returns null if SIP API is not supported.
- *
- * @param context application context for creating the manager object
- * @return the manager instance or null if SIP API is not supported
- */
- public static SipManager newInstance(Context context) {
- return (isApiSupported(context) ? new SipManager(context) : null);
- }
-
- /**
- * Returns true if the SIP API is supported by the system.
- */
- public static boolean isApiSupported(Context context) {
- return context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_SIP);
- }
-
- /**
- * Returns true if the system supports SIP-based VOIP API.
- */
- public static boolean isVoipSupported(Context context) {
- return context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
- }
-
- /**
- * Returns true if SIP is only available on WIFI.
- */
- public static boolean isSipWifiOnly(Context context) {
- return context.getResources().getBoolean(
- com.android.internal.R.bool.config_sip_wifi_only);
- }
-
- private SipManager(Context context) {
- mContext = context;
- createSipService();
- }
-
- private void createSipService() {
- IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
- mSipService = ISipService.Stub.asInterface(b);
- }
-
- /**
- * Opens the profile for making generic SIP calls. The caller may make subsequent calls
- * through {@link #makeAudioCall}. If one also wants to receive calls on the
- * profile, use
- * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}
- * instead.
- *
- * @param localProfile the SIP profile to make calls from
- * @throws SipException if the profile contains incorrect settings or
- * calling the SIP service results in an error
- */
- public void open(SipProfile localProfile) throws SipException {
- try {
- mSipService.open(localProfile);
- } catch (RemoteException e) {
- throw new SipException("open()", e);
- }
- }
-
- /**
- * Opens the profile for making calls and/or receiving generic SIP calls. The caller may
- * make subsequent calls through {@link #makeAudioCall}. If the
- * auto-registration option is enabled in the profile, the SIP service
- * will register the profile to the corresponding SIP provider periodically
- * in order to receive calls from the provider. When the SIP service
- * receives a new call, it will send out an intent with the provided action
- * string. The intent contains a call ID extra and an offer session
- * description string extra. Use {@link #getCallId} and
- * {@link #getOfferSessionDescription} to retrieve those extras.
- *
- * @param localProfile the SIP profile to receive incoming calls for
- * @param incomingCallPendingIntent When an incoming call is received, the
- * SIP service will call
- * {@link PendingIntent#send(Context, int, Intent)} to send back the
- * intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the
- * result code and the intent to fill in the call ID and session
- * description information. It cannot be null.
- * @param listener to listen to registration events; can be null
- * @see #getCallId
- * @see #getOfferSessionDescription
- * @see #takeAudioCall
- * @throws NullPointerException if {@code incomingCallPendingIntent} is null
- * @throws SipException if the profile contains incorrect settings or
- * calling the SIP service results in an error
- * @see #isIncomingCallIntent
- * @see #getCallId
- * @see #getOfferSessionDescription
- */
- public void open(SipProfile localProfile,
- PendingIntent incomingCallPendingIntent,
- SipRegistrationListener listener) throws SipException {
- if (incomingCallPendingIntent == null) {
- throw new NullPointerException(
- "incomingCallPendingIntent cannot be null");
- }
- try {
- mSipService.open3(localProfile, incomingCallPendingIntent,
- createRelay(listener, localProfile.getUriString()));
- } catch (RemoteException e) {
- throw new SipException("open()", e);
- }
- }
-
- /**
- * Sets the listener to listen to registration events. No effect if the
- * profile has not been opened to receive calls (see
- * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}).
- *
- * @param localProfileUri the URI of the profile
- * @param listener to listen to registration events; can be null
- * @throws SipException if calling the SIP service results in an error
- */
- public void setRegistrationListener(String localProfileUri,
- SipRegistrationListener listener) throws SipException {
- try {
- mSipService.setRegistrationListener(
- localProfileUri, createRelay(listener, localProfileUri));
- } catch (RemoteException e) {
- throw new SipException("setRegistrationListener()", e);
- }
- }
-
- /**
- * Closes the specified profile to not make/receive calls. All the resources
- * that were allocated to the profile are also released.
- *
- * @param localProfileUri the URI of the profile to close
- * @throws SipException if calling the SIP service results in an error
- */
- public void close(String localProfileUri) throws SipException {
- try {
- mSipService.close(localProfileUri);
- } catch (RemoteException e) {
- throw new SipException("close()", e);
- }
- }
-
- /**
- * Checks if the specified profile is opened in the SIP service for
- * making and/or receiving calls.
- *
- * @param localProfileUri the URI of the profile in question
- * @return true if the profile is enabled to receive calls
- * @throws SipException if calling the SIP service results in an error
- */
- public boolean isOpened(String localProfileUri) throws SipException {
- try {
- return mSipService.isOpened(localProfileUri);
- } catch (RemoteException e) {
- throw new SipException("isOpened()", e);
- }
- }
-
- /**
- * Checks if the SIP service has successfully registered the profile to the
- * SIP provider (specified in the profile) for receiving calls. Returning
- * true from this method also implies the profile is opened
- * ({@link #isOpened}).
- *
- * @param localProfileUri the URI of the profile in question
- * @return true if the profile is registered to the SIP provider; false if
- * the profile has not been opened in the SIP service or the SIP
- * service has not yet successfully registered the profile to the SIP
- * provider
- * @throws SipException if calling the SIP service results in an error
- */
- public boolean isRegistered(String localProfileUri) throws SipException {
- try {
- return mSipService.isRegistered(localProfileUri);
- } catch (RemoteException e) {
- throw new SipException("isRegistered()", e);
- }
- }
-
- /**
- * Creates a {@link SipAudioCall} to make a call. The attempt will be timed
- * out if the call is not established within {@code timeout} seconds and
- * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
- * will be called.
- *
- * @param localProfile the SIP profile to make the call from
- * @param peerProfile the SIP profile to make the call to
- * @param listener to listen to the call events from {@link SipAudioCall};
- * can be null
- * @param timeout the timeout value in seconds. Default value (defined by
- * SIP protocol) is used if {@code timeout} is zero or negative.
- * @return a {@link SipAudioCall} object
- * @throws SipException if calling the SIP service results in an error or
- * VOIP API is not supported by the device
- * @see SipAudioCall.Listener#onError
- * @see #isVoipSupported
- */
- public SipAudioCall makeAudioCall(SipProfile localProfile,
- SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
- throws SipException {
- if (!isVoipSupported(mContext)) {
- throw new SipException("VOIP API is not supported");
- }
- SipAudioCall call = new SipAudioCall(mContext, localProfile);
- call.setListener(listener);
- SipSession s = createSipSession(localProfile, null);
- call.makeCall(peerProfile, s, timeout);
- return call;
- }
-
- /**
- * Creates a {@link SipAudioCall} to make an audio call. The attempt will be
- * timed out if the call is not established within {@code timeout} seconds
- * and
- * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
- * will be called.
- *
- * @param localProfileUri URI of the SIP profile to make the call from
- * @param peerProfileUri URI of the SIP profile to make the call to
- * @param listener to listen to the call events from {@link SipAudioCall};
- * can be null
- * @param timeout the timeout value in seconds. Default value (defined by
- * SIP protocol) is used if {@code timeout} is zero or negative.
- * @return a {@link SipAudioCall} object
- * @throws SipException if calling the SIP service results in an error or
- * VOIP API is not supported by the device
- * @see SipAudioCall.Listener#onError
- * @see #isVoipSupported
- */
- public SipAudioCall makeAudioCall(String localProfileUri,
- String peerProfileUri, SipAudioCall.Listener listener, int timeout)
- throws SipException {
- if (!isVoipSupported(mContext)) {
- throw new SipException("VOIP API is not supported");
- }
- try {
- return makeAudioCall(
- new SipProfile.Builder(localProfileUri).build(),
- new SipProfile.Builder(peerProfileUri).build(), listener,
- timeout);
- } catch (ParseException e) {
- throw new SipException("build SipProfile", e);
- }
- }
-
- /**
- * Creates a {@link SipAudioCall} to take an incoming call. Before the call
- * is returned, the listener will receive a
- * {@link SipAudioCall.Listener#onRinging}
- * callback.
- *
- * @param incomingCallIntent the incoming call broadcast intent
- * @param listener to listen to the call events from {@link SipAudioCall};
- * can be null
- * @return a {@link SipAudioCall} object
- * @throws SipException if calling the SIP service results in an error
- */
- public SipAudioCall takeAudioCall(Intent incomingCallIntent,
- SipAudioCall.Listener listener) throws SipException {
- if (incomingCallIntent == null) {
- throw new SipException("Cannot retrieve session with null intent");
- }
-
- String callId = getCallId(incomingCallIntent);
- if (callId == null) {
- throw new SipException("Call ID missing in incoming call intent");
- }
-
- String offerSd = getOfferSessionDescription(incomingCallIntent);
- if (offerSd == null) {
- throw new SipException("Session description missing in incoming "
- + "call intent");
- }
-
- try {
- ISipSession session = mSipService.getPendingSession(callId);
- if (session == null) {
- throw new SipException("No pending session for the call");
- }
- SipAudioCall call = new SipAudioCall(
- mContext, session.getLocalProfile());
- call.attachCall(new SipSession(session), offerSd);
- call.setListener(listener);
- return call;
- } catch (Throwable t) {
- throw new SipException("takeAudioCall()", t);
- }
- }
-
- /**
- * Checks if the intent is an incoming call broadcast intent.
- *
- * @param intent the intent in question
- * @return true if the intent is an incoming call broadcast intent
- */
- public static boolean isIncomingCallIntent(Intent intent) {
- if (intent == null) return false;
- String callId = getCallId(intent);
- String offerSd = getOfferSessionDescription(intent);
- return ((callId != null) && (offerSd != null));
- }
-
- /**
- * Gets the call ID from the specified incoming call broadcast intent.
- *
- * @param incomingCallIntent the incoming call broadcast intent
- * @return the call ID or null if the intent does not contain it
- */
- public static String getCallId(Intent incomingCallIntent) {
- return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
- }
-
- /**
- * Gets the offer session description from the specified incoming call
- * broadcast intent.
- *
- * @param incomingCallIntent the incoming call broadcast intent
- * @return the offer session description or null if the intent does not
- * have it
- */
- public static String getOfferSessionDescription(Intent incomingCallIntent) {
- return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD);
- }
-
- /**
- * Creates an incoming call broadcast intent.
- *
- * @param callId the call ID of the incoming call
- * @param sessionDescription the session description of the incoming call
- * @return the incoming call intent
- * @hide
- */
- public static Intent createIncomingCallBroadcast(String callId,
- String sessionDescription) {
- Intent intent = new Intent();
- intent.putExtra(EXTRA_CALL_ID, callId);
- intent.putExtra(EXTRA_OFFER_SD, sessionDescription);
- return intent;
- }
-
- /**
- * Manually registers the profile to the corresponding SIP provider for
- * receiving calls.
- * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is
- * still needed to be called at least once in order for the SIP service to
- * notify the caller with the {@link android.app.PendingIntent} when an incoming call is
- * received.
- *
- * @param localProfile the SIP profile to register with
- * @param expiryTime registration expiration time (in seconds)
- * @param listener to listen to the registration events
- * @throws SipException if calling the SIP service results in an error
- */
- public void register(SipProfile localProfile, int expiryTime,
- SipRegistrationListener listener) throws SipException {
- try {
- ISipSession session = mSipService.createSession(localProfile,
- createRelay(listener, localProfile.getUriString()));
- if (session == null) {
- throw new SipException(
- "SipService.createSession() returns null");
- }
- session.register(expiryTime);
- } catch (RemoteException e) {
- throw new SipException("register()", e);
- }
- }
-
- /**
- * Manually unregisters the profile from the corresponding SIP provider for
- * stop receiving further calls. This may interference with the auto
- * registration process in the SIP service if the auto-registration option
- * in the profile is enabled.
- *
- * @param localProfile the SIP profile to register with
- * @param listener to listen to the registration events
- * @throws SipException if calling the SIP service results in an error
- */
- public void unregister(SipProfile localProfile,
- SipRegistrationListener listener) throws SipException {
- try {
- ISipSession session = mSipService.createSession(localProfile,
- createRelay(listener, localProfile.getUriString()));
- if (session == null) {
- throw new SipException(
- "SipService.createSession() returns null");
- }
- session.unregister();
- } catch (RemoteException e) {
- throw new SipException("unregister()", e);
- }
- }
-
- /**
- * Gets the {@link SipSession} that handles the incoming call. For audio
- * calls, consider to use {@link SipAudioCall} to handle the incoming call.
- * See {@link #takeAudioCall}. Note that the method may be called only once
- * for the same intent. For subsequent calls on the same intent, the method
- * returns null.
- *
- * @param incomingCallIntent the incoming call broadcast intent
- * @return the session object that handles the incoming call
- */
- public SipSession getSessionFor(Intent incomingCallIntent)
- throws SipException {
- try {
- String callId = getCallId(incomingCallIntent);
- ISipSession s = mSipService.getPendingSession(callId);
- return ((s == null) ? null : new SipSession(s));
- } catch (RemoteException e) {
- throw new SipException("getSessionFor()", e);
- }
- }
-
- private static ISipSessionListener createRelay(
- SipRegistrationListener listener, String uri) {
- return ((listener == null) ? null : new ListenerRelay(listener, uri));
- }
-
- /**
- * Creates a {@link SipSession} with the specified profile. Use other
- * methods, if applicable, instead of interacting with {@link SipSession}
- * directly.
- *
- * @param localProfile the SIP profile the session is associated with
- * @param listener to listen to SIP session events
- */
- public SipSession createSipSession(SipProfile localProfile,
- SipSession.Listener listener) throws SipException {
- try {
- ISipSession s = mSipService.createSession(localProfile, null);
- if (s == null) {
- throw new SipException(
- "Failed to create SipSession; network unavailable?");
- }
- return new SipSession(s, listener);
- } catch (RemoteException e) {
- throw new SipException("createSipSession()", e);
- }
- }
-
- /**
- * Gets the list of profiles hosted by the SIP service. The user information
- * (username, password and display name) are crossed out.
- * @hide
- */
- public SipProfile[] getListOfProfiles() {
- try {
- return mSipService.getListOfProfiles();
- } catch (RemoteException e) {
- return new SipProfile[0];
- }
- }
-
- private static class ListenerRelay extends SipSessionAdapter {
- private SipRegistrationListener mListener;
- private String mUri;
-
- // listener must not be null
- public ListenerRelay(SipRegistrationListener listener, String uri) {
- mListener = listener;
- mUri = uri;
- }
-
- private String getUri(ISipSession session) {
- try {
- return ((session == null)
- ? mUri
- : session.getLocalProfile().getUriString());
- } catch (Throwable e) {
- // SipService died? SIP stack died?
- Rlog.e(TAG, "getUri(): ", e);
- return null;
- }
- }
-
- @Override
- public void onRegistering(ISipSession session) {
- mListener.onRegistering(getUri(session));
- }
-
- @Override
- public void onRegistrationDone(ISipSession session, int duration) {
- long expiryTime = duration;
- if (duration > 0) expiryTime += System.currentTimeMillis();
- mListener.onRegistrationDone(getUri(session), expiryTime);
- }
-
- @Override
- public void onRegistrationFailed(ISipSession session, int errorCode,
- String message) {
- mListener.onRegistrationFailed(getUri(session), errorCode, message);
- }
-
- @Override
- public void onRegistrationTimeout(ISipSession session) {
- mListener.onRegistrationFailed(getUri(session),
- SipErrorCode.TIME_OUT, "registration timed out");
- }
- }
-}
diff --git a/voip/java/android/net/sip/SipProfile.aidl b/voip/java/android/net/sip/SipProfile.aidl
deleted file mode 100644
index 3b6f68f..0000000
--- a/voip/java/android/net/sip/SipProfile.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2010, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-parcelable SipProfile;
diff --git a/voip/java/android/net/sip/SipProfile.java b/voip/java/android/net/sip/SipProfile.java
deleted file mode 100644
index 0ef754c..0000000
--- a/voip/java/android/net/sip/SipProfile.java
+++ /dev/null
@@ -1,502 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.io.ObjectStreamException;
-import java.io.Serializable;
-import java.text.ParseException;
-import javax.sip.InvalidArgumentException;
-import javax.sip.ListeningPoint;
-import javax.sip.PeerUnavailableException;
-import javax.sip.SipFactory;
-import javax.sip.address.Address;
-import javax.sip.address.AddressFactory;
-import javax.sip.address.SipURI;
-import javax.sip.address.URI;
-
-/**
- * Defines a SIP profile, including a SIP account, domain and server information.
- * <p>You can create a {@link SipProfile} using {@link
- * SipProfile.Builder}. You can also retrieve one from a {@link SipSession}, using {@link
- * SipSession#getLocalProfile} and {@link SipSession#getPeerProfile}.</p>
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For more information about using SIP, read the
- * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
- * developer guide.</p>
- * </div>
- */
-public class SipProfile implements Parcelable, Serializable, Cloneable {
- private static final long serialVersionUID = 1L;
- private static final int DEFAULT_PORT = 5060;
- private static final String TCP = "TCP";
- private static final String UDP = "UDP";
- private Address mAddress;
- private String mProxyAddress;
- private String mPassword;
- private String mDomain;
- private String mProtocol = UDP;
- private String mProfileName;
- private String mAuthUserName;
- private int mPort = DEFAULT_PORT;
- private boolean mSendKeepAlive = false;
- private boolean mAutoRegistration = true;
- private transient int mCallingUid = 0;
-
- public static final Parcelable.Creator<SipProfile> CREATOR =
- new Parcelable.Creator<SipProfile>() {
- public SipProfile createFromParcel(Parcel in) {
- return new SipProfile(in);
- }
-
- public SipProfile[] newArray(int size) {
- return new SipProfile[size];
- }
- };
-
- /**
- * Helper class for creating a {@link SipProfile}.
- */
- public static class Builder {
- private AddressFactory mAddressFactory;
- private SipProfile mProfile = new SipProfile();
- private SipURI mUri;
- private String mDisplayName;
- private String mProxyAddress;
-
- {
- try {
- mAddressFactory =
- SipFactory.getInstance().createAddressFactory();
- } catch (PeerUnavailableException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Creates a builder based on the given profile.
- */
- public Builder(SipProfile profile) {
- if (profile == null) throw new NullPointerException();
- try {
- mProfile = (SipProfile) profile.clone();
- } catch (CloneNotSupportedException e) {
- throw new RuntimeException("should not occur", e);
- }
- mProfile.mAddress = null;
- mUri = profile.getUri();
- mUri.setUserPassword(profile.getPassword());
- mDisplayName = profile.getDisplayName();
- mProxyAddress = profile.getProxyAddress();
- mProfile.mPort = profile.getPort();
- }
-
- /**
- * Constructor.
- *
- * @param uriString the URI string as "sip:<user_name>@<domain>"
- * @throws ParseException if the string is not a valid URI
- */
- public Builder(String uriString) throws ParseException {
- if (uriString == null) {
- throw new NullPointerException("uriString cannot be null");
- }
- URI uri = mAddressFactory.createURI(fix(uriString));
- if (uri instanceof SipURI) {
- mUri = (SipURI) uri;
- } else {
- throw new ParseException(uriString + " is not a SIP URI", 0);
- }
- mProfile.mDomain = mUri.getHost();
- }
-
- /**
- * Constructor.
- *
- * @param username username of the SIP account
- * @param serverDomain the SIP server domain; if the network address
- * is different from the domain, use {@link #setOutboundProxy} to
- * set server address
- * @throws ParseException if the parameters are not valid
- */
- public Builder(String username, String serverDomain)
- throws ParseException {
- if ((username == null) || (serverDomain == null)) {
- throw new NullPointerException(
- "username and serverDomain cannot be null");
- }
- mUri = mAddressFactory.createSipURI(username, serverDomain);
- mProfile.mDomain = serverDomain;
- }
-
- private String fix(String uriString) {
- return (uriString.trim().toLowerCase().startsWith("sip:")
- ? uriString
- : "sip:" + uriString);
- }
-
- /**
- * Sets the username used for authentication.
- *
- * @param name authentication username of the profile
- * @return this builder object
- */
- public Builder setAuthUserName(String name) {
- mProfile.mAuthUserName = name;
- return this;
- }
-
- /**
- * Sets the name of the profile. This name is given by user.
- *
- * @param name name of the profile
- * @return this builder object
- */
- public Builder setProfileName(String name) {
- mProfile.mProfileName = name;
- return this;
- }
-
- /**
- * Sets the password of the SIP account
- *
- * @param password password of the SIP account
- * @return this builder object
- */
- public Builder setPassword(String password) {
- mUri.setUserPassword(password);
- return this;
- }
-
- /**
- * Sets the port number of the server. By default, it is 5060.
- *
- * @param port port number of the server
- * @return this builder object
- * @throws IllegalArgumentException if the port number is out of range
- */
- public Builder setPort(int port) throws IllegalArgumentException {
- if ((port > 65535) || (port < 1000)) {
- throw new IllegalArgumentException("incorrect port arugment: " + port);
- }
- mProfile.mPort = port;
- return this;
- }
-
- /**
- * Sets the protocol used to connect to the SIP server. Currently,
- * only "UDP" and "TCP" are supported.
- *
- * @param protocol the protocol string
- * @return this builder object
- * @throws IllegalArgumentException if the protocol is not recognized
- */
- public Builder setProtocol(String protocol)
- throws IllegalArgumentException {
- if (protocol == null) {
- throw new NullPointerException("protocol cannot be null");
- }
- protocol = protocol.toUpperCase();
- if (!protocol.equals(UDP) && !protocol.equals(TCP)) {
- throw new IllegalArgumentException(
- "unsupported protocol: " + protocol);
- }
- mProfile.mProtocol = protocol;
- return this;
- }
-
- /**
- * Sets the outbound proxy of the SIP server.
- *
- * @param outboundProxy the network address of the outbound proxy
- * @return this builder object
- */
- public Builder setOutboundProxy(String outboundProxy) {
- mProxyAddress = outboundProxy;
- return this;
- }
-
- /**
- * Sets the display name of the user.
- *
- * @param displayName display name of the user
- * @return this builder object
- */
- public Builder setDisplayName(String displayName) {
- mDisplayName = displayName;
- return this;
- }
-
- /**
- * Sets the send keep-alive flag.
- *
- * @param flag true if sending keep-alive message is required,
- * false otherwise
- * @return this builder object
- */
- public Builder setSendKeepAlive(boolean flag) {
- mProfile.mSendKeepAlive = flag;
- return this;
- }
-
-
- /**
- * Sets the auto. registration flag.
- *
- * @param flag true if the profile will be registered automatically,
- * false otherwise
- * @return this builder object
- */
- public Builder setAutoRegistration(boolean flag) {
- mProfile.mAutoRegistration = flag;
- return this;
- }
-
- /**
- * Builds and returns the SIP profile object.
- *
- * @return the profile object created
- */
- public SipProfile build() {
- // remove password from URI
- mProfile.mPassword = mUri.getUserPassword();
- mUri.setUserPassword(null);
- try {
- if (!TextUtils.isEmpty(mProxyAddress)) {
- SipURI uri = (SipURI)
- mAddressFactory.createURI(fix(mProxyAddress));
- mProfile.mProxyAddress = uri.getHost();
- } else {
- if (!mProfile.mProtocol.equals(UDP)) {
- mUri.setTransportParam(mProfile.mProtocol);
- }
- if (mProfile.mPort != DEFAULT_PORT) {
- mUri.setPort(mProfile.mPort);
- }
- }
- mProfile.mAddress = mAddressFactory.createAddress(
- mDisplayName, mUri);
- } catch (InvalidArgumentException e) {
- throw new RuntimeException(e);
- } catch (ParseException e) {
- // must not occur
- throw new RuntimeException(e);
- }
- return mProfile;
- }
- }
-
- private SipProfile() {
- }
-
- private SipProfile(Parcel in) {
- mAddress = (Address) in.readSerializable();
- mProxyAddress = in.readString();
- mPassword = in.readString();
- mDomain = in.readString();
- mProtocol = in.readString();
- mProfileName = in.readString();
- mSendKeepAlive = (in.readInt() == 0) ? false : true;
- mAutoRegistration = (in.readInt() == 0) ? false : true;
- mCallingUid = in.readInt();
- mPort = in.readInt();
- mAuthUserName = in.readString();
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeSerializable(mAddress);
- out.writeString(mProxyAddress);
- out.writeString(mPassword);
- out.writeString(mDomain);
- out.writeString(mProtocol);
- out.writeString(mProfileName);
- out.writeInt(mSendKeepAlive ? 1 : 0);
- out.writeInt(mAutoRegistration ? 1 : 0);
- out.writeInt(mCallingUid);
- out.writeInt(mPort);
- out.writeString(mAuthUserName);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- /**
- * Gets the SIP URI of this profile.
- *
- * @return the SIP URI of this profile
- * @hide
- */
- public SipURI getUri() {
- return (SipURI) mAddress.getURI();
- }
-
- /**
- * Gets the SIP URI string of this profile.
- *
- * @return the SIP URI string of this profile
- */
- public String getUriString() {
- // We need to return the sip uri domain instead of
- // the SIP URI with transport, port information if
- // the outbound proxy address exists.
- if (!TextUtils.isEmpty(mProxyAddress)) {
- return "sip:" + getUserName() + "@" + mDomain;
- }
- return getUri().toString();
- }
-
- /**
- * Gets the SIP address of this profile.
- *
- * @return the SIP address of this profile
- * @hide
- */
- public Address getSipAddress() {
- return mAddress;
- }
-
- /**
- * Gets the display name of the user.
- *
- * @return the display name of the user
- */
- public String getDisplayName() {
- return mAddress.getDisplayName();
- }
-
- /**
- * Gets the username.
- *
- * @return the username
- */
- public String getUserName() {
- return getUri().getUser();
- }
-
- /**
- * Gets the username for authentication. If it is null, then the username
- * is used in authentication instead.
- *
- * @return the authentication username
- * @see #getUserName
- */
- public String getAuthUserName() {
- return mAuthUserName;
- }
-
- /**
- * Gets the password.
- *
- * @return the password
- */
- public String getPassword() {
- return mPassword;
- }
-
- /**
- * Gets the SIP domain.
- *
- * @return the SIP domain
- */
- public String getSipDomain() {
- return mDomain;
- }
-
- /**
- * Gets the port number of the SIP server.
- *
- * @return the port number of the SIP server
- */
- public int getPort() {
- return mPort;
- }
-
- /**
- * Gets the protocol used to connect to the server.
- *
- * @return the protocol
- */
- public String getProtocol() {
- return mProtocol;
- }
-
- /**
- * Gets the network address of the server outbound proxy.
- *
- * @return the network address of the server outbound proxy
- */
- public String getProxyAddress() {
- return mProxyAddress;
- }
-
- /**
- * Gets the (user-defined) name of the profile.
- *
- * @return name of the profile
- */
- public String getProfileName() {
- return mProfileName;
- }
-
- /**
- * Gets the flag of 'Sending keep-alive'.
- *
- * @return the flag of sending SIP keep-alive messages.
- */
- public boolean getSendKeepAlive() {
- return mSendKeepAlive;
- }
-
- /**
- * Gets the flag of 'Auto Registration'.
- *
- * @return the flag of registering the profile automatically.
- */
- public boolean getAutoRegistration() {
- return mAutoRegistration;
- }
-
- /**
- * Sets the calling process's Uid in the sip service.
- * @hide
- */
- public void setCallingUid(int uid) {
- mCallingUid = uid;
- }
-
- /**
- * Gets the calling process's Uid in the sip settings.
- * @hide
- */
- public int getCallingUid() {
- return mCallingUid;
- }
-
- private Object readResolve() throws ObjectStreamException {
- // For compatibility.
- if (mPort == 0) mPort = DEFAULT_PORT;
- return this;
- }
-}
diff --git a/voip/java/android/net/sip/SipRegistrationListener.java b/voip/java/android/net/sip/SipRegistrationListener.java
deleted file mode 100644
index 9968cc7..0000000
--- a/voip/java/android/net/sip/SipRegistrationListener.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-/**
- * Listener for SIP registration events.
- */
-public interface SipRegistrationListener {
- /**
- * Called when a registration request is sent.
- *
- * @param localProfileUri the URI string of the SIP profile to register with
- */
- void onRegistering(String localProfileUri);
-
- /**
- * Called when the registration succeeded.
- *
- * @param localProfileUri the URI string of the SIP profile to register with
- * @param expiryTime duration in seconds before the registration expires
- */
- void onRegistrationDone(String localProfileUri, long expiryTime);
-
- /**
- * Called when the registration failed.
- *
- * @param localProfileUri the URI string of the SIP profile to register with
- * @param errorCode error code of this error
- * @param errorMessage error message
- * @see SipErrorCode
- */
- void onRegistrationFailed(String localProfileUri, int errorCode,
- String errorMessage);
-}
diff --git a/voip/java/android/net/sip/SipSession.java b/voip/java/android/net/sip/SipSession.java
deleted file mode 100644
index edbc66f..0000000
--- a/voip/java/android/net/sip/SipSession.java
+++ /dev/null
@@ -1,574 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.os.RemoteException;
-import android.telephony.Rlog;
-
-/**
- * Represents a SIP session that is associated with a SIP dialog or a standalone
- * transaction not within a dialog.
- * <p>You can get a {@link SipSession} from {@link SipManager} with {@link
- * SipManager#createSipSession createSipSession()} (when initiating calls) or {@link
- * SipManager#getSessionFor getSessionFor()} (when receiving calls).</p>
- */
-public final class SipSession {
- private static final String TAG = "SipSession";
-
- /**
- * Defines SIP session states, such as "registering", "outgoing call", and "in call".
- */
- public static class State {
- /** When session is ready to initiate a call or transaction. */
- public static final int READY_TO_CALL = 0;
-
- /** When the registration request is sent out. */
- public static final int REGISTERING = 1;
-
- /** When the unregistration request is sent out. */
- public static final int DEREGISTERING = 2;
-
- /** When an INVITE request is received. */
- public static final int INCOMING_CALL = 3;
-
- /** When an OK response is sent for the INVITE request received. */
- public static final int INCOMING_CALL_ANSWERING = 4;
-
- /** When an INVITE request is sent. */
- public static final int OUTGOING_CALL = 5;
-
- /** When a RINGING response is received for the INVITE request sent. */
- public static final int OUTGOING_CALL_RING_BACK = 6;
-
- /** When a CANCEL request is sent for the INVITE request sent. */
- public static final int OUTGOING_CALL_CANCELING = 7;
-
- /** When a call is established. */
- public static final int IN_CALL = 8;
-
- /** When an OPTIONS request is sent. */
- public static final int PINGING = 9;
-
- /** When ending a call. @hide */
- public static final int ENDING_CALL = 10;
-
- /** Not defined. */
- public static final int NOT_DEFINED = 101;
-
- /**
- * Converts the state to string.
- */
- public static String toString(int state) {
- switch (state) {
- case READY_TO_CALL:
- return "READY_TO_CALL";
- case REGISTERING:
- return "REGISTERING";
- case DEREGISTERING:
- return "DEREGISTERING";
- case INCOMING_CALL:
- return "INCOMING_CALL";
- case INCOMING_CALL_ANSWERING:
- return "INCOMING_CALL_ANSWERING";
- case OUTGOING_CALL:
- return "OUTGOING_CALL";
- case OUTGOING_CALL_RING_BACK:
- return "OUTGOING_CALL_RING_BACK";
- case OUTGOING_CALL_CANCELING:
- return "OUTGOING_CALL_CANCELING";
- case IN_CALL:
- return "IN_CALL";
- case PINGING:
- return "PINGING";
- default:
- return "NOT_DEFINED";
- }
- }
-
- private State() {
- }
- }
-
- /**
- * Listener for events relating to a SIP session, such as when a session is being registered
- * ("on registering") or a call is outgoing ("on calling").
- * <p>Many of these events are also received by {@link SipAudioCall.Listener}.</p>
- */
- public static class Listener {
- /**
- * Called when an INVITE request is sent to initiate a new call.
- *
- * @param session the session object that carries out the transaction
- */
- public void onCalling(SipSession session) {
- }
-
- /**
- * Called when an INVITE request is received.
- *
- * @param session the session object that carries out the transaction
- * @param caller the SIP profile of the caller
- * @param sessionDescription the caller's session description
- */
- public void onRinging(SipSession session, SipProfile caller,
- String sessionDescription) {
- }
-
- /**
- * Called when a RINGING response is received for the INVITE request sent
- *
- * @param session the session object that carries out the transaction
- */
- public void onRingingBack(SipSession session) {
- }
-
- /**
- * Called when the session is established.
- *
- * @param session the session object that is associated with the dialog
- * @param sessionDescription the peer's session description
- */
- public void onCallEstablished(SipSession session,
- String sessionDescription) {
- }
-
- /**
- * Called when the session is terminated.
- *
- * @param session the session object that is associated with the dialog
- */
- public void onCallEnded(SipSession session) {
- }
-
- /**
- * Called when the peer is busy during session initialization.
- *
- * @param session the session object that carries out the transaction
- */
- public void onCallBusy(SipSession session) {
- }
-
- /**
- * Called when the call is being transferred to a new one.
- *
- * @hide
- * @param newSession the new session that the call will be transferred to
- * @param sessionDescription the new peer's session description
- */
- public void onCallTransferring(SipSession newSession,
- String sessionDescription) {
- }
-
- /**
- * Called when an error occurs during session initialization and
- * termination.
- *
- * @param session the session object that carries out the transaction
- * @param errorCode error code defined in {@link SipErrorCode}
- * @param errorMessage error message
- */
- public void onError(SipSession session, int errorCode,
- String errorMessage) {
- }
-
- /**
- * Called when an error occurs during session modification negotiation.
- *
- * @param session the session object that carries out the transaction
- * @param errorCode error code defined in {@link SipErrorCode}
- * @param errorMessage error message
- */
- public void onCallChangeFailed(SipSession session, int errorCode,
- String errorMessage) {
- }
-
- /**
- * Called when a registration request is sent.
- *
- * @param session the session object that carries out the transaction
- */
- public void onRegistering(SipSession session) {
- }
-
- /**
- * Called when registration is successfully done.
- *
- * @param session the session object that carries out the transaction
- * @param duration duration in second before the registration expires
- */
- public void onRegistrationDone(SipSession session, int duration) {
- }
-
- /**
- * Called when the registration fails.
- *
- * @param session the session object that carries out the transaction
- * @param errorCode error code defined in {@link SipErrorCode}
- * @param errorMessage error message
- */
- public void onRegistrationFailed(SipSession session, int errorCode,
- String errorMessage) {
- }
-
- /**
- * Called when the registration gets timed out.
- *
- * @param session the session object that carries out the transaction
- */
- public void onRegistrationTimeout(SipSession session) {
- }
- }
-
- private final ISipSession mSession;
- private Listener mListener;
-
- SipSession(ISipSession realSession) {
- mSession = realSession;
- if (realSession != null) {
- try {
- realSession.setListener(createListener());
- } catch (RemoteException e) {
- loge("SipSession.setListener:", e);
- }
- }
- }
-
- SipSession(ISipSession realSession, Listener listener) {
- this(realSession);
- setListener(listener);
- }
-
- /**
- * Gets the IP address of the local host on which this SIP session runs.
- *
- * @return the IP address of the local host
- */
- public String getLocalIp() {
- try {
- return mSession.getLocalIp();
- } catch (RemoteException e) {
- loge("getLocalIp:", e);
- return "127.0.0.1";
- }
- }
-
- /**
- * Gets the SIP profile that this session is associated with.
- *
- * @return the SIP profile that this session is associated with
- */
- public SipProfile getLocalProfile() {
- try {
- return mSession.getLocalProfile();
- } catch (RemoteException e) {
- loge("getLocalProfile:", e);
- return null;
- }
- }
-
- /**
- * Gets the SIP profile that this session is connected to. Only available
- * when the session is associated with a SIP dialog.
- *
- * @return the SIP profile that this session is connected to
- */
- public SipProfile getPeerProfile() {
- try {
- return mSession.getPeerProfile();
- } catch (RemoteException e) {
- loge("getPeerProfile:", e);
- return null;
- }
- }
-
- /**
- * Gets the session state. The value returned must be one of the states in
- * {@link State}.
- *
- * @return the session state
- */
- public int getState() {
- try {
- return mSession.getState();
- } catch (RemoteException e) {
- loge("getState:", e);
- return State.NOT_DEFINED;
- }
- }
-
- /**
- * Checks if the session is in a call.
- *
- * @return true if the session is in a call
- */
- public boolean isInCall() {
- try {
- return mSession.isInCall();
- } catch (RemoteException e) {
- loge("isInCall:", e);
- return false;
- }
- }
-
- /**
- * Gets the call ID of the session.
- *
- * @return the call ID
- */
- public String getCallId() {
- try {
- return mSession.getCallId();
- } catch (RemoteException e) {
- loge("getCallId:", e);
- return null;
- }
- }
-
-
- /**
- * Sets the listener to listen to the session events. A {@code SipSession}
- * can only hold one listener at a time. Subsequent calls to this method
- * override the previous listener.
- *
- * @param listener to listen to the session events of this object
- */
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
-
- /**
- * Performs registration to the server specified by the associated local
- * profile. The session listener is called back upon success or failure of
- * registration. The method is only valid to call when the session state is
- * in {@link State#READY_TO_CALL}.
- *
- * @param duration duration in second before the registration expires
- * @see Listener
- */
- public void register(int duration) {
- try {
- mSession.register(duration);
- } catch (RemoteException e) {
- loge("register:", e);
- }
- }
-
- /**
- * Performs unregistration to the server specified by the associated local
- * profile. Unregistration is technically the same as registration with zero
- * expiration duration. The session listener is called back upon success or
- * failure of unregistration. The method is only valid to call when the
- * session state is in {@link State#READY_TO_CALL}.
- *
- * @see Listener
- */
- public void unregister() {
- try {
- mSession.unregister();
- } catch (RemoteException e) {
- loge("unregister:", e);
- }
- }
-
- /**
- * Initiates a call to the specified profile. The session listener is called
- * back upon defined session events. The method is only valid to call when
- * the session state is in {@link State#READY_TO_CALL}.
- *
- * @param callee the SIP profile to make the call to
- * @param sessionDescription the session description of this call
- * @param timeout the session will be timed out if the call is not
- * established within {@code timeout} seconds. Default value (defined
- * by SIP protocol) is used if {@code timeout} is zero or negative.
- * @see Listener
- */
- public void makeCall(SipProfile callee, String sessionDescription,
- int timeout) {
- try {
- mSession.makeCall(callee, sessionDescription, timeout);
- } catch (RemoteException e) {
- loge("makeCall:", e);
- }
- }
-
- /**
- * Answers an incoming call with the specified session description. The
- * method is only valid to call when the session state is in
- * {@link State#INCOMING_CALL}.
- *
- * @param sessionDescription the session description to answer this call
- * @param timeout the session will be timed out if the call is not
- * established within {@code timeout} seconds. Default value (defined
- * by SIP protocol) is used if {@code timeout} is zero or negative.
- */
- public void answerCall(String sessionDescription, int timeout) {
- try {
- mSession.answerCall(sessionDescription, timeout);
- } catch (RemoteException e) {
- loge("answerCall:", e);
- }
- }
-
- /**
- * Ends an established call, terminates an outgoing call or rejects an
- * incoming call. The method is only valid to call when the session state is
- * in {@link State#IN_CALL},
- * {@link State#INCOMING_CALL},
- * {@link State#OUTGOING_CALL} or
- * {@link State#OUTGOING_CALL_RING_BACK}.
- */
- public void endCall() {
- try {
- mSession.endCall();
- } catch (RemoteException e) {
- loge("endCall:", e);
- }
- }
-
- /**
- * Changes the session description during a call. The method is only valid
- * to call when the session state is in {@link State#IN_CALL}.
- *
- * @param sessionDescription the new session description
- * @param timeout the session will be timed out if the call is not
- * established within {@code timeout} seconds. Default value (defined
- * by SIP protocol) is used if {@code timeout} is zero or negative.
- */
- public void changeCall(String sessionDescription, int timeout) {
- try {
- mSession.changeCall(sessionDescription, timeout);
- } catch (RemoteException e) {
- loge("changeCall:", e);
- }
- }
-
- ISipSession getRealSession() {
- return mSession;
- }
-
- private ISipSessionListener createListener() {
- return new ISipSessionListener.Stub() {
- @Override
- public void onCalling(ISipSession session) {
- if (mListener != null) {
- mListener.onCalling(SipSession.this);
- }
- }
-
- @Override
- public void onRinging(ISipSession session, SipProfile caller,
- String sessionDescription) {
- if (mListener != null) {
- mListener.onRinging(SipSession.this, caller,
- sessionDescription);
- }
- }
-
- @Override
- public void onRingingBack(ISipSession session) {
- if (mListener != null) {
- mListener.onRingingBack(SipSession.this);
- }
- }
-
- @Override
- public void onCallEstablished(ISipSession session,
- String sessionDescription) {
- if (mListener != null) {
- mListener.onCallEstablished(SipSession.this,
- sessionDescription);
- }
- }
-
- @Override
- public void onCallEnded(ISipSession session) {
- if (mListener != null) {
- mListener.onCallEnded(SipSession.this);
- }
- }
-
- @Override
- public void onCallBusy(ISipSession session) {
- if (mListener != null) {
- mListener.onCallBusy(SipSession.this);
- }
- }
-
- @Override
- public void onCallTransferring(ISipSession session,
- String sessionDescription) {
- if (mListener != null) {
- mListener.onCallTransferring(
- new SipSession(session, SipSession.this.mListener),
- sessionDescription);
-
- }
- }
-
- @Override
- public void onCallChangeFailed(ISipSession session, int errorCode,
- String message) {
- if (mListener != null) {
- mListener.onCallChangeFailed(SipSession.this, errorCode,
- message);
- }
- }
-
- @Override
- public void onError(ISipSession session, int errorCode, String message) {
- if (mListener != null) {
- mListener.onError(SipSession.this, errorCode, message);
- }
- }
-
- @Override
- public void onRegistering(ISipSession session) {
- if (mListener != null) {
- mListener.onRegistering(SipSession.this);
- }
- }
-
- @Override
- public void onRegistrationDone(ISipSession session, int duration) {
- if (mListener != null) {
- mListener.onRegistrationDone(SipSession.this, duration);
- }
- }
-
- @Override
- public void onRegistrationFailed(ISipSession session, int errorCode,
- String message) {
- if (mListener != null) {
- mListener.onRegistrationFailed(SipSession.this, errorCode,
- message);
- }
- }
-
- @Override
- public void onRegistrationTimeout(ISipSession session) {
- if (mListener != null) {
- mListener.onRegistrationTimeout(SipSession.this);
- }
- }
- };
- }
-
- private void loge(String s, Throwable t) {
- Rlog.e(TAG, s, t);
- }
-}
diff --git a/voip/java/android/net/sip/SipSessionAdapter.java b/voip/java/android/net/sip/SipSessionAdapter.java
deleted file mode 100644
index f538983..0000000
--- a/voip/java/android/net/sip/SipSessionAdapter.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-/**
- * Adapter class for {@link ISipSessionListener}. Default implementation of all
- * callback methods is no-op.
- * @hide
- */
-public class SipSessionAdapter extends ISipSessionListener.Stub {
- public void onCalling(ISipSession session) {
- }
-
- public void onRinging(ISipSession session, SipProfile caller,
- String sessionDescription) {
- }
-
- public void onRingingBack(ISipSession session) {
- }
-
- public void onCallEstablished(ISipSession session,
- String sessionDescription) {
- }
-
- public void onCallEnded(ISipSession session) {
- }
-
- public void onCallBusy(ISipSession session) {
- }
-
- public void onCallTransferring(ISipSession session,
- String sessionDescription) {
- }
-
- public void onCallChangeFailed(ISipSession session, int errorCode,
- String message) {
- }
-
- public void onError(ISipSession session, int errorCode, String message) {
- }
-
- public void onRegistering(ISipSession session) {
- }
-
- public void onRegistrationDone(ISipSession session, int duration) {
- }
-
- public void onRegistrationFailed(ISipSession session, int errorCode,
- String message) {
- }
-
- public void onRegistrationTimeout(ISipSession session) {
- }
-}
diff --git a/voip/java/android/net/sip/package.html b/voip/java/android/net/sip/package.html
deleted file mode 100644
index 3c4cc23..0000000
--- a/voip/java/android/net/sip/package.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<HTML>
-<BODY>
-<p>Provides access to Session Initiation Protocol (SIP) functionality, such as
-making and answering VOIP calls using SIP.</p>
-
-<p>For more information, see the
-<a href="{@docRoot}guide/topics/connectivity/sip.html">Session Initiation Protocol</a>
-developer guide.</p>
-{@more}
-
-<p>To get started, you need to get an instance of the {@link android.net.sip.SipManager} by
-calling {@link android.net.sip.SipManager#newInstance newInstance()}.</p>
-
-<p>With the {@link android.net.sip.SipManager}, you can initiate SIP audio calls with {@link
-android.net.sip.SipManager#makeAudioCall makeAudioCall()} and {@link
-android.net.sip.SipManager#takeAudioCall takeAudioCall()}. Both methods require
-a {@link android.net.sip.SipAudioCall.Listener} that receives callbacks when the state of the
-call changes, such as when the call is ringing, established, or ended.</p>
-
-<p>Both {@link android.net.sip.SipManager#makeAudioCall makeAudioCall()} also requires two
-{@link android.net.sip.SipProfile} objects, representing the local device and the peer
-device. You can create a {@link android.net.sip.SipProfile} using the {@link
-android.net.sip.SipProfile.Builder} subclass.</p>
-
-<p>Once you have a {@link android.net.sip.SipAudioCall}, you can perform SIP audio call actions with
-the instance, such as make a call, answer a call, mute a call, turn on speaker mode, send DTMF
-tones, and more.</p>
-
-<p>If you want to create generic SIP connections (such as for video calls or other), you can
-create a SIP connection from the {@link android.net.sip.SipManager}, using {@link
-android.net.sip.SipManager#open open()}. If you only want to create audio SIP calls, though, you
-should use the {@link android.net.sip.SipAudioCall} class, as described above.</p>
-
-<p class="note"><strong>Note:</strong>
-Not all Android-powered devices support VOIP functionality with SIP. Before performing any SIP
-activity, you should call {@link android.net.sip.SipManager#isVoipSupported isVoipSupported()}
-to verify that the device supports VOIP calling and {@link
-android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports the
-SIP APIs.
-Your application must also request the {@link android.Manifest.permission#INTERNET} and {@link
-android.Manifest.permission#USE_SIP} permissions in order to use the SIP APIs.
-</p>
-
-</BODY>
-</HTML>
\ No newline at end of file
diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java
deleted file mode 100644
index c708be8..0000000
--- a/voip/java/com/android/server/sip/SipHelper.java
+++ /dev/null
@@ -1,537 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.sip;
-
-import gov.nist.javax.sip.SipStackExt;
-import gov.nist.javax.sip.clientauthutils.AccountManager;
-import gov.nist.javax.sip.clientauthutils.AuthenticationHelper;
-import gov.nist.javax.sip.header.extensions.ReferencesHeader;
-import gov.nist.javax.sip.header.extensions.ReferredByHeader;
-import gov.nist.javax.sip.header.extensions.ReplacesHeader;
-
-import android.net.sip.SipProfile;
-import android.telephony.Rlog;
-
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.EventObject;
-import java.util.List;
-import java.util.regex.Pattern;
-
-import javax.sip.ClientTransaction;
-import javax.sip.Dialog;
-import javax.sip.DialogTerminatedEvent;
-import javax.sip.InvalidArgumentException;
-import javax.sip.ListeningPoint;
-import javax.sip.PeerUnavailableException;
-import javax.sip.RequestEvent;
-import javax.sip.ResponseEvent;
-import javax.sip.ServerTransaction;
-import javax.sip.SipException;
-import javax.sip.SipFactory;
-import javax.sip.SipProvider;
-import javax.sip.SipStack;
-import javax.sip.Transaction;
-import javax.sip.TransactionTerminatedEvent;
-import javax.sip.TransactionState;
-import javax.sip.address.Address;
-import javax.sip.address.AddressFactory;
-import javax.sip.address.SipURI;
-import javax.sip.header.CSeqHeader;
-import javax.sip.header.CallIdHeader;
-import javax.sip.header.ContactHeader;
-import javax.sip.header.FromHeader;
-import javax.sip.header.Header;
-import javax.sip.header.HeaderFactory;
-import javax.sip.header.MaxForwardsHeader;
-import javax.sip.header.ToHeader;
-import javax.sip.header.ViaHeader;
-import javax.sip.message.Message;
-import javax.sip.message.MessageFactory;
-import javax.sip.message.Request;
-import javax.sip.message.Response;
-
-/**
- * Helper class for holding SIP stack related classes and for various low-level
- * SIP tasks like sending messages.
- */
-class SipHelper {
- private static final String TAG = SipHelper.class.getSimpleName();
- private static final boolean DBG = false;
- private static final boolean DBG_PING = false;
-
- private SipStack mSipStack;
- private SipProvider mSipProvider;
- private AddressFactory mAddressFactory;
- private HeaderFactory mHeaderFactory;
- private MessageFactory mMessageFactory;
-
- public SipHelper(SipStack sipStack, SipProvider sipProvider)
- throws PeerUnavailableException {
- mSipStack = sipStack;
- mSipProvider = sipProvider;
-
- SipFactory sipFactory = SipFactory.getInstance();
- mAddressFactory = sipFactory.createAddressFactory();
- mHeaderFactory = sipFactory.createHeaderFactory();
- mMessageFactory = sipFactory.createMessageFactory();
- }
-
- private FromHeader createFromHeader(SipProfile profile, String tag)
- throws ParseException {
- return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag);
- }
-
- private ToHeader createToHeader(SipProfile profile) throws ParseException {
- return createToHeader(profile, null);
- }
-
- private ToHeader createToHeader(SipProfile profile, String tag)
- throws ParseException {
- return mHeaderFactory.createToHeader(profile.getSipAddress(), tag);
- }
-
- private CallIdHeader createCallIdHeader() {
- return mSipProvider.getNewCallId();
- }
-
- private CSeqHeader createCSeqHeader(String method)
- throws ParseException, InvalidArgumentException {
- long sequence = (long) (Math.random() * 10000);
- return mHeaderFactory.createCSeqHeader(sequence, method);
- }
-
- private MaxForwardsHeader createMaxForwardsHeader()
- throws InvalidArgumentException {
- return mHeaderFactory.createMaxForwardsHeader(70);
- }
-
- private MaxForwardsHeader createMaxForwardsHeader(int max)
- throws InvalidArgumentException {
- return mHeaderFactory.createMaxForwardsHeader(max);
- }
-
- private ListeningPoint getListeningPoint() throws SipException {
- ListeningPoint lp = mSipProvider.getListeningPoint(ListeningPoint.UDP);
- if (lp == null) lp = mSipProvider.getListeningPoint(ListeningPoint.TCP);
- if (lp == null) {
- ListeningPoint[] lps = mSipProvider.getListeningPoints();
- if ((lps != null) && (lps.length > 0)) lp = lps[0];
- }
- if (lp == null) {
- throw new SipException("no listening point is available");
- }
- return lp;
- }
-
- private List<ViaHeader> createViaHeaders()
- throws ParseException, SipException {
- List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(1);
- ListeningPoint lp = getListeningPoint();
- ViaHeader viaHeader = mHeaderFactory.createViaHeader(lp.getIPAddress(),
- lp.getPort(), lp.getTransport(), null);
- viaHeader.setRPort();
- viaHeaders.add(viaHeader);
- return viaHeaders;
- }
-
- private ContactHeader createContactHeader(SipProfile profile)
- throws ParseException, SipException {
- return createContactHeader(profile, null, 0);
- }
-
- private ContactHeader createContactHeader(SipProfile profile,
- String ip, int port) throws ParseException,
- SipException {
- SipURI contactURI = (ip == null)
- ? createSipUri(profile.getUserName(), profile.getProtocol(),
- getListeningPoint())
- : createSipUri(profile.getUserName(), profile.getProtocol(),
- ip, port);
-
- Address contactAddress = mAddressFactory.createAddress(contactURI);
- contactAddress.setDisplayName(profile.getDisplayName());
-
- return mHeaderFactory.createContactHeader(contactAddress);
- }
-
- private ContactHeader createWildcardContactHeader() {
- ContactHeader contactHeader = mHeaderFactory.createContactHeader();
- contactHeader.setWildCard();
- return contactHeader;
- }
-
- private SipURI createSipUri(String username, String transport,
- ListeningPoint lp) throws ParseException {
- return createSipUri(username, transport, lp.getIPAddress(), lp.getPort());
- }
-
- private SipURI createSipUri(String username, String transport,
- String ip, int port) throws ParseException {
- SipURI uri = mAddressFactory.createSipURI(username, ip);
- try {
- uri.setPort(port);
- uri.setTransportParam(transport);
- } catch (InvalidArgumentException e) {
- throw new RuntimeException(e);
- }
- return uri;
- }
-
- public ClientTransaction sendOptions(SipProfile caller, SipProfile callee,
- String tag) throws SipException {
- try {
- Request request = (caller == callee)
- ? createRequest(Request.OPTIONS, caller, tag)
- : createRequest(Request.OPTIONS, caller, callee, tag);
-
- ClientTransaction clientTransaction =
- mSipProvider.getNewClientTransaction(request);
- clientTransaction.sendRequest();
- return clientTransaction;
- } catch (Exception e) {
- throw new SipException("sendOptions()", e);
- }
- }
-
- public ClientTransaction sendRegister(SipProfile userProfile, String tag,
- int expiry) throws SipException {
- try {
- Request request = createRequest(Request.REGISTER, userProfile, tag);
- if (expiry == 0) {
- // remove all previous registrations by wildcard
- // rfc3261#section-10.2.2
- request.addHeader(createWildcardContactHeader());
- } else {
- request.addHeader(createContactHeader(userProfile));
- }
- request.addHeader(mHeaderFactory.createExpiresHeader(expiry));
-
- ClientTransaction clientTransaction =
- mSipProvider.getNewClientTransaction(request);
- clientTransaction.sendRequest();
- return clientTransaction;
- } catch (ParseException e) {
- throw new SipException("sendRegister()", e);
- }
- }
-
- private Request createRequest(String requestType, SipProfile userProfile,
- String tag) throws ParseException, SipException {
- FromHeader fromHeader = createFromHeader(userProfile, tag);
- ToHeader toHeader = createToHeader(userProfile);
-
- String replaceStr = Pattern.quote(userProfile.getUserName() + "@");
- SipURI requestURI = mAddressFactory.createSipURI(
- userProfile.getUriString().replaceFirst(replaceStr, ""));
-
- List<ViaHeader> viaHeaders = createViaHeaders();
- CallIdHeader callIdHeader = createCallIdHeader();
- CSeqHeader cSeqHeader = createCSeqHeader(requestType);
- MaxForwardsHeader maxForwards = createMaxForwardsHeader();
- Request request = mMessageFactory.createRequest(requestURI,
- requestType, callIdHeader, cSeqHeader, fromHeader,
- toHeader, viaHeaders, maxForwards);
- Header userAgentHeader = mHeaderFactory.createHeader("User-Agent",
- "SIPAUA/0.1.001");
- request.addHeader(userAgentHeader);
- return request;
- }
-
- public ClientTransaction handleChallenge(ResponseEvent responseEvent,
- AccountManager accountManager) throws SipException {
- AuthenticationHelper authenticationHelper =
- ((SipStackExt) mSipStack).getAuthenticationHelper(
- accountManager, mHeaderFactory);
- ClientTransaction tid = responseEvent.getClientTransaction();
- ClientTransaction ct = authenticationHelper.handleChallenge(
- responseEvent.getResponse(), tid, mSipProvider, 5);
- if (DBG) log("send request with challenge response: "
- + ct.getRequest());
- ct.sendRequest();
- return ct;
- }
-
- private Request createRequest(String requestType, SipProfile caller,
- SipProfile callee, String tag) throws ParseException, SipException {
- FromHeader fromHeader = createFromHeader(caller, tag);
- ToHeader toHeader = createToHeader(callee);
- SipURI requestURI = callee.getUri();
- List<ViaHeader> viaHeaders = createViaHeaders();
- CallIdHeader callIdHeader = createCallIdHeader();
- CSeqHeader cSeqHeader = createCSeqHeader(requestType);
- MaxForwardsHeader maxForwards = createMaxForwardsHeader();
-
- Request request = mMessageFactory.createRequest(requestURI,
- requestType, callIdHeader, cSeqHeader, fromHeader,
- toHeader, viaHeaders, maxForwards);
-
- request.addHeader(createContactHeader(caller));
- return request;
- }
-
- public ClientTransaction sendInvite(SipProfile caller, SipProfile callee,
- String sessionDescription, String tag, ReferredByHeader referredBy,
- String replaces) throws SipException {
- try {
- Request request = createRequest(Request.INVITE, caller, callee, tag);
- if (referredBy != null) request.addHeader(referredBy);
- if (replaces != null) {
- request.addHeader(mHeaderFactory.createHeader(
- ReplacesHeader.NAME, replaces));
- }
- request.setContent(sessionDescription,
- mHeaderFactory.createContentTypeHeader(
- "application", "sdp"));
- ClientTransaction clientTransaction =
- mSipProvider.getNewClientTransaction(request);
- if (DBG) log("send INVITE: " + request);
- clientTransaction.sendRequest();
- return clientTransaction;
- } catch (ParseException e) {
- throw new SipException("sendInvite()", e);
- }
- }
-
- public ClientTransaction sendReinvite(Dialog dialog,
- String sessionDescription) throws SipException {
- try {
- Request request = dialog.createRequest(Request.INVITE);
- request.setContent(sessionDescription,
- mHeaderFactory.createContentTypeHeader(
- "application", "sdp"));
-
- // Adding rport argument in the request could fix some SIP servers
- // in resolving the initiator's NAT port mapping for relaying the
- // response message from the other end.
-
- ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
- if (viaHeader != null) viaHeader.setRPort();
-
- ClientTransaction clientTransaction =
- mSipProvider.getNewClientTransaction(request);
- if (DBG) log("send RE-INVITE: " + request);
- dialog.sendRequest(clientTransaction);
- return clientTransaction;
- } catch (ParseException e) {
- throw new SipException("sendReinvite()", e);
- }
- }
-
- public ServerTransaction getServerTransaction(RequestEvent event)
- throws SipException {
- ServerTransaction transaction = event.getServerTransaction();
- if (transaction == null) {
- Request request = event.getRequest();
- return mSipProvider.getNewServerTransaction(request);
- } else {
- return transaction;
- }
- }
-
- /**
- * @param event the INVITE request event
- */
- public ServerTransaction sendRinging(RequestEvent event, String tag)
- throws SipException {
- try {
- Request request = event.getRequest();
- ServerTransaction transaction = getServerTransaction(event);
-
- Response response = mMessageFactory.createResponse(Response.RINGING,
- request);
-
- ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME);
- toHeader.setTag(tag);
- response.addHeader(toHeader);
- if (DBG) log("send RINGING: " + response);
- transaction.sendResponse(response);
- return transaction;
- } catch (ParseException e) {
- throw new SipException("sendRinging()", e);
- }
- }
-
- /**
- * @param event the INVITE request event
- */
- public ServerTransaction sendInviteOk(RequestEvent event,
- SipProfile localProfile, String sessionDescription,
- ServerTransaction inviteTransaction, String externalIp,
- int externalPort) throws SipException {
- try {
- Request request = event.getRequest();
- Response response = mMessageFactory.createResponse(Response.OK,
- request);
- response.addHeader(createContactHeader(localProfile, externalIp,
- externalPort));
- response.setContent(sessionDescription,
- mHeaderFactory.createContentTypeHeader(
- "application", "sdp"));
-
- if (inviteTransaction == null) {
- inviteTransaction = getServerTransaction(event);
- }
-
- if (inviteTransaction.getState() != TransactionState.COMPLETED) {
- if (DBG) log("send OK: " + response);
- inviteTransaction.sendResponse(response);
- }
-
- return inviteTransaction;
- } catch (ParseException e) {
- throw new SipException("sendInviteOk()", e);
- }
- }
-
- public void sendInviteBusyHere(RequestEvent event,
- ServerTransaction inviteTransaction) throws SipException {
- try {
- Request request = event.getRequest();
- Response response = mMessageFactory.createResponse(
- Response.BUSY_HERE, request);
-
- if (inviteTransaction == null) {
- inviteTransaction = getServerTransaction(event);
- }
-
- if (inviteTransaction.getState() != TransactionState.COMPLETED) {
- if (DBG) log("send BUSY HERE: " + response);
- inviteTransaction.sendResponse(response);
- }
- } catch (ParseException e) {
- throw new SipException("sendInviteBusyHere()", e);
- }
- }
-
- /**
- * @param event the INVITE ACK request event
- */
- public void sendInviteAck(ResponseEvent event, Dialog dialog)
- throws SipException {
- Response response = event.getResponse();
- long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME))
- .getSeqNumber();
- Request ack = dialog.createAck(cseq);
- if (DBG) log("send ACK: " + ack);
- dialog.sendAck(ack);
- }
-
- public void sendBye(Dialog dialog) throws SipException {
- Request byeRequest = dialog.createRequest(Request.BYE);
- if (DBG) log("send BYE: " + byeRequest);
- dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest));
- }
-
- public void sendCancel(ClientTransaction inviteTransaction)
- throws SipException {
- Request cancelRequest = inviteTransaction.createCancel();
- if (DBG) log("send CANCEL: " + cancelRequest);
- mSipProvider.getNewClientTransaction(cancelRequest).sendRequest();
- }
-
- public void sendResponse(RequestEvent event, int responseCode)
- throws SipException {
- try {
- Request request = event.getRequest();
- Response response = mMessageFactory.createResponse(
- responseCode, request);
- if (DBG && (!Request.OPTIONS.equals(request.getMethod())
- || DBG_PING)) {
- log("send response: " + response);
- }
- getServerTransaction(event).sendResponse(response);
- } catch (ParseException e) {
- throw new SipException("sendResponse()", e);
- }
- }
-
- public void sendReferNotify(Dialog dialog, String content)
- throws SipException {
- try {
- Request request = dialog.createRequest(Request.NOTIFY);
- request.addHeader(mHeaderFactory.createSubscriptionStateHeader(
- "active;expires=60"));
- // set content here
- request.setContent(content,
- mHeaderFactory.createContentTypeHeader(
- "message", "sipfrag"));
- request.addHeader(mHeaderFactory.createEventHeader(
- ReferencesHeader.REFER));
- if (DBG) log("send NOTIFY: " + request);
- dialog.sendRequest(mSipProvider.getNewClientTransaction(request));
- } catch (ParseException e) {
- throw new SipException("sendReferNotify()", e);
- }
- }
-
- public void sendInviteRequestTerminated(Request inviteRequest,
- ServerTransaction inviteTransaction) throws SipException {
- try {
- Response response = mMessageFactory.createResponse(
- Response.REQUEST_TERMINATED, inviteRequest);
- if (DBG) log("send response: " + response);
- inviteTransaction.sendResponse(response);
- } catch (ParseException e) {
- throw new SipException("sendInviteRequestTerminated()", e);
- }
- }
-
- public static String getCallId(EventObject event) {
- if (event == null) return null;
- if (event instanceof RequestEvent) {
- return getCallId(((RequestEvent) event).getRequest());
- } else if (event instanceof ResponseEvent) {
- return getCallId(((ResponseEvent) event).getResponse());
- } else if (event instanceof DialogTerminatedEvent) {
- Dialog dialog = ((DialogTerminatedEvent) event).getDialog();
- return getCallId(((DialogTerminatedEvent) event).getDialog());
- } else if (event instanceof TransactionTerminatedEvent) {
- TransactionTerminatedEvent e = (TransactionTerminatedEvent) event;
- return getCallId(e.isServerTransaction()
- ? e.getServerTransaction()
- : e.getClientTransaction());
- } else {
- Object source = event.getSource();
- if (source instanceof Transaction) {
- return getCallId(((Transaction) source));
- } else if (source instanceof Dialog) {
- return getCallId((Dialog) source);
- }
- }
- return "";
- }
-
- public static String getCallId(Transaction transaction) {
- return ((transaction != null) ? getCallId(transaction.getRequest())
- : "");
- }
-
- private static String getCallId(Message message) {
- CallIdHeader callIdHeader =
- (CallIdHeader) message.getHeader(CallIdHeader.NAME);
- return callIdHeader.getCallId();
- }
-
- private static String getCallId(Dialog dialog) {
- return dialog.getCallId().getCallId();
- }
-
- private void log(String s) {
- Rlog.d(TAG, s);
- }
-}
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
deleted file mode 100644
index 80fe68c..0000000
--- a/voip/java/com/android/server/sip/SipService.java
+++ /dev/null
@@ -1,1262 +0,0 @@
-/*
- * Copyright (C) 2010, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.sip;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.sip.ISipService;
-import android.net.sip.ISipSession;
-import android.net.sip.ISipSessionListener;
-import android.net.sip.SipErrorCode;
-import android.net.sip.SipManager;
-import android.net.sip.SipProfile;
-import android.net.sip.SipSession;
-import android.net.sip.SipSessionAdapter;
-import android.net.wifi.WifiManager;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.telephony.Rlog;
-
-import java.io.IOException;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import javax.sip.SipException;
-
-/**
- * @hide
- */
-public final class SipService extends ISipService.Stub {
- static final String TAG = "SipService";
- static final boolean DBG = true;
- private static final int EXPIRY_TIME = 3600;
- private static final int SHORT_EXPIRY_TIME = 10;
- private static final int MIN_EXPIRY_TIME = 60;
- private static final int DEFAULT_KEEPALIVE_INTERVAL = 10; // in seconds
- private static final int DEFAULT_MAX_KEEPALIVE_INTERVAL = 120; // in seconds
-
- private Context mContext;
- private String mLocalIp;
- private int mNetworkType = -1;
- private SipWakeupTimer mTimer;
- private WifiManager.WifiLock mWifiLock;
- private boolean mSipOnWifiOnly;
-
- private SipKeepAliveProcessCallback mSipKeepAliveProcessCallback;
-
- private MyExecutor mExecutor = new MyExecutor();
-
- // SipProfile URI --> group
- private Map<String, SipSessionGroupExt> mSipGroups =
- new HashMap<String, SipSessionGroupExt>();
-
- // session ID --> session
- private Map<String, ISipSession> mPendingSessions =
- new HashMap<String, ISipSession>();
-
- private ConnectivityReceiver mConnectivityReceiver;
- private SipWakeLock mMyWakeLock;
- private int mKeepAliveInterval;
- private int mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
-
- /**
- * Starts the SIP service. Do nothing if the SIP API is not supported on the
- * device.
- */
- public static void start(Context context) {
- if (SipManager.isApiSupported(context)) {
- ServiceManager.addService("sip", new SipService(context));
- context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP));
- if (DBG) slog("start:");
- }
- }
-
- private SipService(Context context) {
- if (DBG) log("SipService: started!");
- mContext = context;
- mConnectivityReceiver = new ConnectivityReceiver();
-
- mWifiLock = ((WifiManager)
- context.getSystemService(Context.WIFI_SERVICE))
- .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
- mWifiLock.setReferenceCounted(false);
- mSipOnWifiOnly = SipManager.isSipWifiOnly(context);
-
- mMyWakeLock = new SipWakeLock((PowerManager)
- context.getSystemService(Context.POWER_SERVICE));
-
- mTimer = new SipWakeupTimer(context, mExecutor);
- }
-
- @Override
- public synchronized SipProfile[] getListOfProfiles() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.USE_SIP, null);
- boolean isCallerRadio = isCallerRadio();
- ArrayList<SipProfile> profiles = new ArrayList<SipProfile>();
- for (SipSessionGroupExt group : mSipGroups.values()) {
- if (isCallerRadio || isCallerCreator(group)) {
- profiles.add(group.getLocalProfile());
- }
- }
- return profiles.toArray(new SipProfile[profiles.size()]);
- }
-
- @Override
- public synchronized void open(SipProfile localProfile) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.USE_SIP, null);
- localProfile.setCallingUid(Binder.getCallingUid());
- try {
- createGroup(localProfile);
- } catch (SipException e) {
- loge("openToMakeCalls()", e);
- // TODO: how to send the exception back
- }
- }
-
- @Override
- public synchronized void open3(SipProfile localProfile,
- PendingIntent incomingCallPendingIntent,
- ISipSessionListener listener) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.USE_SIP, null);
- localProfile.setCallingUid(Binder.getCallingUid());
- if (incomingCallPendingIntent == null) {
- if (DBG) log("open3: incomingCallPendingIntent cannot be null; "
- + "the profile is not opened");
- return;
- }
- if (DBG) log("open3: " + localProfile.getUriString() + ": "
- + incomingCallPendingIntent + ": " + listener);
- try {
- SipSessionGroupExt group = createGroup(localProfile,
- incomingCallPendingIntent, listener);
- if (localProfile.getAutoRegistration()) {
- group.openToReceiveCalls();
- updateWakeLocks();
- }
- } catch (SipException e) {
- loge("open3:", e);
- // TODO: how to send the exception back
- }
- }
-
- private boolean isCallerCreator(SipSessionGroupExt group) {
- SipProfile profile = group.getLocalProfile();
- return (profile.getCallingUid() == Binder.getCallingUid());
- }
-
- private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) {
- return (isCallerRadio() || isCallerCreator(group));
- }
-
- private boolean isCallerRadio() {
- return (Binder.getCallingUid() == Process.PHONE_UID);
- }
-
- @Override
- public synchronized void close(String localProfileUri) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.USE_SIP, null);
- SipSessionGroupExt group = mSipGroups.get(localProfileUri);
- if (group == null) return;
- if (!isCallerCreatorOrRadio(group)) {
- if (DBG) log("only creator or radio can close this profile");
- return;
- }
-
- group = mSipGroups.remove(localProfileUri);
- notifyProfileRemoved(group.getLocalProfile());
- group.close();
-
- updateWakeLocks();
- }
-
- @Override
- public synchronized boolean isOpened(String localProfileUri) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.USE_SIP, null);
- SipSessionGroupExt group = mSipGroups.get(localProfileUri);
- if (group == null) return false;
- if (isCallerCreatorOrRadio(group)) {
- return true;
- } else {
- if (DBG) log("only creator or radio can query on the profile");
- return false;
- }
- }
-
- @Override
- public synchronized boolean isRegistered(String localProfileUri) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.USE_SIP, null);
- SipSessionGroupExt group = mSipGroups.get(localProfileUri);
- if (group == null) return false;
- if (isCallerCreatorOrRadio(group)) {
- return group.isRegistered();
- } else {
- if (DBG) log("only creator or radio can query on the profile");
- return false;
- }
- }
-
- @Override
- public synchronized void setRegistrationListener(String localProfileUri,
- ISipSessionListener listener) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.USE_SIP, null);
- SipSessionGroupExt group = mSipGroups.get(localProfileUri);
- if (group == null) return;
- if (isCallerCreator(group)) {
- group.setListener(listener);
- } else {
- if (DBG) log("only creator can set listener on the profile");
- }
- }
-
- @Override
- public synchronized ISipSession createSession(SipProfile localProfile,
- ISipSessionListener listener) {
- if (DBG) log("createSession: profile" + localProfile);
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.USE_SIP, null);
- localProfile.setCallingUid(Binder.getCallingUid());
- if (mNetworkType == -1) {
- if (DBG) log("createSession: mNetworkType==-1 ret=null");
- return null;
- }
- try {
- SipSessionGroupExt group = createGroup(localProfile);
- return group.createSession(listener);
- } catch (SipException e) {
- if (DBG) loge("createSession;", e);
- return null;
- }
- }
-
- @Override
- public synchronized ISipSession getPendingSession(String callId) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.USE_SIP, null);
- if (callId == null) return null;
- return mPendingSessions.get(callId);
- }
-
- private String determineLocalIp() {
- try {
- DatagramSocket s = new DatagramSocket();
- s.connect(InetAddress.getByName("192.168.1.1"), 80);
- return s.getLocalAddress().getHostAddress();
- } catch (IOException e) {
- if (DBG) loge("determineLocalIp()", e);
- // dont do anything; there should be a connectivity change going
- return null;
- }
- }
-
- private SipSessionGroupExt createGroup(SipProfile localProfile)
- throws SipException {
- String key = localProfile.getUriString();
- SipSessionGroupExt group = mSipGroups.get(key);
- if (group == null) {
- group = new SipSessionGroupExt(localProfile, null, null);
- mSipGroups.put(key, group);
- notifyProfileAdded(localProfile);
- } else if (!isCallerCreator(group)) {
- throw new SipException("only creator can access the profile");
- }
- return group;
- }
-
- private SipSessionGroupExt createGroup(SipProfile localProfile,
- PendingIntent incomingCallPendingIntent,
- ISipSessionListener listener) throws SipException {
- String key = localProfile.getUriString();
- SipSessionGroupExt group = mSipGroups.get(key);
- if (group != null) {
- if (!isCallerCreator(group)) {
- throw new SipException("only creator can access the profile");
- }
- group.setIncomingCallPendingIntent(incomingCallPendingIntent);
- group.setListener(listener);
- } else {
- group = new SipSessionGroupExt(localProfile,
- incomingCallPendingIntent, listener);
- mSipGroups.put(key, group);
- notifyProfileAdded(localProfile);
- }
- return group;
- }
-
- private void notifyProfileAdded(SipProfile localProfile) {
- if (DBG) log("notify: profile added: " + localProfile);
- Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE);
- intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
- mContext.sendBroadcast(intent);
- if (mSipGroups.size() == 1) {
- registerReceivers();
- }
- }
-
- private void notifyProfileRemoved(SipProfile localProfile) {
- if (DBG) log("notify: profile removed: " + localProfile);
- Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE);
- intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
- mContext.sendBroadcast(intent);
- if (mSipGroups.size() == 0) {
- unregisterReceivers();
- }
- }
-
- private void stopPortMappingMeasurement() {
- if (mSipKeepAliveProcessCallback != null) {
- mSipKeepAliveProcessCallback.stop();
- mSipKeepAliveProcessCallback = null;
- }
- }
-
- private void startPortMappingLifetimeMeasurement(
- SipProfile localProfile) {
- startPortMappingLifetimeMeasurement(localProfile,
- DEFAULT_MAX_KEEPALIVE_INTERVAL);
- }
-
- private void startPortMappingLifetimeMeasurement(
- SipProfile localProfile, int maxInterval) {
- if ((mSipKeepAliveProcessCallback == null)
- && (mKeepAliveInterval == -1)
- && isBehindNAT(mLocalIp)) {
- if (DBG) log("startPortMappingLifetimeMeasurement: profile="
- + localProfile.getUriString());
-
- int minInterval = mLastGoodKeepAliveInterval;
- if (minInterval >= maxInterval) {
- // If mLastGoodKeepAliveInterval also does not work, reset it
- // to the default min
- minInterval = mLastGoodKeepAliveInterval
- = DEFAULT_KEEPALIVE_INTERVAL;
- log(" reset min interval to " + minInterval);
- }
- mSipKeepAliveProcessCallback = new SipKeepAliveProcessCallback(
- localProfile, minInterval, maxInterval);
- mSipKeepAliveProcessCallback.start();
- }
- }
-
- private void restartPortMappingLifetimeMeasurement(
- SipProfile localProfile, int maxInterval) {
- stopPortMappingMeasurement();
- mKeepAliveInterval = -1;
- startPortMappingLifetimeMeasurement(localProfile, maxInterval);
- }
-
- private synchronized void addPendingSession(ISipSession session) {
- try {
- cleanUpPendingSessions();
- mPendingSessions.put(session.getCallId(), session);
- if (DBG) log("#pending sess=" + mPendingSessions.size());
- } catch (RemoteException e) {
- // should not happen with a local call
- loge("addPendingSession()", e);
- }
- }
-
- private void cleanUpPendingSessions() throws RemoteException {
- Map.Entry<String, ISipSession>[] entries =
- mPendingSessions.entrySet().toArray(
- new Map.Entry[mPendingSessions.size()]);
- for (Map.Entry<String, ISipSession> entry : entries) {
- if (entry.getValue().getState() != SipSession.State.INCOMING_CALL) {
- mPendingSessions.remove(entry.getKey());
- }
- }
- }
-
- private synchronized boolean callingSelf(SipSessionGroupExt ringingGroup,
- SipSessionGroup.SipSessionImpl ringingSession) {
- String callId = ringingSession.getCallId();
- for (SipSessionGroupExt group : mSipGroups.values()) {
- if ((group != ringingGroup) && group.containsSession(callId)) {
- if (DBG) log("call self: "
- + ringingSession.getLocalProfile().getUriString()
- + " -> " + group.getLocalProfile().getUriString());
- return true;
- }
- }
- return false;
- }
-
- private synchronized void onKeepAliveIntervalChanged() {
- for (SipSessionGroupExt group : mSipGroups.values()) {
- group.onKeepAliveIntervalChanged();
- }
- }
-
- private int getKeepAliveInterval() {
- return (mKeepAliveInterval < 0)
- ? mLastGoodKeepAliveInterval
- : mKeepAliveInterval;
- }
-
- private boolean isBehindNAT(String address) {
- try {
- // TODO: How is isBehindNAT used and why these constanst address:
- // 10.x.x.x | 192.168.x.x | 172.16.x.x .. 172.19.x.x
- byte[] d = InetAddress.getByName(address).getAddress();
- if ((d[0] == 10) ||
- (((0x000000FF & d[0]) == 172) &&
- ((0x000000F0 & d[1]) == 16)) ||
- (((0x000000FF & d[0]) == 192) &&
- ((0x000000FF & d[1]) == 168))) {
- return true;
- }
- } catch (UnknownHostException e) {
- loge("isBehindAT()" + address, e);
- }
- return false;
- }
-
- private class SipSessionGroupExt extends SipSessionAdapter {
- private static final String SSGE_TAG = "SipSessionGroupExt";
- private static final boolean SSGE_DBG = true;
- private SipSessionGroup mSipGroup;
- private PendingIntent mIncomingCallPendingIntent;
- private boolean mOpenedToReceiveCalls;
-
- private SipAutoReg mAutoRegistration =
- new SipAutoReg();
-
- public SipSessionGroupExt(SipProfile localProfile,
- PendingIntent incomingCallPendingIntent,
- ISipSessionListener listener) throws SipException {
- if (SSGE_DBG) log("SipSessionGroupExt: profile=" + localProfile);
- mSipGroup = new SipSessionGroup(duplicate(localProfile),
- localProfile.getPassword(), mTimer, mMyWakeLock);
- mIncomingCallPendingIntent = incomingCallPendingIntent;
- mAutoRegistration.setListener(listener);
- }
-
- public SipProfile getLocalProfile() {
- return mSipGroup.getLocalProfile();
- }
-
- public boolean containsSession(String callId) {
- return mSipGroup.containsSession(callId);
- }
-
- public void onKeepAliveIntervalChanged() {
- mAutoRegistration.onKeepAliveIntervalChanged();
- }
-
- // TODO: remove this method once SipWakeupTimer can better handle variety
- // of timeout values
- void setWakeupTimer(SipWakeupTimer timer) {
- mSipGroup.setWakeupTimer(timer);
- }
-
- private SipProfile duplicate(SipProfile p) {
- try {
- return new SipProfile.Builder(p).setPassword("*").build();
- } catch (Exception e) {
- loge("duplicate()", e);
- throw new RuntimeException("duplicate profile", e);
- }
- }
-
- public void setListener(ISipSessionListener listener) {
- mAutoRegistration.setListener(listener);
- }
-
- public void setIncomingCallPendingIntent(PendingIntent pIntent) {
- mIncomingCallPendingIntent = pIntent;
- }
-
- public void openToReceiveCalls() throws SipException {
- mOpenedToReceiveCalls = true;
- if (mNetworkType != -1) {
- mSipGroup.openToReceiveCalls(this);
- mAutoRegistration.start(mSipGroup);
- }
- if (SSGE_DBG) log("openToReceiveCalls: " + getUri() + ": "
- + mIncomingCallPendingIntent);
- }
-
- public void onConnectivityChanged(boolean connected)
- throws SipException {
- if (SSGE_DBG) {
- log("onConnectivityChanged: connected=" + connected + " uri="
- + getUri() + ": " + mIncomingCallPendingIntent);
- }
- mSipGroup.onConnectivityChanged();
- if (connected) {
- mSipGroup.reset();
- if (mOpenedToReceiveCalls) openToReceiveCalls();
- } else {
- mSipGroup.close();
- mAutoRegistration.stop();
- }
- }
-
- public void close() {
- mOpenedToReceiveCalls = false;
- mSipGroup.close();
- mAutoRegistration.stop();
- if (SSGE_DBG) log("close: " + getUri() + ": " + mIncomingCallPendingIntent);
- }
-
- public ISipSession createSession(ISipSessionListener listener) {
- if (SSGE_DBG) log("createSession");
- return mSipGroup.createSession(listener);
- }
-
- @Override
- public void onRinging(ISipSession s, SipProfile caller,
- String sessionDescription) {
- SipSessionGroup.SipSessionImpl session =
- (SipSessionGroup.SipSessionImpl) s;
- synchronized (SipService.this) {
- try {
- if (!isRegistered() || callingSelf(this, session)) {
- if (SSGE_DBG) log("onRinging: end notReg or self");
- session.endCall();
- return;
- }
-
- // send out incoming call broadcast
- addPendingSession(session);
- Intent intent = SipManager.createIncomingCallBroadcast(
- session.getCallId(), sessionDescription);
- if (SSGE_DBG) log("onRinging: uri=" + getUri() + ": "
- + caller.getUri() + ": " + session.getCallId()
- + " " + mIncomingCallPendingIntent);
- mIncomingCallPendingIntent.send(mContext,
- SipManager.INCOMING_CALL_RESULT_CODE, intent);
- } catch (PendingIntent.CanceledException e) {
- loge("onRinging: pendingIntent is canceled, drop incoming call", e);
- session.endCall();
- }
- }
- }
-
- @Override
- public void onError(ISipSession session, int errorCode,
- String message) {
- if (SSGE_DBG) log("onError: errorCode=" + errorCode + " desc="
- + SipErrorCode.toString(errorCode) + ": " + message);
- }
-
- public boolean isOpenedToReceiveCalls() {
- return mOpenedToReceiveCalls;
- }
-
- public boolean isRegistered() {
- return mAutoRegistration.isRegistered();
- }
-
- private String getUri() {
- return mSipGroup.getLocalProfileUri();
- }
-
- private void log(String s) {
- Rlog.d(SSGE_TAG, s);
- }
-
- private void loge(String s, Throwable t) {
- Rlog.e(SSGE_TAG, s, t);
- }
-
- }
-
- private class SipKeepAliveProcessCallback implements Runnable,
- SipSessionGroup.KeepAliveProcessCallback {
- private static final String SKAI_TAG = "SipKeepAliveProcessCallback";
- private static final boolean SKAI_DBG = true;
- private static final int MIN_INTERVAL = 5; // in seconds
- private static final int PASS_THRESHOLD = 10;
- private static final int NAT_MEASUREMENT_RETRY_INTERVAL = 120; // in seconds
- private SipProfile mLocalProfile;
- private SipSessionGroupExt mGroup;
- private SipSessionGroup.SipSessionImpl mSession;
- private int mMinInterval;
- private int mMaxInterval;
- private int mInterval;
- private int mPassCount;
-
- public SipKeepAliveProcessCallback(SipProfile localProfile,
- int minInterval, int maxInterval) {
- mMaxInterval = maxInterval;
- mMinInterval = minInterval;
- mLocalProfile = localProfile;
- }
-
- public void start() {
- synchronized (SipService.this) {
- if (mSession != null) {
- return;
- }
-
- mInterval = (mMaxInterval + mMinInterval) / 2;
- mPassCount = 0;
-
- // Don't start measurement if the interval is too small
- if (mInterval < DEFAULT_KEEPALIVE_INTERVAL || checkTermination()) {
- if (SKAI_DBG) log("start: measurement aborted; interval=[" +
- mMinInterval + "," + mMaxInterval + "]");
- return;
- }
-
- try {
- if (SKAI_DBG) log("start: interval=" + mInterval);
-
- mGroup = new SipSessionGroupExt(mLocalProfile, null, null);
- // TODO: remove this line once SipWakeupTimer can better handle
- // variety of timeout values
- mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor));
-
- mSession = (SipSessionGroup.SipSessionImpl)
- mGroup.createSession(null);
- mSession.startKeepAliveProcess(mInterval, this);
- } catch (Throwable t) {
- onError(SipErrorCode.CLIENT_ERROR, t.toString());
- }
- }
- }
-
- public void stop() {
- synchronized (SipService.this) {
- if (mSession != null) {
- mSession.stopKeepAliveProcess();
- mSession = null;
- }
- if (mGroup != null) {
- mGroup.close();
- mGroup = null;
- }
- mTimer.cancel(this);
- if (SKAI_DBG) log("stop");
- }
- }
-
- private void restart() {
- synchronized (SipService.this) {
- // Return immediately if the measurement process is stopped
- if (mSession == null) return;
-
- if (SKAI_DBG) log("restart: interval=" + mInterval);
- try {
- mSession.stopKeepAliveProcess();
- mPassCount = 0;
- mSession.startKeepAliveProcess(mInterval, this);
- } catch (SipException e) {
- loge("restart", e);
- }
- }
- }
-
- private boolean checkTermination() {
- return ((mMaxInterval - mMinInterval) < MIN_INTERVAL);
- }
-
- // SipSessionGroup.KeepAliveProcessCallback
- @Override
- public void onResponse(boolean portChanged) {
- synchronized (SipService.this) {
- if (!portChanged) {
- if (++mPassCount != PASS_THRESHOLD) return;
- // update the interval, since the current interval is good to
- // keep the port mapping.
- if (mKeepAliveInterval > 0) {
- mLastGoodKeepAliveInterval = mKeepAliveInterval;
- }
- mKeepAliveInterval = mMinInterval = mInterval;
- if (SKAI_DBG) {
- log("onResponse: portChanged=" + portChanged + " mKeepAliveInterval="
- + mKeepAliveInterval);
- }
- onKeepAliveIntervalChanged();
- } else {
- // Since the rport is changed, shorten the interval.
- mMaxInterval = mInterval;
- }
- if (checkTermination()) {
- // update mKeepAliveInterval and stop measurement.
- stop();
- // If all the measurements failed, we still set it to
- // mMinInterval; If mMinInterval still doesn't work, a new
- // measurement with min interval=DEFAULT_KEEPALIVE_INTERVAL
- // will be conducted.
- mKeepAliveInterval = mMinInterval;
- if (SKAI_DBG) {
- log("onResponse: checkTermination mKeepAliveInterval="
- + mKeepAliveInterval);
- }
- } else {
- // calculate the new interval and continue.
- mInterval = (mMaxInterval + mMinInterval) / 2;
- if (SKAI_DBG) {
- log("onResponse: mKeepAliveInterval=" + mKeepAliveInterval
- + ", new mInterval=" + mInterval);
- }
- restart();
- }
- }
- }
-
- // SipSessionGroup.KeepAliveProcessCallback
- @Override
- public void onError(int errorCode, String description) {
- if (SKAI_DBG) loge("onError: errorCode=" + errorCode + " desc=" + description);
- restartLater();
- }
-
- // timeout handler
- @Override
- public void run() {
- mTimer.cancel(this);
- restart();
- }
-
- private void restartLater() {
- synchronized (SipService.this) {
- int interval = NAT_MEASUREMENT_RETRY_INTERVAL;
- mTimer.cancel(this);
- mTimer.set(interval * 1000, this);
- }
- }
-
- private void log(String s) {
- Rlog.d(SKAI_TAG, s);
- }
-
- private void loge(String s) {
- Rlog.d(SKAI_TAG, s);
- }
-
- private void loge(String s, Throwable t) {
- Rlog.d(SKAI_TAG, s, t);
- }
- }
-
- private class SipAutoReg extends SipSessionAdapter
- implements Runnable, SipSessionGroup.KeepAliveProcessCallback {
- private String SAR_TAG;
- private static final boolean SAR_DBG = true;
- private static final int MIN_KEEPALIVE_SUCCESS_COUNT = 10;
-
- private SipSessionGroup.SipSessionImpl mSession;
- private SipSessionGroup.SipSessionImpl mKeepAliveSession;
- private SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
- private int mBackoff = 1;
- private boolean mRegistered;
- private long mExpiryTime;
- private int mErrorCode;
- private String mErrorMessage;
- private boolean mRunning = false;
-
- private int mKeepAliveSuccessCount = 0;
-
- public void start(SipSessionGroup group) {
- if (!mRunning) {
- mRunning = true;
- mBackoff = 1;
- mSession = (SipSessionGroup.SipSessionImpl)
- group.createSession(this);
- // return right away if no active network connection.
- if (mSession == null) return;
-
- // start unregistration to clear up old registration at server
- // TODO: when rfc5626 is deployed, use reg-id and sip.instance
- // in registration to avoid adding duplicate entries to server
- mMyWakeLock.acquire(mSession);
- mSession.unregister();
- SAR_TAG = "SipAutoReg:" + mSession.getLocalProfile().getUriString();
- if (SAR_DBG) log("start: group=" + group);
- }
- }
-
- private void startKeepAliveProcess(int interval) {
- if (SAR_DBG) log("startKeepAliveProcess: interval=" + interval);
- if (mKeepAliveSession == null) {
- mKeepAliveSession = mSession.duplicate();
- } else {
- mKeepAliveSession.stopKeepAliveProcess();
- }
- try {
- mKeepAliveSession.startKeepAliveProcess(interval, this);
- } catch (SipException e) {
- loge("startKeepAliveProcess: interval=" + interval, e);
- }
- }
-
- private void stopKeepAliveProcess() {
- if (mKeepAliveSession != null) {
- mKeepAliveSession.stopKeepAliveProcess();
- mKeepAliveSession = null;
- }
- mKeepAliveSuccessCount = 0;
- }
-
- // SipSessionGroup.KeepAliveProcessCallback
- @Override
- public void onResponse(boolean portChanged) {
- synchronized (SipService.this) {
- if (portChanged) {
- int interval = getKeepAliveInterval();
- if (mKeepAliveSuccessCount < MIN_KEEPALIVE_SUCCESS_COUNT) {
- if (SAR_DBG) {
- log("onResponse: keepalive doesn't work with interval "
- + interval + ", past success count="
- + mKeepAliveSuccessCount);
- }
- if (interval > DEFAULT_KEEPALIVE_INTERVAL) {
- restartPortMappingLifetimeMeasurement(
- mSession.getLocalProfile(), interval);
- mKeepAliveSuccessCount = 0;
- }
- } else {
- if (SAR_DBG) {
- log("keep keepalive going with interval "
- + interval + ", past success count="
- + mKeepAliveSuccessCount);
- }
- mKeepAliveSuccessCount /= 2;
- }
- } else {
- // Start keep-alive interval measurement on the first
- // successfully kept-alive SipSessionGroup
- startPortMappingLifetimeMeasurement(
- mSession.getLocalProfile());
- mKeepAliveSuccessCount++;
- }
-
- if (!mRunning || !portChanged) return;
-
- // The keep alive process is stopped when port is changed;
- // Nullify the session so that the process can be restarted
- // again when the re-registration is done
- mKeepAliveSession = null;
-
- // Acquire wake lock for the registration process. The
- // lock will be released when registration is complete.
- mMyWakeLock.acquire(mSession);
- mSession.register(EXPIRY_TIME);
- }
- }
-
- // SipSessionGroup.KeepAliveProcessCallback
- @Override
- public void onError(int errorCode, String description) {
- if (SAR_DBG) {
- loge("onError: errorCode=" + errorCode + " desc=" + description);
- }
- onResponse(true); // re-register immediately
- }
-
- public void stop() {
- if (!mRunning) return;
- mRunning = false;
- mMyWakeLock.release(mSession);
- if (mSession != null) {
- mSession.setListener(null);
- if (mNetworkType != -1 && mRegistered) mSession.unregister();
- }
-
- mTimer.cancel(this);
- stopKeepAliveProcess();
-
- mRegistered = false;
- setListener(mProxy.getListener());
- }
-
- public void onKeepAliveIntervalChanged() {
- if (mKeepAliveSession != null) {
- int newInterval = getKeepAliveInterval();
- if (SAR_DBG) {
- log("onKeepAliveIntervalChanged: interval=" + newInterval);
- }
- mKeepAliveSuccessCount = 0;
- startKeepAliveProcess(newInterval);
- }
- }
-
- public void setListener(ISipSessionListener listener) {
- synchronized (SipService.this) {
- mProxy.setListener(listener);
-
- try {
- int state = (mSession == null)
- ? SipSession.State.READY_TO_CALL
- : mSession.getState();
- if ((state == SipSession.State.REGISTERING)
- || (state == SipSession.State.DEREGISTERING)) {
- mProxy.onRegistering(mSession);
- } else if (mRegistered) {
- int duration = (int)
- (mExpiryTime - SystemClock.elapsedRealtime());
- mProxy.onRegistrationDone(mSession, duration);
- } else if (mErrorCode != SipErrorCode.NO_ERROR) {
- if (mErrorCode == SipErrorCode.TIME_OUT) {
- mProxy.onRegistrationTimeout(mSession);
- } else {
- mProxy.onRegistrationFailed(mSession, mErrorCode,
- mErrorMessage);
- }
- } else if (mNetworkType == -1) {
- mProxy.onRegistrationFailed(mSession,
- SipErrorCode.DATA_CONNECTION_LOST,
- "no data connection");
- } else if (!mRunning) {
- mProxy.onRegistrationFailed(mSession,
- SipErrorCode.CLIENT_ERROR,
- "registration not running");
- } else {
- mProxy.onRegistrationFailed(mSession,
- SipErrorCode.IN_PROGRESS,
- String.valueOf(state));
- }
- } catch (Throwable t) {
- loge("setListener: ", t);
- }
- }
- }
-
- public boolean isRegistered() {
- return mRegistered;
- }
-
- // timeout handler: re-register
- @Override
- public void run() {
- synchronized (SipService.this) {
- if (!mRunning) return;
-
- mErrorCode = SipErrorCode.NO_ERROR;
- mErrorMessage = null;
- if (SAR_DBG) log("run: registering");
- if (mNetworkType != -1) {
- mMyWakeLock.acquire(mSession);
- mSession.register(EXPIRY_TIME);
- }
- }
- }
-
- private void restart(int duration) {
- if (SAR_DBG) log("restart: duration=" + duration + "s later.");
- mTimer.cancel(this);
- mTimer.set(duration * 1000, this);
- }
-
- private int backoffDuration() {
- int duration = SHORT_EXPIRY_TIME * mBackoff;
- if (duration > 3600) {
- duration = 3600;
- } else {
- mBackoff *= 2;
- }
- return duration;
- }
-
- @Override
- public void onRegistering(ISipSession session) {
- if (SAR_DBG) log("onRegistering: " + session);
- synchronized (SipService.this) {
- if (notCurrentSession(session)) return;
-
- mRegistered = false;
- mProxy.onRegistering(session);
- }
- }
-
- private boolean notCurrentSession(ISipSession session) {
- if (session != mSession) {
- ((SipSessionGroup.SipSessionImpl) session).setListener(null);
- mMyWakeLock.release(session);
- return true;
- }
- return !mRunning;
- }
-
- @Override
- public void onRegistrationDone(ISipSession session, int duration) {
- if (SAR_DBG) log("onRegistrationDone: " + session);
- synchronized (SipService.this) {
- if (notCurrentSession(session)) return;
-
- mProxy.onRegistrationDone(session, duration);
-
- if (duration > 0) {
- mExpiryTime = SystemClock.elapsedRealtime()
- + (duration * 1000);
-
- if (!mRegistered) {
- mRegistered = true;
- // allow some overlap to avoid call drop during renew
- duration -= MIN_EXPIRY_TIME;
- if (duration < MIN_EXPIRY_TIME) {
- duration = MIN_EXPIRY_TIME;
- }
- restart(duration);
-
- SipProfile localProfile = mSession.getLocalProfile();
- if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp)
- || localProfile.getSendKeepAlive())) {
- startKeepAliveProcess(getKeepAliveInterval());
- }
- }
- mMyWakeLock.release(session);
- } else {
- mRegistered = false;
- mExpiryTime = -1L;
- if (SAR_DBG) log("Refresh registration immediately");
- run();
- }
- }
- }
-
- @Override
- public void onRegistrationFailed(ISipSession session, int errorCode,
- String message) {
- if (SAR_DBG) log("onRegistrationFailed: " + session + ": "
- + SipErrorCode.toString(errorCode) + ": " + message);
- synchronized (SipService.this) {
- if (notCurrentSession(session)) return;
-
- switch (errorCode) {
- case SipErrorCode.INVALID_CREDENTIALS:
- case SipErrorCode.SERVER_UNREACHABLE:
- if (SAR_DBG) log(" pause auto-registration");
- stop();
- break;
- default:
- restartLater();
- }
-
- mErrorCode = errorCode;
- mErrorMessage = message;
- mProxy.onRegistrationFailed(session, errorCode, message);
- mMyWakeLock.release(session);
- }
- }
-
- @Override
- public void onRegistrationTimeout(ISipSession session) {
- if (SAR_DBG) log("onRegistrationTimeout: " + session);
- synchronized (SipService.this) {
- if (notCurrentSession(session)) return;
-
- mErrorCode = SipErrorCode.TIME_OUT;
- mProxy.onRegistrationTimeout(session);
- restartLater();
- mMyWakeLock.release(session);
- }
- }
-
- private void restartLater() {
- if (SAR_DBG) loge("restartLater");
- mRegistered = false;
- restart(backoffDuration());
- }
-
- private void log(String s) {
- Rlog.d(SAR_TAG, s);
- }
-
- private void loge(String s) {
- Rlog.e(SAR_TAG, s);
- }
-
- private void loge(String s, Throwable e) {
- Rlog.e(SAR_TAG, s, e);
- }
- }
-
- private class ConnectivityReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- Bundle bundle = intent.getExtras();
- if (bundle != null) {
- final NetworkInfo info = (NetworkInfo)
- bundle.get(ConnectivityManager.EXTRA_NETWORK_INFO);
-
- // Run the handler in MyExecutor to be protected by wake lock
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- onConnectivityChanged(info);
- }
- });
- }
- }
- }
-
- private void registerReceivers() {
- mContext.registerReceiver(mConnectivityReceiver,
- new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
- if (DBG) log("registerReceivers:");
- }
-
- private void unregisterReceivers() {
- mContext.unregisterReceiver(mConnectivityReceiver);
- if (DBG) log("unregisterReceivers:");
-
- // Reset variables maintained by ConnectivityReceiver.
- mWifiLock.release();
- mNetworkType = -1;
- }
-
- private void updateWakeLocks() {
- for (SipSessionGroupExt group : mSipGroups.values()) {
- if (group.isOpenedToReceiveCalls()) {
- // Also grab the WifiLock when we are disconnected, so the
- // system will keep trying to reconnect. It will be released
- // when the system eventually connects to something else.
- if (mNetworkType == ConnectivityManager.TYPE_WIFI || mNetworkType == -1) {
- mWifiLock.acquire();
- } else {
- mWifiLock.release();
- }
- return;
- }
- }
- mWifiLock.release();
- mMyWakeLock.reset(); // in case there's a leak
- }
-
- private synchronized void onConnectivityChanged(NetworkInfo info) {
- // We only care about the default network, and getActiveNetworkInfo()
- // is the only way to distinguish them. However, as broadcasts are
- // delivered asynchronously, we might miss DISCONNECTED events from
- // getActiveNetworkInfo(), which is critical to our SIP stack. To
- // solve this, if it is a DISCONNECTED event to our current network,
- // respect it. Otherwise get a new one from getActiveNetworkInfo().
- if (info == null || info.isConnected() || info.getType() != mNetworkType) {
- ConnectivityManager cm = (ConnectivityManager)
- mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- info = cm.getActiveNetworkInfo();
- }
-
- // Some devices limit SIP on Wi-Fi. In this case, if we are not on
- // Wi-Fi, treat it as a DISCONNECTED event.
- int networkType = (info != null && info.isConnected()) ? info.getType() : -1;
- if (mSipOnWifiOnly && networkType != ConnectivityManager.TYPE_WIFI) {
- networkType = -1;
- }
-
- // Ignore the event if the current active network is not changed.
- if (mNetworkType == networkType) {
- // TODO: Maybe we need to send seq/generation number
- return;
- }
- if (DBG) {
- log("onConnectivityChanged: " + mNetworkType +
- " -> " + networkType);
- }
-
- try {
- if (mNetworkType != -1) {
- mLocalIp = null;
- stopPortMappingMeasurement();
- for (SipSessionGroupExt group : mSipGroups.values()) {
- group.onConnectivityChanged(false);
- }
- }
- mNetworkType = networkType;
-
- if (mNetworkType != -1) {
- mLocalIp = determineLocalIp();
- mKeepAliveInterval = -1;
- mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
- for (SipSessionGroupExt group : mSipGroups.values()) {
- group.onConnectivityChanged(true);
- }
- }
- updateWakeLocks();
- } catch (SipException e) {
- loge("onConnectivityChanged()", e);
- }
- }
-
- private static Looper createLooper() {
- HandlerThread thread = new HandlerThread("SipService.Executor");
- thread.start();
- return thread.getLooper();
- }
-
- // Executes immediate tasks in a single thread.
- // Hold/release wake lock for running tasks
- private class MyExecutor extends Handler implements Executor {
- MyExecutor() {
- super(createLooper());
- }
-
- @Override
- public void execute(Runnable task) {
- mMyWakeLock.acquire(task);
- Message.obtain(this, 0/* don't care */, task).sendToTarget();
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.obj instanceof Runnable) {
- executeInternal((Runnable) msg.obj);
- } else {
- if (DBG) log("handleMessage: not Runnable ignore msg=" + msg);
- }
- }
-
- private void executeInternal(Runnable task) {
- try {
- task.run();
- } catch (Throwable t) {
- loge("run task: " + task, t);
- } finally {
- mMyWakeLock.release(task);
- }
- }
- }
-
- private void log(String s) {
- Rlog.d(TAG, s);
- }
-
- private static void slog(String s) {
- Rlog.d(TAG, s);
- }
-
- private void loge(String s, Throwable e) {
- Rlog.e(TAG, s, e);
- }
-}
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
deleted file mode 100644
index e820f35..0000000
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ /dev/null
@@ -1,1863 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.sip;
-
-import gov.nist.javax.sip.clientauthutils.AccountManager;
-import gov.nist.javax.sip.clientauthutils.UserCredentials;
-import gov.nist.javax.sip.header.ProxyAuthenticate;
-import gov.nist.javax.sip.header.ReferTo;
-import gov.nist.javax.sip.header.SIPHeaderNames;
-import gov.nist.javax.sip.header.StatusLine;
-import gov.nist.javax.sip.header.WWWAuthenticate;
-import gov.nist.javax.sip.header.extensions.ReferredByHeader;
-import gov.nist.javax.sip.header.extensions.ReplacesHeader;
-import gov.nist.javax.sip.message.SIPMessage;
-import gov.nist.javax.sip.message.SIPResponse;
-
-import android.net.sip.ISipSession;
-import android.net.sip.ISipSessionListener;
-import android.net.sip.SipErrorCode;
-import android.net.sip.SipProfile;
-import android.net.sip.SipSession;
-import android.net.sip.SipSessionAdapter;
-import android.text.TextUtils;
-import android.telephony.Rlog;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.text.ParseException;
-import java.util.EventObject;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-import javax.sip.ClientTransaction;
-import javax.sip.Dialog;
-import javax.sip.DialogTerminatedEvent;
-import javax.sip.IOExceptionEvent;
-import javax.sip.ObjectInUseException;
-import javax.sip.RequestEvent;
-import javax.sip.ResponseEvent;
-import javax.sip.ServerTransaction;
-import javax.sip.SipException;
-import javax.sip.SipFactory;
-import javax.sip.SipListener;
-import javax.sip.SipProvider;
-import javax.sip.SipStack;
-import javax.sip.TimeoutEvent;
-import javax.sip.Transaction;
-import javax.sip.TransactionTerminatedEvent;
-import javax.sip.address.Address;
-import javax.sip.address.SipURI;
-import javax.sip.header.CSeqHeader;
-import javax.sip.header.ContactHeader;
-import javax.sip.header.ExpiresHeader;
-import javax.sip.header.FromHeader;
-import javax.sip.header.HeaderAddress;
-import javax.sip.header.MinExpiresHeader;
-import javax.sip.header.ReferToHeader;
-import javax.sip.header.ViaHeader;
-import javax.sip.message.Message;
-import javax.sip.message.Request;
-import javax.sip.message.Response;
-
-
-/**
- * Manages {@link ISipSession}'s for a SIP account.
- */
-class SipSessionGroup implements SipListener {
- private static final String TAG = "SipSession";
- private static final boolean DBG = false;
- private static final boolean DBG_PING = false;
- private static final String ANONYMOUS = "anonymous";
- // Limit the size of thread pool to 1 for the order issue when the phone is
- // waken up from sleep and there are many packets to be processed in the SIP
- // stack. Note: The default thread pool size in NIST SIP stack is -1 which is
- // unlimited.
- private static final String THREAD_POOL_SIZE = "1";
- private static final int EXPIRY_TIME = 3600; // in seconds
- private static final int CANCEL_CALL_TIMER = 3; // in seconds
- private static final int END_CALL_TIMER = 3; // in seconds
- private static final int KEEPALIVE_TIMEOUT = 5; // in seconds
- private static final int INCALL_KEEPALIVE_INTERVAL = 10; // in seconds
- private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds
-
- private static final EventObject DEREGISTER = new EventObject("Deregister");
- private static final EventObject END_CALL = new EventObject("End call");
-
- private final SipProfile mLocalProfile;
- private final String mPassword;
-
- private SipStack mSipStack;
- private SipHelper mSipHelper;
-
- // session that processes INVITE requests
- private SipSessionImpl mCallReceiverSession;
- private String mLocalIp;
-
- private SipWakeupTimer mWakeupTimer;
- private SipWakeLock mWakeLock;
-
- // call-id-to-SipSession map
- private Map<String, SipSessionImpl> mSessionMap =
- new HashMap<String, SipSessionImpl>();
-
- // external address observed from any response
- private String mExternalIp;
- private int mExternalPort;
-
- /**
- * @param profile the local profile with password crossed out
- * @param password the password of the profile
- * @throws SipException if cannot assign requested address
- */
- public SipSessionGroup(SipProfile profile, String password,
- SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException {
- mLocalProfile = profile;
- mPassword = password;
- mWakeupTimer = timer;
- mWakeLock = wakeLock;
- reset();
- }
-
- // TODO: remove this method once SipWakeupTimer can better handle variety
- // of timeout values
- void setWakeupTimer(SipWakeupTimer timer) {
- mWakeupTimer = timer;
- }
-
- synchronized void reset() throws SipException {
- Properties properties = new Properties();
-
- String protocol = mLocalProfile.getProtocol();
- int port = mLocalProfile.getPort();
- String server = mLocalProfile.getProxyAddress();
-
- if (!TextUtils.isEmpty(server)) {
- properties.setProperty("javax.sip.OUTBOUND_PROXY",
- server + ':' + port + '/' + protocol);
- } else {
- server = mLocalProfile.getSipDomain();
- }
- if (server.startsWith("[") && server.endsWith("]")) {
- server = server.substring(1, server.length() - 1);
- }
-
- String local = null;
- try {
- for (InetAddress remote : InetAddress.getAllByName(server)) {
- DatagramSocket socket = new DatagramSocket();
- socket.connect(remote, port);
- if (socket.isConnected()) {
- local = socket.getLocalAddress().getHostAddress();
- port = socket.getLocalPort();
- socket.close();
- break;
- }
- socket.close();
- }
- } catch (Exception e) {
- // ignore.
- }
- if (local == null) {
- // We are unable to reach the server. Just bail out.
- return;
- }
-
- close();
- mLocalIp = local;
-
- properties.setProperty("javax.sip.STACK_NAME", getStackName());
- properties.setProperty(
- "gov.nist.javax.sip.THREAD_POOL_SIZE", THREAD_POOL_SIZE);
- mSipStack = SipFactory.getInstance().createSipStack(properties);
- try {
- SipProvider provider = mSipStack.createSipProvider(
- mSipStack.createListeningPoint(local, port, protocol));
- provider.addSipListener(this);
- mSipHelper = new SipHelper(mSipStack, provider);
- } catch (SipException e) {
- throw e;
- } catch (Exception e) {
- throw new SipException("failed to initialize SIP stack", e);
- }
-
- if (DBG) log("reset: start stack for " + mLocalProfile.getUriString());
- mSipStack.start();
- }
-
- synchronized void onConnectivityChanged() {
- SipSessionImpl[] ss = mSessionMap.values().toArray(
- new SipSessionImpl[mSessionMap.size()]);
- // Iterate on the copied array instead of directly on mSessionMap to
- // avoid ConcurrentModificationException being thrown when
- // SipSessionImpl removes itself from mSessionMap in onError() in the
- // following loop.
- for (SipSessionImpl s : ss) {
- s.onError(SipErrorCode.DATA_CONNECTION_LOST,
- "data connection lost");
- }
- }
-
- synchronized void resetExternalAddress() {
- if (DBG) {
- log("resetExternalAddress: " + mSipStack);
- }
- mExternalIp = null;
- mExternalPort = 0;
- }
-
- public SipProfile getLocalProfile() {
- return mLocalProfile;
- }
-
- public String getLocalProfileUri() {
- return mLocalProfile.getUriString();
- }
-
- private String getStackName() {
- return "stack" + System.currentTimeMillis();
- }
-
- public synchronized void close() {
- if (DBG) log("close: " + mLocalProfile.getUriString());
- onConnectivityChanged();
- mSessionMap.clear();
- closeToNotReceiveCalls();
- if (mSipStack != null) {
- mSipStack.stop();
- mSipStack = null;
- mSipHelper = null;
- }
- resetExternalAddress();
- }
-
- public synchronized boolean isClosed() {
- return (mSipStack == null);
- }
-
- // For internal use, require listener not to block in callbacks.
- public synchronized void openToReceiveCalls(ISipSessionListener listener) {
- if (mCallReceiverSession == null) {
- mCallReceiverSession = new SipSessionCallReceiverImpl(listener);
- } else {
- mCallReceiverSession.setListener(listener);
- }
- }
-
- public synchronized void closeToNotReceiveCalls() {
- mCallReceiverSession = null;
- }
-
- public ISipSession createSession(ISipSessionListener listener) {
- return (isClosed() ? null : new SipSessionImpl(listener));
- }
-
- synchronized boolean containsSession(String callId) {
- return mSessionMap.containsKey(callId);
- }
-
- private synchronized SipSessionImpl getSipSession(EventObject event) {
- String key = SipHelper.getCallId(event);
- SipSessionImpl session = mSessionMap.get(key);
- if ((session != null) && isLoggable(session)) {
- if (DBG) log("getSipSession: event=" + key);
- if (DBG) log("getSipSession: active sessions:");
- for (String k : mSessionMap.keySet()) {
- if (DBG) log("getSipSession: ..." + k + ": " + mSessionMap.get(k));
- }
- }
- return ((session != null) ? session : mCallReceiverSession);
- }
-
- private synchronized void addSipSession(SipSessionImpl newSession) {
- removeSipSession(newSession);
- String key = newSession.getCallId();
- mSessionMap.put(key, newSession);
- if (isLoggable(newSession)) {
- if (DBG) log("addSipSession: key='" + key + "'");
- for (String k : mSessionMap.keySet()) {
- if (DBG) log("addSipSession: " + k + ": " + mSessionMap.get(k));
- }
- }
- }
-
- private synchronized void removeSipSession(SipSessionImpl session) {
- if (session == mCallReceiverSession) return;
- String key = session.getCallId();
- SipSessionImpl s = mSessionMap.remove(key);
- // sanity check
- if ((s != null) && (s != session)) {
- if (DBG) log("removeSession: " + session + " is not associated with key '"
- + key + "'");
- mSessionMap.put(key, s);
- for (Map.Entry<String, SipSessionImpl> entry
- : mSessionMap.entrySet()) {
- if (entry.getValue() == s) {
- key = entry.getKey();
- mSessionMap.remove(key);
- }
- }
- }
-
- if ((s != null) && isLoggable(s)) {
- if (DBG) log("removeSession: " + session + " @key '" + key + "'");
- for (String k : mSessionMap.keySet()) {
- if (DBG) log("removeSession: " + k + ": " + mSessionMap.get(k));
- }
- }
- }
-
- @Override
- public void processRequest(final RequestEvent event) {
- if (isRequestEvent(Request.INVITE, event)) {
- if (DBG) log("processRequest: mWakeLock.acquire got INVITE, thread:"
- + Thread.currentThread());
- // Acquire a wake lock and keep it for WAKE_LOCK_HOLDING_TIME;
- // should be large enough to bring up the app.
- mWakeLock.acquire(WAKE_LOCK_HOLDING_TIME);
- }
- process(event);
- }
-
- @Override
- public void processResponse(ResponseEvent event) {
- process(event);
- }
-
- @Override
- public void processIOException(IOExceptionEvent event) {
- process(event);
- }
-
- @Override
- public void processTimeout(TimeoutEvent event) {
- process(event);
- }
-
- @Override
- public void processTransactionTerminated(TransactionTerminatedEvent event) {
- process(event);
- }
-
- @Override
- public void processDialogTerminated(DialogTerminatedEvent event) {
- process(event);
- }
-
- private synchronized void process(EventObject event) {
- SipSessionImpl session = getSipSession(event);
- try {
- boolean isLoggable = isLoggable(session, event);
- boolean processed = (session != null) && session.process(event);
- if (isLoggable && processed) {
- log("process: event new state after: "
- + SipSession.State.toString(session.mState));
- }
- } catch (Throwable e) {
- loge("process: error event=" + event, getRootCause(e));
- session.onError(e);
- }
- }
-
- private String extractContent(Message message) {
- // Currently we do not support secure MIME bodies.
- byte[] bytes = message.getRawContent();
- if (bytes != null) {
- try {
- if (message instanceof SIPMessage) {
- return ((SIPMessage) message).getMessageContent();
- } else {
- return new String(bytes, "UTF-8");
- }
- } catch (UnsupportedEncodingException e) {
- }
- }
- return null;
- }
-
- private void extractExternalAddress(ResponseEvent evt) {
- Response response = evt.getResponse();
- ViaHeader viaHeader = (ViaHeader)(response.getHeader(
- SIPHeaderNames.VIA));
- if (viaHeader == null) return;
- int rport = viaHeader.getRPort();
- String externalIp = viaHeader.getReceived();
- if ((rport > 0) && (externalIp != null)) {
- mExternalIp = externalIp;
- mExternalPort = rport;
- if (DBG) {
- log("extractExternalAddress: external addr " + externalIp + ":" + rport
- + " on " + mSipStack);
- }
- }
- }
-
- private Throwable getRootCause(Throwable exception) {
- Throwable cause = exception.getCause();
- while (cause != null) {
- exception = cause;
- cause = exception.getCause();
- }
- return exception;
- }
-
- private SipSessionImpl createNewSession(RequestEvent event,
- ISipSessionListener listener, ServerTransaction transaction,
- int newState) throws SipException {
- SipSessionImpl newSession = new SipSessionImpl(listener);
- newSession.mServerTransaction = transaction;
- newSession.mState = newState;
- newSession.mDialog = newSession.mServerTransaction.getDialog();
- newSession.mInviteReceived = event;
- newSession.mPeerProfile = createPeerProfile((HeaderAddress)
- event.getRequest().getHeader(FromHeader.NAME));
- newSession.mPeerSessionDescription =
- extractContent(event.getRequest());
- return newSession;
- }
-
- private class SipSessionCallReceiverImpl extends SipSessionImpl {
- private static final String SSCRI_TAG = "SipSessionCallReceiverImpl";
- private static final boolean SSCRI_DBG = true;
-
- public SipSessionCallReceiverImpl(ISipSessionListener listener) {
- super(listener);
- }
-
- private int processInviteWithReplaces(RequestEvent event,
- ReplacesHeader replaces) {
- String callId = replaces.getCallId();
- SipSessionImpl session = mSessionMap.get(callId);
- if (session == null) {
- return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
- }
-
- Dialog dialog = session.mDialog;
- if (dialog == null) return Response.DECLINE;
-
- if (!dialog.getLocalTag().equals(replaces.getToTag()) ||
- !dialog.getRemoteTag().equals(replaces.getFromTag())) {
- // No match is found, returns 481.
- return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
- }
-
- ReferredByHeader referredBy = (ReferredByHeader) event.getRequest()
- .getHeader(ReferredByHeader.NAME);
- if ((referredBy == null) ||
- !dialog.getRemoteParty().equals(referredBy.getAddress())) {
- return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
- }
- return Response.OK;
- }
-
- private void processNewInviteRequest(RequestEvent event)
- throws SipException {
- ReplacesHeader replaces = (ReplacesHeader) event.getRequest()
- .getHeader(ReplacesHeader.NAME);
- SipSessionImpl newSession = null;
- if (replaces != null) {
- int response = processInviteWithReplaces(event, replaces);
- if (SSCRI_DBG) {
- log("processNewInviteRequest: " + replaces
- + " response=" + response);
- }
- if (response == Response.OK) {
- SipSessionImpl replacedSession =
- mSessionMap.get(replaces.getCallId());
- // got INVITE w/ replaces request.
- newSession = createNewSession(event,
- replacedSession.mProxy.getListener(),
- mSipHelper.getServerTransaction(event),
- SipSession.State.INCOMING_CALL);
- newSession.mProxy.onCallTransferring(newSession,
- newSession.mPeerSessionDescription);
- } else {
- mSipHelper.sendResponse(event, response);
- }
- } else {
- // New Incoming call.
- newSession = createNewSession(event, mProxy,
- mSipHelper.sendRinging(event, generateTag()),
- SipSession.State.INCOMING_CALL);
- mProxy.onRinging(newSession, newSession.mPeerProfile,
- newSession.mPeerSessionDescription);
- }
- if (newSession != null) addSipSession(newSession);
- }
-
- @Override
- public boolean process(EventObject evt) throws SipException {
- if (isLoggable(this, evt)) log("process: " + this + ": "
- + SipSession.State.toString(mState) + ": processing "
- + logEvt(evt));
- if (isRequestEvent(Request.INVITE, evt)) {
- processNewInviteRequest((RequestEvent) evt);
- return true;
- } else if (isRequestEvent(Request.OPTIONS, evt)) {
- mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
- return true;
- } else {
- return false;
- }
- }
-
- private void log(String s) {
- Rlog.d(SSCRI_TAG, s);
- }
- }
-
- static interface KeepAliveProcessCallback {
- /** Invoked when the response of keeping alive comes back. */
- void onResponse(boolean portChanged);
- void onError(int errorCode, String description);
- }
-
- class SipSessionImpl extends ISipSession.Stub {
- private static final String SSI_TAG = "SipSessionImpl";
- private static final boolean SSI_DBG = true;
-
- SipProfile mPeerProfile;
- SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
- int mState = SipSession.State.READY_TO_CALL;
- RequestEvent mInviteReceived;
- Dialog mDialog;
- ServerTransaction mServerTransaction;
- ClientTransaction mClientTransaction;
- String mPeerSessionDescription;
- boolean mInCall;
- SessionTimer mSessionTimer;
- int mAuthenticationRetryCount;
-
- private SipKeepAlive mSipKeepAlive;
-
- private SipSessionImpl mSipSessionImpl;
-
- // the following three members are used for handling refer request.
- SipSessionImpl mReferSession;
- ReferredByHeader mReferredBy;
- String mReplaces;
-
- // lightweight timer
- class SessionTimer {
- private boolean mRunning = true;
-
- void start(final int timeout) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- sleep(timeout);
- if (mRunning) timeout();
- }
- }, "SipSessionTimerThread").start();
- }
-
- synchronized void cancel() {
- mRunning = false;
- this.notify();
- }
-
- private void timeout() {
- synchronized (SipSessionGroup.this) {
- onError(SipErrorCode.TIME_OUT, "Session timed out!");
- }
- }
-
- private synchronized void sleep(int timeout) {
- try {
- this.wait(timeout * 1000);
- } catch (InterruptedException e) {
- loge("session timer interrupted!", e);
- }
- }
- }
-
- public SipSessionImpl(ISipSessionListener listener) {
- setListener(listener);
- }
-
- SipSessionImpl duplicate() {
- return new SipSessionImpl(mProxy.getListener());
- }
-
- private void reset() {
- mInCall = false;
- removeSipSession(this);
- mPeerProfile = null;
- mState = SipSession.State.READY_TO_CALL;
- mInviteReceived = null;
- mPeerSessionDescription = null;
- mAuthenticationRetryCount = 0;
- mReferSession = null;
- mReferredBy = null;
- mReplaces = null;
-
- if (mDialog != null) mDialog.delete();
- mDialog = null;
-
- try {
- if (mServerTransaction != null) mServerTransaction.terminate();
- } catch (ObjectInUseException e) {
- // ignored
- }
- mServerTransaction = null;
-
- try {
- if (mClientTransaction != null) mClientTransaction.terminate();
- } catch (ObjectInUseException e) {
- // ignored
- }
- mClientTransaction = null;
-
- cancelSessionTimer();
-
- if (mSipSessionImpl != null) {
- mSipSessionImpl.stopKeepAliveProcess();
- mSipSessionImpl = null;
- }
- }
-
- @Override
- public boolean isInCall() {
- return mInCall;
- }
-
- @Override
- public String getLocalIp() {
- return mLocalIp;
- }
-
- @Override
- public SipProfile getLocalProfile() {
- return mLocalProfile;
- }
-
- @Override
- public SipProfile getPeerProfile() {
- return mPeerProfile;
- }
-
- @Override
- public String getCallId() {
- return SipHelper.getCallId(getTransaction());
- }
-
- private Transaction getTransaction() {
- if (mClientTransaction != null) return mClientTransaction;
- if (mServerTransaction != null) return mServerTransaction;
- return null;
- }
-
- @Override
- public int getState() {
- return mState;
- }
-
- @Override
- public void setListener(ISipSessionListener listener) {
- mProxy.setListener((listener instanceof SipSessionListenerProxy)
- ? ((SipSessionListenerProxy) listener).getListener()
- : listener);
- }
-
- // process the command in a new thread
- private void doCommandAsync(final EventObject command) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- processCommand(command);
- } catch (Throwable e) {
- loge("command error: " + command + ": "
- + mLocalProfile.getUriString(),
- getRootCause(e));
- onError(e);
- }
- }
- }, "SipSessionAsyncCmdThread").start();
- }
-
- @Override
- public void makeCall(SipProfile peerProfile, String sessionDescription,
- int timeout) {
- doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription,
- timeout));
- }
-
- @Override
- public void answerCall(String sessionDescription, int timeout) {
- synchronized (SipSessionGroup.this) {
- if (mPeerProfile == null) return;
- doCommandAsync(new MakeCallCommand(mPeerProfile,
- sessionDescription, timeout));
- }
- }
-
- @Override
- public void endCall() {
- doCommandAsync(END_CALL);
- }
-
- @Override
- public void changeCall(String sessionDescription, int timeout) {
- synchronized (SipSessionGroup.this) {
- if (mPeerProfile == null) return;
- doCommandAsync(new MakeCallCommand(mPeerProfile,
- sessionDescription, timeout));
- }
- }
-
- @Override
- public void register(int duration) {
- doCommandAsync(new RegisterCommand(duration));
- }
-
- @Override
- public void unregister() {
- doCommandAsync(DEREGISTER);
- }
-
- private void processCommand(EventObject command) throws SipException {
- if (isLoggable(command)) log("process cmd: " + command);
- if (!process(command)) {
- onError(SipErrorCode.IN_PROGRESS,
- "cannot initiate a new transaction to execute: "
- + command);
- }
- }
-
- protected String generateTag() {
- // 32-bit randomness
- return String.valueOf((long) (Math.random() * 0x100000000L));
- }
-
- @Override
- public String toString() {
- try {
- String s = super.toString();
- return s.substring(s.indexOf("@")) + ":"
- + SipSession.State.toString(mState);
- } catch (Throwable e) {
- return super.toString();
- }
- }
-
- public boolean process(EventObject evt) throws SipException {
- if (isLoggable(this, evt)) log(" ~~~~~ " + this + ": "
- + SipSession.State.toString(mState) + ": processing "
- + logEvt(evt));
- synchronized (SipSessionGroup.this) {
- if (isClosed()) return false;
-
- if (mSipKeepAlive != null) {
- // event consumed by keepalive process
- if (mSipKeepAlive.process(evt)) return true;
- }
-
- Dialog dialog = null;
- if (evt instanceof RequestEvent) {
- dialog = ((RequestEvent) evt).getDialog();
- } else if (evt instanceof ResponseEvent) {
- dialog = ((ResponseEvent) evt).getDialog();
- extractExternalAddress((ResponseEvent) evt);
- }
- if (dialog != null) mDialog = dialog;
-
- boolean processed;
-
- switch (mState) {
- case SipSession.State.REGISTERING:
- case SipSession.State.DEREGISTERING:
- processed = registeringToReady(evt);
- break;
- case SipSession.State.READY_TO_CALL:
- processed = readyForCall(evt);
- break;
- case SipSession.State.INCOMING_CALL:
- processed = incomingCall(evt);
- break;
- case SipSession.State.INCOMING_CALL_ANSWERING:
- processed = incomingCallToInCall(evt);
- break;
- case SipSession.State.OUTGOING_CALL:
- case SipSession.State.OUTGOING_CALL_RING_BACK:
- processed = outgoingCall(evt);
- break;
- case SipSession.State.OUTGOING_CALL_CANCELING:
- processed = outgoingCallToReady(evt);
- break;
- case SipSession.State.IN_CALL:
- processed = inCall(evt);
- break;
- case SipSession.State.ENDING_CALL:
- processed = endingCall(evt);
- break;
- default:
- processed = false;
- }
- return (processed || processExceptions(evt));
- }
- }
-
- private boolean processExceptions(EventObject evt) throws SipException {
- if (isRequestEvent(Request.BYE, evt)) {
- // terminate the call whenever a BYE is received
- mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
- endCallNormally();
- return true;
- } else if (isRequestEvent(Request.CANCEL, evt)) {
- mSipHelper.sendResponse((RequestEvent) evt,
- Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST);
- return true;
- } else if (evt instanceof TransactionTerminatedEvent) {
- if (isCurrentTransaction((TransactionTerminatedEvent) evt)) {
- if (evt instanceof TimeoutEvent) {
- processTimeout((TimeoutEvent) evt);
- } else {
- processTransactionTerminated(
- (TransactionTerminatedEvent) evt);
- }
- return true;
- }
- } else if (isRequestEvent(Request.OPTIONS, evt)) {
- mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
- return true;
- } else if (evt instanceof DialogTerminatedEvent) {
- processDialogTerminated((DialogTerminatedEvent) evt);
- return true;
- }
- return false;
- }
-
- private void processDialogTerminated(DialogTerminatedEvent event) {
- if (mDialog == event.getDialog()) {
- onError(new SipException("dialog terminated"));
- } else {
- if (SSI_DBG) log("not the current dialog; current=" + mDialog
- + ", terminated=" + event.getDialog());
- }
- }
-
- private boolean isCurrentTransaction(TransactionTerminatedEvent event) {
- Transaction current = event.isServerTransaction()
- ? mServerTransaction
- : mClientTransaction;
- Transaction target = event.isServerTransaction()
- ? event.getServerTransaction()
- : event.getClientTransaction();
-
- if ((current != target) && (mState != SipSession.State.PINGING)) {
- if (SSI_DBG) log("not the current transaction; current="
- + toString(current) + ", target=" + toString(target));
- return false;
- } else if (current != null) {
- if (SSI_DBG) log("transaction terminated: " + toString(current));
- return true;
- } else {
- // no transaction; shouldn't be here; ignored
- return true;
- }
- }
-
- private String toString(Transaction transaction) {
- if (transaction == null) return "null";
- Request request = transaction.getRequest();
- Dialog dialog = transaction.getDialog();
- CSeqHeader cseq = (CSeqHeader) request.getHeader(CSeqHeader.NAME);
- return String.format("req=%s,%s,s=%s,ds=%s,", request.getMethod(),
- cseq.getSeqNumber(), transaction.getState(),
- ((dialog == null) ? "-" : dialog.getState()));
- }
-
- private void processTransactionTerminated(
- TransactionTerminatedEvent event) {
- switch (mState) {
- case SipSession.State.IN_CALL:
- case SipSession.State.READY_TO_CALL:
- if (SSI_DBG) log("Transaction terminated; do nothing");
- break;
- default:
- if (SSI_DBG) log("Transaction terminated early: " + this);
- onError(SipErrorCode.TRANSACTION_TERMINTED,
- "transaction terminated");
- }
- }
-
- private void processTimeout(TimeoutEvent event) {
- if (SSI_DBG) log("processing Timeout...");
- switch (mState) {
- case SipSession.State.REGISTERING:
- case SipSession.State.DEREGISTERING:
- reset();
- mProxy.onRegistrationTimeout(this);
- break;
- case SipSession.State.INCOMING_CALL:
- case SipSession.State.INCOMING_CALL_ANSWERING:
- case SipSession.State.OUTGOING_CALL:
- case SipSession.State.OUTGOING_CALL_CANCELING:
- onError(SipErrorCode.TIME_OUT, event.toString());
- break;
-
- default:
- if (SSI_DBG) log(" do nothing");
- break;
- }
- }
-
- private int getExpiryTime(Response response) {
- int time = -1;
- ContactHeader contact = (ContactHeader) response.getHeader(ContactHeader.NAME);
- if (contact != null) {
- time = contact.getExpires();
- }
- ExpiresHeader expires = (ExpiresHeader) response.getHeader(ExpiresHeader.NAME);
- if (expires != null && (time < 0 || time > expires.getExpires())) {
- time = expires.getExpires();
- }
- if (time <= 0) {
- time = EXPIRY_TIME;
- }
- expires = (ExpiresHeader) response.getHeader(MinExpiresHeader.NAME);
- if (expires != null && time < expires.getExpires()) {
- time = expires.getExpires();
- }
- if (SSI_DBG) {
- log("Expiry time = " + time);
- }
- return time;
- }
-
- private boolean registeringToReady(EventObject evt)
- throws SipException {
- if (expectResponse(Request.REGISTER, evt)) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
-
- int statusCode = response.getStatusCode();
- switch (statusCode) {
- case Response.OK:
- int state = mState;
- onRegistrationDone((state == SipSession.State.REGISTERING)
- ? getExpiryTime(((ResponseEvent) evt).getResponse())
- : -1);
- return true;
- case Response.UNAUTHORIZED:
- case Response.PROXY_AUTHENTICATION_REQUIRED:
- handleAuthentication(event);
- return true;
- default:
- if (statusCode >= 500) {
- onRegistrationFailed(response);
- return true;
- }
- }
- }
- return false;
- }
-
- private boolean handleAuthentication(ResponseEvent event)
- throws SipException {
- Response response = event.getResponse();
- String nonce = getNonceFromResponse(response);
- if (nonce == null) {
- onError(SipErrorCode.SERVER_ERROR,
- "server does not provide challenge");
- return false;
- } else if (mAuthenticationRetryCount < 2) {
- mClientTransaction = mSipHelper.handleChallenge(
- event, getAccountManager());
- mDialog = mClientTransaction.getDialog();
- mAuthenticationRetryCount++;
- if (isLoggable(this, event)) {
- if (SSI_DBG) log(" authentication retry count="
- + mAuthenticationRetryCount);
- }
- return true;
- } else {
- if (crossDomainAuthenticationRequired(response)) {
- onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION,
- getRealmFromResponse(response));
- } else {
- onError(SipErrorCode.INVALID_CREDENTIALS,
- "incorrect username or password");
- }
- return false;
- }
- }
-
- private boolean crossDomainAuthenticationRequired(Response response) {
- String realm = getRealmFromResponse(response);
- if (realm == null) realm = "";
- return !mLocalProfile.getSipDomain().trim().equals(realm.trim());
- }
-
- private AccountManager getAccountManager() {
- return new AccountManager() {
- @Override
- public UserCredentials getCredentials(ClientTransaction
- challengedTransaction, String realm) {
- return new UserCredentials() {
- @Override
- public String getUserName() {
- String username = mLocalProfile.getAuthUserName();
- return (!TextUtils.isEmpty(username) ? username :
- mLocalProfile.getUserName());
- }
-
- @Override
- public String getPassword() {
- return mPassword;
- }
-
- @Override
- public String getSipDomain() {
- return mLocalProfile.getSipDomain();
- }
- };
- }
- };
- }
-
- private String getRealmFromResponse(Response response) {
- WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader(
- SIPHeaderNames.WWW_AUTHENTICATE);
- if (wwwAuth != null) return wwwAuth.getRealm();
- ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader(
- SIPHeaderNames.PROXY_AUTHENTICATE);
- return (proxyAuth == null) ? null : proxyAuth.getRealm();
- }
-
- private String getNonceFromResponse(Response response) {
- WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader(
- SIPHeaderNames.WWW_AUTHENTICATE);
- if (wwwAuth != null) return wwwAuth.getNonce();
- ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader(
- SIPHeaderNames.PROXY_AUTHENTICATE);
- return (proxyAuth == null) ? null : proxyAuth.getNonce();
- }
-
- private String getResponseString(int statusCode) {
- StatusLine statusLine = new StatusLine();
- statusLine.setStatusCode(statusCode);
- statusLine.setReasonPhrase(SIPResponse.getReasonPhrase(statusCode));
- return statusLine.encode();
- }
-
- private boolean readyForCall(EventObject evt) throws SipException {
- // expect MakeCallCommand, RegisterCommand, DEREGISTER
- if (evt instanceof MakeCallCommand) {
- mState = SipSession.State.OUTGOING_CALL;
- MakeCallCommand cmd = (MakeCallCommand) evt;
- mPeerProfile = cmd.getPeerProfile();
- if (mReferSession != null) {
- mSipHelper.sendReferNotify(mReferSession.mDialog,
- getResponseString(Response.TRYING));
- }
- mClientTransaction = mSipHelper.sendInvite(
- mLocalProfile, mPeerProfile, cmd.getSessionDescription(),
- generateTag(), mReferredBy, mReplaces);
- mDialog = mClientTransaction.getDialog();
- addSipSession(this);
- startSessionTimer(cmd.getTimeout());
- mProxy.onCalling(this);
- return true;
- } else if (evt instanceof RegisterCommand) {
- mState = SipSession.State.REGISTERING;
- int duration = ((RegisterCommand) evt).getDuration();
- mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
- generateTag(), duration);
- mDialog = mClientTransaction.getDialog();
- addSipSession(this);
- mProxy.onRegistering(this);
- return true;
- } else if (DEREGISTER == evt) {
- mState = SipSession.State.DEREGISTERING;
- mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
- generateTag(), 0);
- mDialog = mClientTransaction.getDialog();
- addSipSession(this);
- mProxy.onRegistering(this);
- return true;
- }
- return false;
- }
-
- private boolean incomingCall(EventObject evt) throws SipException {
- // expect MakeCallCommand(answering) , END_CALL cmd , Cancel
- if (evt instanceof MakeCallCommand) {
- // answer call
- mState = SipSession.State.INCOMING_CALL_ANSWERING;
- mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
- mLocalProfile,
- ((MakeCallCommand) evt).getSessionDescription(),
- mServerTransaction,
- mExternalIp, mExternalPort);
- startSessionTimer(((MakeCallCommand) evt).getTimeout());
- return true;
- } else if (END_CALL == evt) {
- mSipHelper.sendInviteBusyHere(mInviteReceived,
- mServerTransaction);
- endCallNormally();
- return true;
- } else if (isRequestEvent(Request.CANCEL, evt)) {
- RequestEvent event = (RequestEvent) evt;
- mSipHelper.sendResponse(event, Response.OK);
- mSipHelper.sendInviteRequestTerminated(
- mInviteReceived.getRequest(), mServerTransaction);
- endCallNormally();
- return true;
- }
- return false;
- }
-
- private boolean incomingCallToInCall(EventObject evt) {
- // expect ACK, CANCEL request
- if (isRequestEvent(Request.ACK, evt)) {
- String sdp = extractContent(((RequestEvent) evt).getRequest());
- if (sdp != null) mPeerSessionDescription = sdp;
- if (mPeerSessionDescription == null) {
- onError(SipErrorCode.CLIENT_ERROR, "peer sdp is empty");
- } else {
- establishCall(false);
- }
- return true;
- } else if (isRequestEvent(Request.CANCEL, evt)) {
- // http://tools.ietf.org/html/rfc3261#section-9.2
- // Final response has been sent; do nothing here.
- return true;
- }
- return false;
- }
-
- private boolean outgoingCall(EventObject evt) throws SipException {
- if (expectResponse(Request.INVITE, evt)) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
-
- int statusCode = response.getStatusCode();
- switch (statusCode) {
- case Response.RINGING:
- case Response.CALL_IS_BEING_FORWARDED:
- case Response.QUEUED:
- case Response.SESSION_PROGRESS:
- // feedback any provisional responses (except TRYING) as
- // ring back for better UX
- if (mState == SipSession.State.OUTGOING_CALL) {
- mState = SipSession.State.OUTGOING_CALL_RING_BACK;
- cancelSessionTimer();
- mProxy.onRingingBack(this);
- }
- return true;
- case Response.OK:
- if (mReferSession != null) {
- mSipHelper.sendReferNotify(mReferSession.mDialog,
- getResponseString(Response.OK));
- // since we don't need to remember the session anymore.
- mReferSession = null;
- }
- mSipHelper.sendInviteAck(event, mDialog);
- mPeerSessionDescription = extractContent(response);
- establishCall(true);
- return true;
- case Response.UNAUTHORIZED:
- case Response.PROXY_AUTHENTICATION_REQUIRED:
- if (handleAuthentication(event)) {
- addSipSession(this);
- }
- return true;
- case Response.REQUEST_PENDING:
- // TODO: rfc3261#section-14.1; re-schedule invite
- return true;
- default:
- if (mReferSession != null) {
- mSipHelper.sendReferNotify(mReferSession.mDialog,
- getResponseString(Response.SERVICE_UNAVAILABLE));
- }
- if (statusCode >= 400) {
- // error: an ack is sent automatically by the stack
- onError(response);
- return true;
- } else if (statusCode >= 300) {
- // TODO: handle 3xx (redirect)
- } else {
- return true;
- }
- }
- return false;
- } else if (END_CALL == evt) {
- // RFC says that UA should not send out cancel when no
- // response comes back yet. We are cheating for not checking
- // response.
- mState = SipSession.State.OUTGOING_CALL_CANCELING;
- mSipHelper.sendCancel(mClientTransaction);
- startSessionTimer(CANCEL_CALL_TIMER);
- return true;
- } else if (isRequestEvent(Request.INVITE, evt)) {
- // Call self? Send BUSY HERE so server may redirect the call to
- // voice mailbox.
- RequestEvent event = (RequestEvent) evt;
- mSipHelper.sendInviteBusyHere(event,
- event.getServerTransaction());
- return true;
- }
- return false;
- }
-
- private boolean outgoingCallToReady(EventObject evt)
- throws SipException {
- if (evt instanceof ResponseEvent) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
- int statusCode = response.getStatusCode();
- if (expectResponse(Request.CANCEL, evt)) {
- if (statusCode == Response.OK) {
- // do nothing; wait for REQUEST_TERMINATED
- return true;
- }
- } else if (expectResponse(Request.INVITE, evt)) {
- switch (statusCode) {
- case Response.OK:
- outgoingCall(evt); // abort Cancel
- return true;
- case Response.REQUEST_TERMINATED:
- endCallNormally();
- return true;
- }
- } else {
- return false;
- }
-
- if (statusCode >= 400) {
- onError(response);
- return true;
- }
- } else if (evt instanceof TransactionTerminatedEvent) {
- // rfc3261#section-14.1:
- // if re-invite gets timed out, terminate the dialog; but
- // re-invite is not reliable, just let it go and pretend
- // nothing happened.
- onError(new SipException("timed out"));
- }
- return false;
- }
-
- private boolean processReferRequest(RequestEvent event)
- throws SipException {
- try {
- ReferToHeader referto = (ReferToHeader) event.getRequest()
- .getHeader(ReferTo.NAME);
- Address address = referto.getAddress();
- SipURI uri = (SipURI) address.getURI();
- String replacesHeader = uri.getHeader(ReplacesHeader.NAME);
- String username = uri.getUser();
- if (username == null) {
- mSipHelper.sendResponse(event, Response.BAD_REQUEST);
- return false;
- }
- // send notify accepted
- mSipHelper.sendResponse(event, Response.ACCEPTED);
- SipSessionImpl newSession = createNewSession(event,
- this.mProxy.getListener(),
- mSipHelper.getServerTransaction(event),
- SipSession.State.READY_TO_CALL);
- newSession.mReferSession = this;
- newSession.mReferredBy = (ReferredByHeader) event.getRequest()
- .getHeader(ReferredByHeader.NAME);
- newSession.mReplaces = replacesHeader;
- newSession.mPeerProfile = createPeerProfile(referto);
- newSession.mProxy.onCallTransferring(newSession,
- null);
- return true;
- } catch (IllegalArgumentException e) {
- throw new SipException("createPeerProfile()", e);
- }
- }
-
- private boolean inCall(EventObject evt) throws SipException {
- // expect END_CALL cmd, BYE request, hold call (MakeCallCommand)
- // OK retransmission is handled in SipStack
- if (END_CALL == evt) {
- // rfc3261#section-15.1.1
- mState = SipSession.State.ENDING_CALL;
- mSipHelper.sendBye(mDialog);
- mProxy.onCallEnded(this);
- startSessionTimer(END_CALL_TIMER);
- return true;
- } else if (isRequestEvent(Request.INVITE, evt)) {
- // got Re-INVITE
- mState = SipSession.State.INCOMING_CALL;
- RequestEvent event = mInviteReceived = (RequestEvent) evt;
- mPeerSessionDescription = extractContent(event.getRequest());
- mServerTransaction = null;
- mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
- return true;
- } else if (isRequestEvent(Request.BYE, evt)) {
- mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
- endCallNormally();
- return true;
- } else if (isRequestEvent(Request.REFER, evt)) {
- return processReferRequest((RequestEvent) evt);
- } else if (evt instanceof MakeCallCommand) {
- // to change call
- mState = SipSession.State.OUTGOING_CALL;
- mClientTransaction = mSipHelper.sendReinvite(mDialog,
- ((MakeCallCommand) evt).getSessionDescription());
- startSessionTimer(((MakeCallCommand) evt).getTimeout());
- return true;
- } else if (evt instanceof ResponseEvent) {
- if (expectResponse(Request.NOTIFY, evt)) return true;
- }
- return false;
- }
-
- private boolean endingCall(EventObject evt) throws SipException {
- if (expectResponse(Request.BYE, evt)) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
-
- int statusCode = response.getStatusCode();
- switch (statusCode) {
- case Response.UNAUTHORIZED:
- case Response.PROXY_AUTHENTICATION_REQUIRED:
- if (handleAuthentication(event)) {
- return true;
- } else {
- // can't authenticate; pass through to end session
- }
- }
- cancelSessionTimer();
- reset();
- return true;
- }
- return false;
- }
-
- // timeout in seconds
- private void startSessionTimer(int timeout) {
- if (timeout > 0) {
- mSessionTimer = new SessionTimer();
- mSessionTimer.start(timeout);
- }
- }
-
- private void cancelSessionTimer() {
- if (mSessionTimer != null) {
- mSessionTimer.cancel();
- mSessionTimer = null;
- }
- }
-
- private String createErrorMessage(Response response) {
- return String.format("%s (%d)", response.getReasonPhrase(),
- response.getStatusCode());
- }
-
- private void enableKeepAlive() {
- if (mSipSessionImpl != null) {
- mSipSessionImpl.stopKeepAliveProcess();
- } else {
- mSipSessionImpl = duplicate();
- }
- try {
- mSipSessionImpl.startKeepAliveProcess(
- INCALL_KEEPALIVE_INTERVAL, mPeerProfile, null);
- } catch (SipException e) {
- loge("keepalive cannot be enabled; ignored", e);
- mSipSessionImpl.stopKeepAliveProcess();
- }
- }
-
- private void establishCall(boolean enableKeepAlive) {
- mState = SipSession.State.IN_CALL;
- cancelSessionTimer();
- if (!mInCall && enableKeepAlive) enableKeepAlive();
- mInCall = true;
- mProxy.onCallEstablished(this, mPeerSessionDescription);
- }
-
- private void endCallNormally() {
- reset();
- mProxy.onCallEnded(this);
- }
-
- private void endCallOnError(int errorCode, String message) {
- reset();
- mProxy.onError(this, errorCode, message);
- }
-
- private void endCallOnBusy() {
- reset();
- mProxy.onCallBusy(this);
- }
-
- private void onError(int errorCode, String message) {
- cancelSessionTimer();
- switch (mState) {
- case SipSession.State.REGISTERING:
- case SipSession.State.DEREGISTERING:
- onRegistrationFailed(errorCode, message);
- break;
- default:
- endCallOnError(errorCode, message);
- }
- }
-
-
- private void onError(Throwable exception) {
- exception = getRootCause(exception);
- onError(getErrorCode(exception), exception.toString());
- }
-
- private void onError(Response response) {
- int statusCode = response.getStatusCode();
- if (!mInCall && (statusCode == Response.BUSY_HERE)) {
- endCallOnBusy();
- } else {
- onError(getErrorCode(statusCode), createErrorMessage(response));
- }
- }
-
- private int getErrorCode(int responseStatusCode) {
- switch (responseStatusCode) {
- case Response.TEMPORARILY_UNAVAILABLE:
- case Response.FORBIDDEN:
- case Response.GONE:
- case Response.NOT_FOUND:
- case Response.NOT_ACCEPTABLE:
- case Response.NOT_ACCEPTABLE_HERE:
- return SipErrorCode.PEER_NOT_REACHABLE;
-
- case Response.REQUEST_URI_TOO_LONG:
- case Response.ADDRESS_INCOMPLETE:
- case Response.AMBIGUOUS:
- return SipErrorCode.INVALID_REMOTE_URI;
-
- case Response.REQUEST_TIMEOUT:
- return SipErrorCode.TIME_OUT;
-
- default:
- if (responseStatusCode < 500) {
- return SipErrorCode.CLIENT_ERROR;
- } else {
- return SipErrorCode.SERVER_ERROR;
- }
- }
- }
-
- private int getErrorCode(Throwable exception) {
- String message = exception.getMessage();
- if (exception instanceof UnknownHostException) {
- return SipErrorCode.SERVER_UNREACHABLE;
- } else if (exception instanceof IOException) {
- return SipErrorCode.SOCKET_ERROR;
- } else {
- return SipErrorCode.CLIENT_ERROR;
- }
- }
-
- private void onRegistrationDone(int duration) {
- reset();
- mProxy.onRegistrationDone(this, duration);
- }
-
- private void onRegistrationFailed(int errorCode, String message) {
- reset();
- mProxy.onRegistrationFailed(this, errorCode, message);
- }
-
- private void onRegistrationFailed(Response response) {
- int statusCode = response.getStatusCode();
- onRegistrationFailed(getErrorCode(statusCode),
- createErrorMessage(response));
- }
-
- // Notes: SipSessionListener will be replaced by the keepalive process
- // @param interval in seconds
- public void startKeepAliveProcess(int interval,
- KeepAliveProcessCallback callback) throws SipException {
- synchronized (SipSessionGroup.this) {
- startKeepAliveProcess(interval, mLocalProfile, callback);
- }
- }
-
- // Notes: SipSessionListener will be replaced by the keepalive process
- // @param interval in seconds
- public void startKeepAliveProcess(int interval, SipProfile peerProfile,
- KeepAliveProcessCallback callback) throws SipException {
- synchronized (SipSessionGroup.this) {
- if (mSipKeepAlive != null) {
- throw new SipException("Cannot create more than one "
- + "keepalive process in a SipSession");
- }
- mPeerProfile = peerProfile;
- mSipKeepAlive = new SipKeepAlive();
- mProxy.setListener(mSipKeepAlive);
- mSipKeepAlive.start(interval, callback);
- }
- }
-
- public void stopKeepAliveProcess() {
- synchronized (SipSessionGroup.this) {
- if (mSipKeepAlive != null) {
- mSipKeepAlive.stop();
- mSipKeepAlive = null;
- }
- }
- }
-
- class SipKeepAlive extends SipSessionAdapter implements Runnable {
- private static final String SKA_TAG = "SipKeepAlive";
- private static final boolean SKA_DBG = true;
-
- private boolean mRunning = false;
- private KeepAliveProcessCallback mCallback;
-
- private boolean mPortChanged = false;
- private int mRPort = 0;
- private int mInterval; // just for debugging
-
- // @param interval in seconds
- void start(int interval, KeepAliveProcessCallback callback) {
- if (mRunning) return;
- mRunning = true;
- mInterval = interval;
- mCallback = new KeepAliveProcessCallbackProxy(callback);
- mWakeupTimer.set(interval * 1000, this);
- if (SKA_DBG) {
- log("start keepalive:"
- + mLocalProfile.getUriString());
- }
-
- // No need to run the first time in a separate thread for now
- run();
- }
-
- // return true if the event is consumed
- boolean process(EventObject evt) {
- if (mRunning && (mState == SipSession.State.PINGING)) {
- if (evt instanceof ResponseEvent) {
- if (parseOptionsResult(evt)) {
- if (mPortChanged) {
- resetExternalAddress();
- stop();
- } else {
- cancelSessionTimer();
- removeSipSession(SipSessionImpl.this);
- }
- mCallback.onResponse(mPortChanged);
- return true;
- }
- }
- }
- return false;
- }
-
- // SipSessionAdapter
- // To react to the session timeout event and network error.
- @Override
- public void onError(ISipSession session, int errorCode, String message) {
- stop();
- mCallback.onError(errorCode, message);
- }
-
- // SipWakeupTimer timeout handler
- // To send out keepalive message.
- @Override
- public void run() {
- synchronized (SipSessionGroup.this) {
- if (!mRunning) return;
-
- if (DBG_PING) {
- String peerUri = (mPeerProfile == null)
- ? "null"
- : mPeerProfile.getUriString();
- log("keepalive: " + mLocalProfile.getUriString()
- + " --> " + peerUri + ", interval=" + mInterval);
- }
- try {
- sendKeepAlive();
- } catch (Throwable t) {
- if (SKA_DBG) {
- loge("keepalive error: "
- + mLocalProfile.getUriString(), getRootCause(t));
- }
- // It's possible that the keepalive process is being stopped
- // during session.sendKeepAlive() so need to check mRunning
- // again here.
- if (mRunning) SipSessionImpl.this.onError(t);
- }
- }
- }
-
- void stop() {
- synchronized (SipSessionGroup.this) {
- if (SKA_DBG) {
- log("stop keepalive:" + mLocalProfile.getUriString()
- + ",RPort=" + mRPort);
- }
- mRunning = false;
- mWakeupTimer.cancel(this);
- reset();
- }
- }
-
- private void sendKeepAlive() throws SipException {
- synchronized (SipSessionGroup.this) {
- mState = SipSession.State.PINGING;
- mClientTransaction = mSipHelper.sendOptions(
- mLocalProfile, mPeerProfile, generateTag());
- mDialog = mClientTransaction.getDialog();
- addSipSession(SipSessionImpl.this);
-
- startSessionTimer(KEEPALIVE_TIMEOUT);
- // when timed out, onError() will be called with SipErrorCode.TIME_OUT
- }
- }
-
- private boolean parseOptionsResult(EventObject evt) {
- if (expectResponse(Request.OPTIONS, evt)) {
- ResponseEvent event = (ResponseEvent) evt;
- int rPort = getRPortFromResponse(event.getResponse());
- if (rPort != -1) {
- if (mRPort == 0) mRPort = rPort;
- if (mRPort != rPort) {
- mPortChanged = true;
- if (SKA_DBG) log(String.format(
- "rport is changed: %d <> %d", mRPort, rPort));
- mRPort = rPort;
- } else {
- if (SKA_DBG) log("rport is the same: " + rPort);
- }
- } else {
- if (SKA_DBG) log("peer did not respond rport");
- }
- return true;
- }
- return false;
- }
-
- private int getRPortFromResponse(Response response) {
- ViaHeader viaHeader = (ViaHeader)(response.getHeader(
- SIPHeaderNames.VIA));
- return (viaHeader == null) ? -1 : viaHeader.getRPort();
- }
-
- private void log(String s) {
- Rlog.d(SKA_TAG, s);
- }
- }
-
- private void log(String s) {
- Rlog.d(SSI_TAG, s);
- }
- }
-
- /**
- * @return true if the event is a request event matching the specified
- * method; false otherwise
- */
- private static boolean isRequestEvent(String method, EventObject event) {
- try {
- if (event instanceof RequestEvent) {
- RequestEvent requestEvent = (RequestEvent) event;
- return method.equals(requestEvent.getRequest().getMethod());
- }
- } catch (Throwable e) {
- }
- return false;
- }
-
- private static String getCseqMethod(Message message) {
- return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod();
- }
-
- /**
- * @return true if the event is a response event and the CSeqHeader method
- * match the given arguments; false otherwise
- */
- private static boolean expectResponse(
- String expectedMethod, EventObject evt) {
- if (evt instanceof ResponseEvent) {
- ResponseEvent event = (ResponseEvent) evt;
- Response response = event.getResponse();
- return expectedMethod.equalsIgnoreCase(getCseqMethod(response));
- }
- return false;
- }
-
- private static SipProfile createPeerProfile(HeaderAddress header)
- throws SipException {
- try {
- Address address = header.getAddress();
- SipURI uri = (SipURI) address.getURI();
- String username = uri.getUser();
- if (username == null) username = ANONYMOUS;
- int port = uri.getPort();
- SipProfile.Builder builder =
- new SipProfile.Builder(username, uri.getHost())
- .setDisplayName(address.getDisplayName());
- if (port > 0) builder.setPort(port);
- return builder.build();
- } catch (IllegalArgumentException e) {
- throw new SipException("createPeerProfile()", e);
- } catch (ParseException e) {
- throw new SipException("createPeerProfile()", e);
- }
- }
-
- private static boolean isLoggable(SipSessionImpl s) {
- if (s != null) {
- switch (s.mState) {
- case SipSession.State.PINGING:
- return DBG_PING;
- }
- }
- return DBG;
- }
-
- private static boolean isLoggable(EventObject evt) {
- return isLoggable(null, evt);
- }
-
- private static boolean isLoggable(SipSessionImpl s, EventObject evt) {
- if (!isLoggable(s)) return false;
- if (evt == null) return false;
-
- if (evt instanceof ResponseEvent) {
- Response response = ((ResponseEvent) evt).getResponse();
- if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) {
- return DBG_PING;
- }
- return DBG;
- } else if (evt instanceof RequestEvent) {
- if (isRequestEvent(Request.OPTIONS, evt)) {
- return DBG_PING;
- }
- return DBG;
- }
- return false;
- }
-
- private static String logEvt(EventObject evt) {
- if (evt instanceof RequestEvent) {
- return ((RequestEvent) evt).getRequest().toString();
- } else if (evt instanceof ResponseEvent) {
- return ((ResponseEvent) evt).getResponse().toString();
- } else {
- return evt.toString();
- }
- }
-
- private class RegisterCommand extends EventObject {
- private int mDuration;
-
- public RegisterCommand(int duration) {
- super(SipSessionGroup.this);
- mDuration = duration;
- }
-
- public int getDuration() {
- return mDuration;
- }
- }
-
- private class MakeCallCommand extends EventObject {
- private String mSessionDescription;
- private int mTimeout; // in seconds
-
- public MakeCallCommand(SipProfile peerProfile,
- String sessionDescription, int timeout) {
- super(peerProfile);
- mSessionDescription = sessionDescription;
- mTimeout = timeout;
- }
-
- public SipProfile getPeerProfile() {
- return (SipProfile) getSource();
- }
-
- public String getSessionDescription() {
- return mSessionDescription;
- }
-
- public int getTimeout() {
- return mTimeout;
- }
- }
-
- /** Class to help safely run KeepAliveProcessCallback in a different thread. */
- static class KeepAliveProcessCallbackProxy implements KeepAliveProcessCallback {
- private static final String KAPCP_TAG = "KeepAliveProcessCallbackProxy";
- private KeepAliveProcessCallback mCallback;
-
- KeepAliveProcessCallbackProxy(KeepAliveProcessCallback callback) {
- mCallback = callback;
- }
-
- private void proxy(Runnable runnable) {
- // One thread for each calling back.
- // Note: Guarantee ordering if the issue becomes important. Currently,
- // the chance of handling two callback events at a time is none.
- new Thread(runnable, "SIP-KeepAliveProcessCallbackThread").start();
- }
-
- @Override
- public void onResponse(final boolean portChanged) {
- if (mCallback == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mCallback.onResponse(portChanged);
- } catch (Throwable t) {
- loge("onResponse", t);
- }
- }
- });
- }
-
- @Override
- public void onError(final int errorCode, final String description) {
- if (mCallback == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mCallback.onError(errorCode, description);
- } catch (Throwable t) {
- loge("onError", t);
- }
- }
- });
- }
-
- private void loge(String s, Throwable t) {
- Rlog.e(KAPCP_TAG, s, t);
- }
- }
-
- private void log(String s) {
- Rlog.d(TAG, s);
- }
-
- private void loge(String s, Throwable t) {
- Rlog.e(TAG, s, t);
- }
-}
diff --git a/voip/java/com/android/server/sip/SipSessionListenerProxy.java b/voip/java/com/android/server/sip/SipSessionListenerProxy.java
deleted file mode 100644
index 7a4ae8d..0000000
--- a/voip/java/com/android/server/sip/SipSessionListenerProxy.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.sip;
-
-import android.net.sip.ISipSession;
-import android.net.sip.ISipSessionListener;
-import android.net.sip.SipProfile;
-import android.os.DeadObjectException;
-import android.telephony.Rlog;
-
-/** Class to help safely run a callback in a different thread. */
-class SipSessionListenerProxy extends ISipSessionListener.Stub {
- private static final String TAG = "SipSessionListnerProxy";
-
- private ISipSessionListener mListener;
-
- public void setListener(ISipSessionListener listener) {
- mListener = listener;
- }
-
- public ISipSessionListener getListener() {
- return mListener;
- }
-
- private void proxy(Runnable runnable) {
- // One thread for each calling back.
- // Note: Guarantee ordering if the issue becomes important. Currently,
- // the chance of handling two callback events at a time is none.
- new Thread(runnable, "SipSessionCallbackThread").start();
- }
-
- @Override
- public void onCalling(final ISipSession session) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onCalling(session);
- } catch (Throwable t) {
- handle(t, "onCalling()");
- }
- }
- });
- }
-
- @Override
- public void onRinging(final ISipSession session, final SipProfile caller,
- final String sessionDescription) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onRinging(session, caller, sessionDescription);
- } catch (Throwable t) {
- handle(t, "onRinging()");
- }
- }
- });
- }
-
- @Override
- public void onRingingBack(final ISipSession session) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onRingingBack(session);
- } catch (Throwable t) {
- handle(t, "onRingingBack()");
- }
- }
- });
- }
-
- @Override
- public void onCallEstablished(final ISipSession session,
- final String sessionDescription) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onCallEstablished(session, sessionDescription);
- } catch (Throwable t) {
- handle(t, "onCallEstablished()");
- }
- }
- });
- }
-
- @Override
- public void onCallEnded(final ISipSession session) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onCallEnded(session);
- } catch (Throwable t) {
- handle(t, "onCallEnded()");
- }
- }
- });
- }
-
- @Override
- public void onCallTransferring(final ISipSession newSession,
- final String sessionDescription) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onCallTransferring(newSession, sessionDescription);
- } catch (Throwable t) {
- handle(t, "onCallTransferring()");
- }
- }
- });
- }
-
- @Override
- public void onCallBusy(final ISipSession session) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onCallBusy(session);
- } catch (Throwable t) {
- handle(t, "onCallBusy()");
- }
- }
- });
- }
-
- @Override
- public void onCallChangeFailed(final ISipSession session,
- final int errorCode, final String message) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onCallChangeFailed(session, errorCode, message);
- } catch (Throwable t) {
- handle(t, "onCallChangeFailed()");
- }
- }
- });
- }
-
- @Override
- public void onError(final ISipSession session, final int errorCode,
- final String message) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onError(session, errorCode, message);
- } catch (Throwable t) {
- handle(t, "onError()");
- }
- }
- });
- }
-
- @Override
- public void onRegistering(final ISipSession session) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onRegistering(session);
- } catch (Throwable t) {
- handle(t, "onRegistering()");
- }
- }
- });
- }
-
- @Override
- public void onRegistrationDone(final ISipSession session,
- final int duration) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onRegistrationDone(session, duration);
- } catch (Throwable t) {
- handle(t, "onRegistrationDone()");
- }
- }
- });
- }
-
- @Override
- public void onRegistrationFailed(final ISipSession session,
- final int errorCode, final String message) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onRegistrationFailed(session, errorCode, message);
- } catch (Throwable t) {
- handle(t, "onRegistrationFailed()");
- }
- }
- });
- }
-
- @Override
- public void onRegistrationTimeout(final ISipSession session) {
- if (mListener == null) return;
- proxy(new Runnable() {
- @Override
- public void run() {
- try {
- mListener.onRegistrationTimeout(session);
- } catch (Throwable t) {
- handle(t, "onRegistrationTimeout()");
- }
- }
- });
- }
-
- private void handle(Throwable t, String message) {
- if (t instanceof DeadObjectException) {
- mListener = null;
- // This creates race but it's harmless. Just don't log the error
- // when it happens.
- } else if (mListener != null) {
- loge(message, t);
- }
- }
-
- private void log(String s) {
- Rlog.d(TAG, s);
- }
-
- private void loge(String s, Throwable t) {
- Rlog.e(TAG, s, t);
- }
-}
diff --git a/voip/java/com/android/server/sip/SipWakeLock.java b/voip/java/com/android/server/sip/SipWakeLock.java
deleted file mode 100644
index b3fbb56..0000000
--- a/voip/java/com/android/server/sip/SipWakeLock.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2010, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.sip;
-
-import android.os.PowerManager;
-import android.telephony.Rlog;
-
-import java.util.HashSet;
-
-class SipWakeLock {
- private static final String TAG = "SipWakeLock";
- private static final boolean DBG = false;
- private PowerManager mPowerManager;
- private PowerManager.WakeLock mWakeLock;
- private PowerManager.WakeLock mTimerWakeLock;
- private HashSet<Object> mHolders = new HashSet<Object>();
-
- SipWakeLock(PowerManager powerManager) {
- mPowerManager = powerManager;
- }
-
- synchronized void reset() {
- if (DBG) log("reset count=" + mHolders.size());
- mHolders.clear();
- release(null);
- }
-
- synchronized void acquire(long timeout) {
- if (mTimerWakeLock == null) {
- mTimerWakeLock = mPowerManager.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock.timer");
- mTimerWakeLock.setReferenceCounted(true);
- }
- mTimerWakeLock.acquire(timeout);
- }
-
- synchronized void acquire(Object holder) {
- mHolders.add(holder);
- if (mWakeLock == null) {
- mWakeLock = mPowerManager.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock");
- }
- if (!mWakeLock.isHeld()) mWakeLock.acquire();
- if (DBG) log("acquire count=" + mHolders.size());
- }
-
- synchronized void release(Object holder) {
- mHolders.remove(holder);
- if ((mWakeLock != null) && mHolders.isEmpty()
- && mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- if (DBG) log("release count=" + mHolders.size());
- }
-
- private void log(String s) {
- Rlog.d(TAG, s);
- }
-}
diff --git a/voip/java/com/android/server/sip/SipWakeupTimer.java b/voip/java/com/android/server/sip/SipWakeupTimer.java
deleted file mode 100644
index 3ba43312..0000000
--- a/voip/java/com/android/server/sip/SipWakeupTimer.java
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (C) 2011, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.sip;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.SystemClock;
-import android.telephony.Rlog;
-
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.TreeSet;
-import java.util.concurrent.Executor;
-
-/**
- * Timer that can schedule events to occur even when the device is in sleep.
- */
-class SipWakeupTimer extends BroadcastReceiver {
- private static final String TAG = "SipWakeupTimer";
- private static final boolean DBG = SipService.DBG && true; // STOPSHIP if true
- private static final String TRIGGER_TIME = "TriggerTime";
-
- private Context mContext;
- private AlarmManager mAlarmManager;
-
- // runnable --> time to execute in SystemClock
- private TreeSet<MyEvent> mEventQueue =
- new TreeSet<MyEvent>(new MyEventComparator());
-
- private PendingIntent mPendingIntent;
-
- private Executor mExecutor;
-
- public SipWakeupTimer(Context context, Executor executor) {
- mContext = context;
- mAlarmManager = (AlarmManager)
- context.getSystemService(Context.ALARM_SERVICE);
-
- IntentFilter filter = new IntentFilter(getAction());
- context.registerReceiver(this, filter);
- mExecutor = executor;
- }
-
- /**
- * Stops the timer. No event can be scheduled after this method is called.
- */
- public synchronized void stop() {
- mContext.unregisterReceiver(this);
- if (mPendingIntent != null) {
- mAlarmManager.cancel(mPendingIntent);
- mPendingIntent = null;
- }
- mEventQueue.clear();
- mEventQueue = null;
- }
-
- private boolean stopped() {
- if (mEventQueue == null) {
- if (DBG) log("Timer stopped");
- return true;
- } else {
- return false;
- }
- }
-
- private void cancelAlarm() {
- mAlarmManager.cancel(mPendingIntent);
- mPendingIntent = null;
- }
-
- private void recalculatePeriods() {
- if (mEventQueue.isEmpty()) return;
-
- MyEvent firstEvent = mEventQueue.first();
- int minPeriod = firstEvent.mMaxPeriod;
- long minTriggerTime = firstEvent.mTriggerTime;
- for (MyEvent e : mEventQueue) {
- e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
- int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod
- - minTriggerTime);
- interval = interval / minPeriod * minPeriod;
- e.mTriggerTime = minTriggerTime + interval;
- }
- TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
- mEventQueue.comparator());
- newQueue.addAll(mEventQueue);
- mEventQueue.clear();
- mEventQueue = newQueue;
- if (DBG) {
- log("queue re-calculated");
- printQueue();
- }
- }
-
- // Determines the period and the trigger time of the new event and insert it
- // to the queue.
- private void insertEvent(MyEvent event) {
- long now = SystemClock.elapsedRealtime();
- if (mEventQueue.isEmpty()) {
- event.mTriggerTime = now + event.mPeriod;
- mEventQueue.add(event);
- return;
- }
- MyEvent firstEvent = mEventQueue.first();
- int minPeriod = firstEvent.mPeriod;
- if (minPeriod <= event.mMaxPeriod) {
- event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod;
- int interval = event.mMaxPeriod;
- interval -= (int) (firstEvent.mTriggerTime - now);
- interval = interval / minPeriod * minPeriod;
- event.mTriggerTime = firstEvent.mTriggerTime + interval;
- mEventQueue.add(event);
- } else {
- long triggerTime = now + event.mPeriod;
- if (firstEvent.mTriggerTime < triggerTime) {
- event.mTriggerTime = firstEvent.mTriggerTime;
- event.mLastTriggerTime -= event.mPeriod;
- } else {
- event.mTriggerTime = triggerTime;
- }
- mEventQueue.add(event);
- recalculatePeriods();
- }
- }
-
- /**
- * Sets a periodic timer.
- *
- * @param period the timer period; in milli-second
- * @param callback is called back when the timer goes off; the same callback
- * can be specified in multiple timer events
- */
- public synchronized void set(int period, Runnable callback) {
- if (stopped()) return;
-
- long now = SystemClock.elapsedRealtime();
- MyEvent event = new MyEvent(period, callback, now);
- insertEvent(event);
-
- if (mEventQueue.first() == event) {
- if (mEventQueue.size() > 1) cancelAlarm();
- scheduleNext();
- }
-
- long triggerTime = event.mTriggerTime;
- if (DBG) {
- log("set: add event " + event + " scheduled on "
- + showTime(triggerTime) + " at " + showTime(now)
- + ", #events=" + mEventQueue.size());
- printQueue();
- }
- }
-
- /**
- * Cancels all the timer events with the specified callback.
- *
- * @param callback the callback
- */
- public synchronized void cancel(Runnable callback) {
- if (stopped() || mEventQueue.isEmpty()) return;
- if (DBG) log("cancel:" + callback);
-
- MyEvent firstEvent = mEventQueue.first();
- for (Iterator<MyEvent> iter = mEventQueue.iterator();
- iter.hasNext();) {
- MyEvent event = iter.next();
- if (event.mCallback == callback) {
- iter.remove();
- if (DBG) log(" cancel found:" + event);
- }
- }
- if (mEventQueue.isEmpty()) {
- cancelAlarm();
- } else if (mEventQueue.first() != firstEvent) {
- cancelAlarm();
- firstEvent = mEventQueue.first();
- firstEvent.mPeriod = firstEvent.mMaxPeriod;
- firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
- + firstEvent.mPeriod;
- recalculatePeriods();
- scheduleNext();
- }
- if (DBG) {
- log("cancel: X");
- printQueue();
- }
- }
-
- private void scheduleNext() {
- if (stopped() || mEventQueue.isEmpty()) return;
-
- if (mPendingIntent != null) {
- throw new RuntimeException("pendingIntent is not null!");
- }
-
- MyEvent event = mEventQueue.first();
- Intent intent = new Intent(getAction());
- intent.putExtra(TRIGGER_TIME, event.mTriggerTime);
- PendingIntent pendingIntent = mPendingIntent =
- PendingIntent.getBroadcast(mContext, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- event.mTriggerTime, pendingIntent);
- }
-
- @Override
- public synchronized void onReceive(Context context, Intent intent) {
- // This callback is already protected by AlarmManager's wake lock.
- String action = intent.getAction();
- if (getAction().equals(action)
- && intent.getExtras().containsKey(TRIGGER_TIME)) {
- mPendingIntent = null;
- long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L);
- execute(triggerTime);
- } else {
- log("onReceive: unrecognized intent: " + intent);
- }
- }
-
- private void printQueue() {
- int count = 0;
- for (MyEvent event : mEventQueue) {
- log(" " + event + ": scheduled at "
- + showTime(event.mTriggerTime) + ": last at "
- + showTime(event.mLastTriggerTime));
- if (++count >= 5) break;
- }
- if (mEventQueue.size() > count) {
- log(" .....");
- } else if (count == 0) {
- log(" <empty>");
- }
- }
-
- private void execute(long triggerTime) {
- if (DBG) log("time's up, triggerTime = "
- + showTime(triggerTime) + ": " + mEventQueue.size());
- if (stopped() || mEventQueue.isEmpty()) return;
-
- for (MyEvent event : mEventQueue) {
- if (event.mTriggerTime != triggerTime) continue;
- if (DBG) log("execute " + event);
-
- event.mLastTriggerTime = triggerTime;
- event.mTriggerTime += event.mPeriod;
-
- // run the callback in the handler thread to prevent deadlock
- mExecutor.execute(event.mCallback);
- }
- if (DBG) {
- log("after timeout execution");
- printQueue();
- }
- scheduleNext();
- }
-
- private String getAction() {
- return toString();
- }
-
- private String showTime(long time) {
- int ms = (int) (time % 1000);
- int s = (int) (time / 1000);
- int m = s / 60;
- s %= 60;
- return String.format("%d.%d.%d", m, s, ms);
- }
-
- private static class MyEvent {
- int mPeriod;
- int mMaxPeriod;
- long mTriggerTime;
- long mLastTriggerTime;
- Runnable mCallback;
-
- MyEvent(int period, Runnable callback, long now) {
- mPeriod = mMaxPeriod = period;
- mCallback = callback;
- mLastTriggerTime = now;
- }
-
- @Override
- public String toString() {
- String s = super.toString();
- s = s.substring(s.indexOf("@"));
- return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":"
- + toString(mCallback);
- }
-
- private String toString(Object o) {
- String s = o.toString();
- int index = s.indexOf("$");
- if (index > 0) s = s.substring(index + 1);
- return s;
- }
- }
-
- // Sort the events by mMaxPeriod so that the first event can be used to
- // align events with larger periods
- private static class MyEventComparator implements Comparator<MyEvent> {
- @Override
- public int compare(MyEvent e1, MyEvent e2) {
- if (e1 == e2) return 0;
- int diff = e1.mMaxPeriod - e2.mMaxPeriod;
- if (diff == 0) diff = -1;
- return diff;
- }
-
- @Override
- public boolean equals(Object that) {
- return (this == that);
- }
- }
-
- private void log(String s) {
- Rlog.d(TAG, s);
- }
-}
diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp
deleted file mode 100644
index e2d820e..0000000
--- a/voip/jni/rtp/AmrCodec.cpp
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyrightm (C) 2010 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.
- */
-
-#include <string.h>
-
-#include "AudioCodec.h"
-
-#include "gsmamr_dec.h"
-#include "gsmamr_enc.h"
-
-namespace {
-
-const int gFrameBits[8] = {95, 103, 118, 134, 148, 159, 204, 244};
-
-//------------------------------------------------------------------------------
-
-// See RFC 4867 for the encoding details.
-
-class AmrCodec : public AudioCodec
-{
-public:
- AmrCodec() {
- if (AMREncodeInit(&mEncoder, &mSidSync, false)) {
- mEncoder = NULL;
- }
- if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) {
- mDecoder = NULL;
- }
- }
-
- ~AmrCodec() {
- if (mEncoder) {
- AMREncodeExit(&mEncoder, &mSidSync);
- }
- if (mDecoder) {
- GSMDecodeFrameExit(&mDecoder);
- }
- }
-
- int set(int sampleRate, const char *fmtp);
- int encode(void *payload, int16_t *samples);
- int decode(int16_t *samples, int count, void *payload, int length);
-
-private:
- void *mEncoder;
- void *mSidSync;
- void *mDecoder;
-
- int mMode;
- int mModeSet;
- bool mOctetAligned;
-};
-
-int AmrCodec::set(int sampleRate, const char *fmtp)
-{
- // These parameters are not supported.
- if (strcasestr(fmtp, "crc=1") || strcasestr(fmtp, "robust-sorting=1") ||
- strcasestr(fmtp, "interleaving=")) {
- return -1;
- }
-
- // Handle mode-set and octet-align.
- const char *modes = strcasestr(fmtp, "mode-set=");
- if (modes) {
- mMode = 0;
- mModeSet = 0;
- for (char c = *modes; c && c != ' '; c = *++modes) {
- if (c >= '0' && c <= '7') {
- int mode = c - '0';
- if (mode > mMode) {
- mMode = mode;
- }
- mModeSet |= 1 << mode;
- }
- }
- } else {
- mMode = 7;
- mModeSet = 0xFF;
- }
- mOctetAligned = (strcasestr(fmtp, "octet-align=1") != NULL);
-
- // TODO: handle mode-change-*.
-
- return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1;
-}
-
-int AmrCodec::encode(void *payload, int16_t *samples)
-{
- unsigned char *bytes = (unsigned char *)payload;
- Frame_Type_3GPP type;
-
- int length = AMREncode(mEncoder, mSidSync, (Mode)mMode,
- samples, bytes + 1, &type, AMR_TX_WMF);
-
- if (type != mMode || length != (8 + gFrameBits[mMode] + 7) >> 3) {
- return -1;
- }
-
- if (mOctetAligned) {
- bytes[0] = 0xF0;
- bytes[1] = (mMode << 3) | 0x04;
- ++length;
- } else {
- // CMR = 15 (4-bit), F = 0 (1-bit), FT = mMode (4-bit), Q = 1 (1-bit).
- bytes[0] = 0xFF;
- bytes[1] = 0xC0 | (mMode << 1) | 1;
-
- // Shift left 6 bits and update the length.
- bytes[length + 1] = 0;
- for (int i = 0; i <= length; ++i) {
- bytes[i] = (bytes[i] << 6) | (bytes[i + 1] >> 2);
- }
- length = (10 + gFrameBits[mMode] + 7) >> 3;
- }
- return length;
-}
-
-int AmrCodec::decode(int16_t *samples, int count, void *payload, int length)
-{
- unsigned char *bytes = (unsigned char *)payload;
- Frame_Type_3GPP type;
- if (length < 2) {
- return -1;
- }
- int request = bytes[0] >> 4;
-
- if (mOctetAligned) {
- if ((bytes[1] & 0xC4) != 0x04) {
- return -1;
- }
- type = (Frame_Type_3GPP)(bytes[1] >> 3);
- if (length != (16 + gFrameBits[type] + 7) >> 3) {
- return -1;
- }
- length -= 2;
- bytes += 2;
- } else {
- if ((bytes[0] & 0x0C) || !(bytes[1] & 0x40)) {
- return -1;
- }
- type = (Frame_Type_3GPP)((bytes[0] << 1 | bytes[1] >> 7) & 0x07);
- if (length != (10 + gFrameBits[type] + 7) >> 3) {
- return -1;
- }
-
- // Shift left 2 bits and update the length.
- --length;
- for (int i = 1; i < length; ++i) {
- bytes[i] = (bytes[i] << 2) | (bytes[i + 1] >> 6);
- }
- bytes[length] <<= 2;
- length = (gFrameBits[type] + 7) >> 3;
- ++bytes;
- }
-
- if (AMRDecode(mDecoder, type, bytes, samples, MIME_IETF) != length) {
- return -1;
- }
-
- // Handle CMR
- if (request < 8 && request != mMode) {
- for (int i = request; i >= 0; --i) {
- if (mModeSet & (1 << i)) {
- mMode = request;
- break;
- }
- }
- }
-
- return 160;
-}
-
-//------------------------------------------------------------------------------
-
-// See RFC 3551 for the encoding details.
-
-class GsmEfrCodec : public AudioCodec
-{
-public:
- GsmEfrCodec() {
- if (AMREncodeInit(&mEncoder, &mSidSync, false)) {
- mEncoder = NULL;
- }
- if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) {
- mDecoder = NULL;
- }
- }
-
- ~GsmEfrCodec() {
- if (mEncoder) {
- AMREncodeExit(&mEncoder, &mSidSync);
- }
- if (mDecoder) {
- GSMDecodeFrameExit(&mDecoder);
- }
- }
-
- int set(int sampleRate, const char *fmtp) {
- return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1;
- }
-
- int encode(void *payload, int16_t *samples);
- int decode(int16_t *samples, int count, void *payload, int length);
-
-private:
- void *mEncoder;
- void *mSidSync;
- void *mDecoder;
-};
-
-int GsmEfrCodec::encode(void *payload, int16_t *samples)
-{
- unsigned char *bytes = (unsigned char *)payload;
- Frame_Type_3GPP type;
-
- int length = AMREncode(mEncoder, mSidSync, MR122,
- samples, bytes, &type, AMR_TX_WMF);
-
- if (type == AMR_122 && length == 32) {
- bytes[0] = 0xC0 | (bytes[1] >> 4);
- for (int i = 1; i < 31; ++i) {
- bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4);
- }
- return 31;
- }
- return -1;
-}
-
-int GsmEfrCodec::decode(int16_t *samples, int count, void *payload, int length)
-{
- unsigned char *bytes = (unsigned char *)payload;
- int n = 0;
- while (n + 160 <= count && length >= 31 && (bytes[0] >> 4) == 0x0C) {
- for (int i = 0; i < 30; ++i) {
- bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4);
- }
- bytes[30] <<= 4;
-
- if (AMRDecode(mDecoder, AMR_122, bytes, &samples[n], MIME_IETF) != 31) {
- break;
- }
- n += 160;
- length -= 31;
- bytes += 31;
- }
- return n;
-}
-
-} // namespace
-
-AudioCodec *newAmrCodec()
-{
- return new AmrCodec;
-}
-
-AudioCodec *newGsmEfrCodec()
-{
- return new GsmEfrCodec;
-}
diff --git a/voip/jni/rtp/Android.mk b/voip/jni/rtp/Android.mk
deleted file mode 100644
index b265cdd..0000000
--- a/voip/jni/rtp/Android.mk
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# Copyright (C) 2010 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := librtp_jni
-
-LOCAL_SRC_FILES := \
- AudioCodec.cpp \
- AudioGroup.cpp \
- EchoSuppressor.cpp \
- RtpStream.cpp \
- util.cpp \
- rtp_jni.cpp
-
-LOCAL_SRC_FILES += \
- AmrCodec.cpp \
- G711Codec.cpp \
- GsmCodec.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libnativehelper \
- libcutils \
- libutils \
- libmedia \
- libstagefright_amrnb_common
-
-LOCAL_STATIC_LIBRARIES := libgsm libstagefright_amrnbdec libstagefright_amrnbenc
-
-LOCAL_C_INCLUDES += \
- $(JNI_H_INCLUDE) \
- external/libgsm/inc \
- frameworks/av/media/libstagefright/codecs/amrnb/common/include \
- frameworks/av/media/libstagefright/codecs/amrnb/common/ \
- frameworks/av/media/libstagefright/codecs/amrnb/enc/include \
- frameworks/av/media/libstagefright/codecs/amrnb/enc/src \
- frameworks/av/media/libstagefright/codecs/amrnb/dec/include \
- frameworks/av/media/libstagefright/codecs/amrnb/dec/src \
- $(call include-path-for, audio-effects)
-
-LOCAL_CFLAGS += -fvisibility=hidden
-
-
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp
deleted file mode 100644
index c75fbc9..0000000
--- a/voip/jni/rtp/AudioCodec.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyrightm (C) 2010 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.
- */
-
-#include <strings.h>
-
-#include "AudioCodec.h"
-
-extern AudioCodec *newAlawCodec();
-extern AudioCodec *newUlawCodec();
-extern AudioCodec *newGsmCodec();
-extern AudioCodec *newAmrCodec();
-extern AudioCodec *newGsmEfrCodec();
-
-struct AudioCodecType {
- const char *name;
- AudioCodec *(*create)();
-} gAudioCodecTypes[] = {
- {"PCMA", newAlawCodec},
- {"PCMU", newUlawCodec},
- {"GSM", newGsmCodec},
- {"AMR", newAmrCodec},
- {"GSM-EFR", newGsmEfrCodec},
- {NULL, NULL},
-};
-
-AudioCodec *newAudioCodec(const char *codecName)
-{
- AudioCodecType *type = gAudioCodecTypes;
- while (type->name != NULL) {
- if (strcasecmp(codecName, type->name) == 0) {
- AudioCodec *codec = type->create();
- codec->name = type->name;
- return codec;
- }
- ++type;
- }
- return NULL;
-}
diff --git a/voip/jni/rtp/AudioCodec.h b/voip/jni/rtp/AudioCodec.h
deleted file mode 100644
index 741730b..0000000
--- a/voip/jni/rtp/AudioCodec.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyrightm (C) 2010 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.
- */
-
-#include <stdint.h>
-
-#ifndef __AUDIO_CODEC_H__
-#define __AUDIO_CODEC_H__
-
-class AudioCodec
-{
-public:
- const char *name;
- // Needed by destruction through base class pointers.
- virtual ~AudioCodec() {}
- // Returns sampleCount or non-positive value if unsupported.
- virtual int set(int sampleRate, const char *fmtp) = 0;
- // Returns the length of payload in bytes.
- virtual int encode(void *payload, int16_t *samples) = 0;
- // Returns the number of decoded samples.
- virtual int decode(int16_t *samples, int count, void *payload, int length) = 0;
-};
-
-AudioCodec *newAudioCodec(const char *codecName);
-
-#endif
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
deleted file mode 100644
index 2f0829e..0000000
--- a/voip/jni/rtp/AudioGroup.cpp
+++ /dev/null
@@ -1,1073 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-#include <stdio.h>
-#include <stdint.h>
-#include <string.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/epoll.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <time.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-
-// #define LOG_NDEBUG 0
-#define LOG_TAG "AudioGroup"
-#include <cutils/atomic.h>
-#include <cutils/properties.h>
-#include <utils/Log.h>
-#include <utils/Errors.h>
-#include <utils/RefBase.h>
-#include <utils/threads.h>
-#include <utils/SystemClock.h>
-#include <media/AudioSystem.h>
-#include <media/AudioRecord.h>
-#include <media/AudioTrack.h>
-#include <media/mediarecorder.h>
-#include <media/AudioEffect.h>
-#include <audio_effects/effect_aec.h>
-#include <system/audio.h>
-
-#include "jni.h"
-#include "JNIHelp.h"
-
-#include "AudioCodec.h"
-#include "EchoSuppressor.h"
-
-extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss);
-
-namespace {
-
-using namespace android;
-
-int gRandom = -1;
-
-// We use a circular array to implement jitter buffer. The simplest way is doing
-// a modulo operation on the index while accessing the array. However modulo can
-// be expensive on some platforms, such as ARM. Thus we round up the size of the
-// array to the nearest power of 2 and then use bitwise-and instead of modulo.
-// Currently we make it 2048ms long and assume packet interval is 50ms or less.
-// The first 100ms is the place where samples get mixed. The rest is the real
-// jitter buffer. For a stream at 8000Hz it takes 32 kilobytes. These numbers
-// are chosen by experiments and each of them can be adjusted as needed.
-
-// Originally a stream does not send packets when it is receive-only or there is
-// nothing to mix. However, this causes some problems with certain firewalls and
-// proxies. A firewall might remove a port mapping when there is no outgoing
-// packet for a preiod of time, and a proxy might wait for incoming packets from
-// both sides before start forwarding. To solve these problems, we send out a
-// silence packet on the stream for every second. It should be good enough to
-// keep the stream alive with relatively low resources.
-
-// Other notes:
-// + We use elapsedRealtime() to get the time. Since we use 32bit variables
-// instead of 64bit ones, comparison must be done by subtraction.
-// + Sampling rate must be multiple of 1000Hz, and packet length must be in
-// milliseconds. No floating points.
-// + If we cannot get enough CPU, we drop samples and simulate packet loss.
-// + Resampling is not done yet, so streams in one group must use the same rate.
-// For the first release only 8000Hz is supported.
-
-#define BUFFER_SIZE 2048
-#define HISTORY_SIZE 100
-#define MEASURE_BASE 100
-#define MEASURE_PERIOD 5000
-#define DTMF_PERIOD 200
-
-class AudioStream
-{
-public:
- AudioStream();
- ~AudioStream();
- bool set(int mode, int socket, sockaddr_storage *remote,
- AudioCodec *codec, int sampleRate, int sampleCount,
- int codecType, int dtmfType);
-
- void sendDtmf(int event);
- bool mix(int32_t *output, int head, int tail, int sampleRate);
- void encode(int tick, AudioStream *chain);
- void decode(int tick);
-
-private:
- enum {
- NORMAL = 0,
- SEND_ONLY = 1,
- RECEIVE_ONLY = 2,
- LAST_MODE = 2,
- };
-
- int mMode;
- int mSocket;
- sockaddr_storage mRemote;
- AudioCodec *mCodec;
- uint32_t mCodecMagic;
- uint32_t mDtmfMagic;
- bool mFixRemote;
-
- int mTick;
- int mSampleRate;
- int mSampleCount;
- int mInterval;
- int mKeepAlive;
-
- int16_t *mBuffer;
- int mBufferMask;
- int mBufferHead;
- int mBufferTail;
- int mLatencyTimer;
- int mLatencyScore;
-
- uint16_t mSequence;
- uint32_t mTimestamp;
- uint32_t mSsrc;
-
- int mDtmfEvent;
- int mDtmfStart;
-
- AudioStream *mNext;
-
- friend class AudioGroup;
-};
-
-AudioStream::AudioStream()
-{
- mSocket = -1;
- mCodec = NULL;
- mBuffer = NULL;
- mNext = NULL;
-}
-
-AudioStream::~AudioStream()
-{
- close(mSocket);
- delete mCodec;
- delete [] mBuffer;
- ALOGD("stream[%d] is dead", mSocket);
-}
-
-bool AudioStream::set(int mode, int socket, sockaddr_storage *remote,
- AudioCodec *codec, int sampleRate, int sampleCount,
- int codecType, int dtmfType)
-{
- if (mode < 0 || mode > LAST_MODE) {
- return false;
- }
- mMode = mode;
-
- mCodecMagic = (0x8000 | codecType) << 16;
- mDtmfMagic = (dtmfType == -1) ? 0 : (0x8000 | dtmfType) << 16;
-
- mTick = elapsedRealtime();
- mSampleRate = sampleRate / 1000;
- mSampleCount = sampleCount;
- mInterval = mSampleCount / mSampleRate;
-
- // Allocate jitter buffer.
- for (mBufferMask = 8; mBufferMask < mSampleRate; mBufferMask <<= 1);
- mBufferMask *= BUFFER_SIZE;
- mBuffer = new int16_t[mBufferMask];
- --mBufferMask;
- mBufferHead = 0;
- mBufferTail = 0;
- mLatencyTimer = 0;
- mLatencyScore = 0;
-
- // Initialize random bits.
- read(gRandom, &mSequence, sizeof(mSequence));
- read(gRandom, &mTimestamp, sizeof(mTimestamp));
- read(gRandom, &mSsrc, sizeof(mSsrc));
-
- mDtmfEvent = -1;
- mDtmfStart = 0;
-
- // Only take over these things when succeeded.
- mSocket = socket;
- if (codec) {
- mRemote = *remote;
- mCodec = codec;
-
- // Here we should never get an private address, but some buggy proxy
- // servers do give us one. To solve this, we replace the address when
- // the first time we successfully decode an incoming packet.
- mFixRemote = false;
- if (remote->ss_family == AF_INET) {
- unsigned char *address =
- (unsigned char *)&((sockaddr_in *)remote)->sin_addr;
- if (address[0] == 10 ||
- (address[0] == 172 && (address[1] >> 4) == 1) ||
- (address[0] == 192 && address[1] == 168)) {
- mFixRemote = true;
- }
- }
- }
-
- ALOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket,
- (codec ? codec->name : "RAW"), mSampleRate, mInterval, mMode);
- return true;
-}
-
-void AudioStream::sendDtmf(int event)
-{
- if (mDtmfMagic != 0) {
- mDtmfEvent = event << 24;
- mDtmfStart = mTimestamp + mSampleCount;
- }
-}
-
-bool AudioStream::mix(int32_t *output, int head, int tail, int sampleRate)
-{
- if (mMode == SEND_ONLY) {
- return false;
- }
-
- if (head - mBufferHead < 0) {
- head = mBufferHead;
- }
- if (tail - mBufferTail > 0) {
- tail = mBufferTail;
- }
- if (tail - head <= 0) {
- return false;
- }
-
- head *= mSampleRate;
- tail *= mSampleRate;
-
- if (sampleRate == mSampleRate) {
- for (int i = head; i - tail < 0; ++i) {
- output[i - head] += mBuffer[i & mBufferMask];
- }
- } else {
- // TODO: implement resampling.
- return false;
- }
- return true;
-}
-
-void AudioStream::encode(int tick, AudioStream *chain)
-{
- if (tick - mTick >= mInterval) {
- // We just missed the train. Pretend that packets in between are lost.
- int skipped = (tick - mTick) / mInterval;
- mTick += skipped * mInterval;
- mSequence += skipped;
- mTimestamp += skipped * mSampleCount;
- ALOGV("stream[%d] skips %d packets", mSocket, skipped);
- }
-
- tick = mTick;
- mTick += mInterval;
- ++mSequence;
- mTimestamp += mSampleCount;
-
- // If there is an ongoing DTMF event, send it now.
- if (mMode != RECEIVE_ONLY && mDtmfEvent != -1) {
- int duration = mTimestamp - mDtmfStart;
- // Make sure duration is reasonable.
- if (duration >= 0 && duration < mSampleRate * DTMF_PERIOD) {
- duration += mSampleCount;
- int32_t buffer[4] = {
- htonl(mDtmfMagic | mSequence),
- htonl(mDtmfStart),
- mSsrc,
- htonl(mDtmfEvent | duration),
- };
- if (duration >= mSampleRate * DTMF_PERIOD) {
- buffer[3] |= htonl(1 << 23);
- mDtmfEvent = -1;
- }
- sendto(mSocket, buffer, sizeof(buffer), MSG_DONTWAIT,
- (sockaddr *)&mRemote, sizeof(mRemote));
- return;
- }
- mDtmfEvent = -1;
- }
-
- int32_t buffer[mSampleCount + 3];
- bool data = false;
- if (mMode != RECEIVE_ONLY) {
- // Mix all other streams.
- memset(buffer, 0, sizeof(buffer));
- while (chain) {
- if (chain != this) {
- data |= chain->mix(buffer, tick - mInterval, tick, mSampleRate);
- }
- chain = chain->mNext;
- }
- }
-
- int16_t samples[mSampleCount];
- if (data) {
- // Saturate into 16 bits.
- for (int i = 0; i < mSampleCount; ++i) {
- int32_t sample = buffer[i];
- if (sample < -32768) {
- sample = -32768;
- }
- if (sample > 32767) {
- sample = 32767;
- }
- samples[i] = sample;
- }
- } else {
- if ((mTick ^ mKeepAlive) >> 10 == 0) {
- return;
- }
- mKeepAlive = mTick;
- memset(samples, 0, sizeof(samples));
-
- if (mMode != RECEIVE_ONLY) {
- ALOGV("stream[%d] no data", mSocket);
- }
- }
-
- if (!mCodec) {
- // Special case for device stream.
- send(mSocket, samples, sizeof(samples), MSG_DONTWAIT);
- return;
- }
-
- // Cook the packet and send it out.
- buffer[0] = htonl(mCodecMagic | mSequence);
- buffer[1] = htonl(mTimestamp);
- buffer[2] = mSsrc;
- int length = mCodec->encode(&buffer[3], samples);
- if (length <= 0) {
- ALOGV("stream[%d] encoder error", mSocket);
- return;
- }
- sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote,
- sizeof(mRemote));
-}
-
-void AudioStream::decode(int tick)
-{
- char c;
- if (mMode == SEND_ONLY) {
- recv(mSocket, &c, 1, MSG_DONTWAIT);
- return;
- }
-
- // Make sure mBufferHead and mBufferTail are reasonable.
- if ((unsigned int)(tick + BUFFER_SIZE - mBufferHead) > BUFFER_SIZE * 2) {
- mBufferHead = tick - HISTORY_SIZE;
- mBufferTail = mBufferHead;
- }
-
- if (tick - mBufferHead > HISTORY_SIZE) {
- // Throw away outdated samples.
- mBufferHead = tick - HISTORY_SIZE;
- if (mBufferTail - mBufferHead < 0) {
- mBufferTail = mBufferHead;
- }
- }
-
- // Adjust the jitter buffer if the latency keeps larger than the threshold
- // in the measurement period.
- int score = mBufferTail - tick - MEASURE_BASE;
- if (mLatencyScore > score || mLatencyScore <= 0) {
- mLatencyScore = score;
- mLatencyTimer = tick;
- } else if (tick - mLatencyTimer >= MEASURE_PERIOD) {
- ALOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore);
- mBufferTail -= mLatencyScore;
- mLatencyScore = -1;
- }
-
- int count = (BUFFER_SIZE - (mBufferTail - mBufferHead)) * mSampleRate;
- if (count < mSampleCount) {
- // Buffer overflow. Drop the packet.
- ALOGV("stream[%d] buffer overflow", mSocket);
- recv(mSocket, &c, 1, MSG_DONTWAIT);
- return;
- }
-
- // Receive the packet and decode it.
- int16_t samples[count];
- if (!mCodec) {
- // Special case for device stream.
- count = recv(mSocket, samples, sizeof(samples),
- MSG_TRUNC | MSG_DONTWAIT) >> 1;
- } else {
- __attribute__((aligned(4))) uint8_t buffer[2048];
- sockaddr_storage remote;
- socklen_t addrlen = sizeof(remote);
-
- int length = recvfrom(mSocket, buffer, sizeof(buffer),
- MSG_TRUNC | MSG_DONTWAIT, (sockaddr *)&remote, &addrlen);
-
- // Do we need to check SSRC, sequence, and timestamp? They are not
- // reliable but at least they can be used to identify duplicates?
- if (length < 12 || length > (int)sizeof(buffer) ||
- (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) {
- ALOGV("stream[%d] malformed packet", mSocket);
- return;
- }
- int offset = 12 + ((buffer[0] & 0x0F) << 2);
- if ((buffer[0] & 0x10) != 0) {
- offset += 4 + (ntohs(*(uint16_t *)&buffer[offset + 2]) << 2);
- }
- if ((buffer[0] & 0x20) != 0) {
- length -= buffer[length - 1];
- }
- length -= offset;
- if (length >= 0) {
- length = mCodec->decode(samples, count, &buffer[offset], length);
- }
- if (length > 0 && mFixRemote) {
- mRemote = remote;
- mFixRemote = false;
- }
- count = length;
- }
- if (count <= 0) {
- ALOGV("stream[%d] decoder error", mSocket);
- return;
- }
-
- if (tick - mBufferTail > 0) {
- // Buffer underrun. Reset the jitter buffer.
- ALOGV("stream[%d] buffer underrun", mSocket);
- if (mBufferTail - mBufferHead <= 0) {
- mBufferHead = tick + mInterval;
- mBufferTail = mBufferHead;
- } else {
- int tail = (tick + mInterval) * mSampleRate;
- for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) {
- mBuffer[i & mBufferMask] = 0;
- }
- mBufferTail = tick + mInterval;
- }
- }
-
- // Append to the jitter buffer.
- int tail = mBufferTail * mSampleRate;
- for (int i = 0; i < count; ++i) {
- mBuffer[tail & mBufferMask] = samples[i];
- ++tail;
- }
- mBufferTail += mInterval;
-}
-
-//------------------------------------------------------------------------------
-
-class AudioGroup
-{
-public:
- AudioGroup();
- ~AudioGroup();
- bool set(int sampleRate, int sampleCount);
-
- bool setMode(int mode);
- bool sendDtmf(int event);
- bool add(AudioStream *stream);
- bool remove(AudioStream *stream);
- bool platformHasAec() { return mPlatformHasAec; }
-
-private:
- enum {
- ON_HOLD = 0,
- MUTED = 1,
- NORMAL = 2,
- ECHO_SUPPRESSION = 3,
- LAST_MODE = 3,
- };
-
- bool checkPlatformAec();
-
- AudioStream *mChain;
- int mEventQueue;
- volatile int mDtmfEvent;
-
- int mMode;
- int mSampleRate;
- int mSampleCount;
- int mDeviceSocket;
- bool mPlatformHasAec;
-
- class NetworkThread : public Thread
- {
- public:
- NetworkThread(AudioGroup *group) : Thread(false), mGroup(group) {}
-
- bool start()
- {
- if (run("Network", ANDROID_PRIORITY_AUDIO) != NO_ERROR) {
- ALOGE("cannot start network thread");
- return false;
- }
- return true;
- }
-
- private:
- AudioGroup *mGroup;
- bool threadLoop();
- };
- sp<NetworkThread> mNetworkThread;
-
- class DeviceThread : public Thread
- {
- public:
- DeviceThread(AudioGroup *group) : Thread(false), mGroup(group) {}
-
- bool start()
- {
- if (run("Device", ANDROID_PRIORITY_AUDIO) != NO_ERROR) {
- ALOGE("cannot start device thread");
- return false;
- }
- return true;
- }
-
- private:
- AudioGroup *mGroup;
- bool threadLoop();
- };
- sp<DeviceThread> mDeviceThread;
-};
-
-AudioGroup::AudioGroup()
-{
- mMode = ON_HOLD;
- mChain = NULL;
- mEventQueue = -1;
- mDtmfEvent = -1;
- mDeviceSocket = -1;
- mNetworkThread = new NetworkThread(this);
- mDeviceThread = new DeviceThread(this);
- mPlatformHasAec = checkPlatformAec();
-}
-
-AudioGroup::~AudioGroup()
-{
- mNetworkThread->requestExitAndWait();
- mDeviceThread->requestExitAndWait();
- close(mEventQueue);
- close(mDeviceSocket);
- while (mChain) {
- AudioStream *next = mChain->mNext;
- delete mChain;
- mChain = next;
- }
- ALOGD("group[%d] is dead", mDeviceSocket);
-}
-
-bool AudioGroup::set(int sampleRate, int sampleCount)
-{
- mEventQueue = epoll_create(2);
- if (mEventQueue == -1) {
- ALOGE("epoll_create: %s", strerror(errno));
- return false;
- }
-
- mSampleRate = sampleRate;
- mSampleCount = sampleCount;
-
- // Create device socket.
- int pair[2];
- if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)) {
- ALOGE("socketpair: %s", strerror(errno));
- return false;
- }
- mDeviceSocket = pair[0];
-
- // Create device stream.
- mChain = new AudioStream;
- if (!mChain->set(AudioStream::NORMAL, pair[1], NULL, NULL,
- sampleRate, sampleCount, -1, -1)) {
- close(pair[1]);
- ALOGE("cannot initialize device stream");
- return false;
- }
-
- // Give device socket a reasonable timeout.
- timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = 1000 * sampleCount / sampleRate * 500;
- if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) {
- ALOGE("setsockopt: %s", strerror(errno));
- return false;
- }
-
- // Add device stream into event queue.
- epoll_event event;
- event.events = EPOLLIN;
- event.data.ptr = mChain;
- if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, pair[1], &event)) {
- ALOGE("epoll_ctl: %s", strerror(errno));
- return false;
- }
-
- // Anything else?
- ALOGD("stream[%d] joins group[%d]", pair[1], pair[0]);
- return true;
-}
-
-bool AudioGroup::setMode(int mode)
-{
- if (mode < 0 || mode > LAST_MODE) {
- return false;
- }
- // FIXME: temporary code to overcome echo and mic gain issues on herring and tuna boards.
- // Must be modified/removed when the root cause of the issue is fixed in the hardware or
- // driver
- char value[PROPERTY_VALUE_MAX];
- property_get("ro.product.board", value, "");
- if (mode == NORMAL &&
- (!strcmp(value, "herring") || !strcmp(value, "tuna"))) {
- mode = ECHO_SUPPRESSION;
- }
- if (mMode == mode) {
- return true;
- }
-
- mDeviceThread->requestExitAndWait();
- ALOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode);
- mMode = mode;
- return (mode == ON_HOLD) || mDeviceThread->start();
-}
-
-bool AudioGroup::sendDtmf(int event)
-{
- if (event < 0 || event > 15) {
- return false;
- }
-
- // DTMF is rarely used, so we try to make it as lightweight as possible.
- // Using volatile might be dodgy, but using a pipe or pthread primitives
- // or stop-set-restart threads seems too heavy. Will investigate later.
- timespec ts;
- ts.tv_sec = 0;
- ts.tv_nsec = 100000000;
- for (int i = 0; mDtmfEvent != -1 && i < 20; ++i) {
- nanosleep(&ts, NULL);
- }
- if (mDtmfEvent != -1) {
- return false;
- }
- mDtmfEvent = event;
- nanosleep(&ts, NULL);
- return true;
-}
-
-bool AudioGroup::add(AudioStream *stream)
-{
- mNetworkThread->requestExitAndWait();
-
- epoll_event event;
- event.events = EPOLLIN;
- event.data.ptr = stream;
- if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, stream->mSocket, &event)) {
- ALOGE("epoll_ctl: %s", strerror(errno));
- return false;
- }
-
- stream->mNext = mChain->mNext;
- mChain->mNext = stream;
- if (!mNetworkThread->start()) {
- // Only take over the stream when succeeded.
- mChain->mNext = stream->mNext;
- return false;
- }
-
- ALOGD("stream[%d] joins group[%d]", stream->mSocket, mDeviceSocket);
- return true;
-}
-
-bool AudioGroup::remove(AudioStream *stream)
-{
- mNetworkThread->requestExitAndWait();
-
- for (AudioStream *chain = mChain; chain->mNext; chain = chain->mNext) {
- if (chain->mNext == stream) {
- if (epoll_ctl(mEventQueue, EPOLL_CTL_DEL, stream->mSocket, NULL)) {
- ALOGE("epoll_ctl: %s", strerror(errno));
- return false;
- }
- chain->mNext = stream->mNext;
- ALOGD("stream[%d] leaves group[%d]", stream->mSocket, mDeviceSocket);
- delete stream;
- break;
- }
- }
-
- // Do not start network thread if there is only one stream.
- if (!mChain->mNext || !mNetworkThread->start()) {
- return false;
- }
- return true;
-}
-
-bool AudioGroup::NetworkThread::threadLoop()
-{
- AudioStream *chain = mGroup->mChain;
- int tick = elapsedRealtime();
- int deadline = tick + 10;
- int count = 0;
-
- for (AudioStream *stream = chain; stream; stream = stream->mNext) {
- if (tick - stream->mTick >= 0) {
- stream->encode(tick, chain);
- }
- if (deadline - stream->mTick > 0) {
- deadline = stream->mTick;
- }
- ++count;
- }
-
- int event = mGroup->mDtmfEvent;
- if (event != -1) {
- for (AudioStream *stream = chain; stream; stream = stream->mNext) {
- stream->sendDtmf(event);
- }
- mGroup->mDtmfEvent = -1;
- }
-
- deadline -= tick;
- if (deadline < 1) {
- deadline = 1;
- }
-
- epoll_event events[count];
- count = epoll_wait(mGroup->mEventQueue, events, count, deadline);
- if (count == -1) {
- ALOGE("epoll_wait: %s", strerror(errno));
- return false;
- }
- for (int i = 0; i < count; ++i) {
- ((AudioStream *)events[i].data.ptr)->decode(tick);
- }
-
- return true;
-}
-
-bool AudioGroup::checkPlatformAec()
-{
- effect_descriptor_t fxDesc;
- uint32_t numFx;
-
- if (AudioEffect::queryNumberEffects(&numFx) != NO_ERROR) {
- return false;
- }
- for (uint32_t i = 0; i < numFx; i++) {
- if (AudioEffect::queryEffect(i, &fxDesc) != NO_ERROR) {
- continue;
- }
- if (memcmp(&fxDesc.type, FX_IID_AEC, sizeof(effect_uuid_t)) == 0) {
- return true;
- }
- }
- return false;
-}
-
-bool AudioGroup::DeviceThread::threadLoop()
-{
- int mode = mGroup->mMode;
- int sampleRate = mGroup->mSampleRate;
- int sampleCount = mGroup->mSampleCount;
- int deviceSocket = mGroup->mDeviceSocket;
-
- // Find out the frame count for AudioTrack and AudioRecord.
- size_t output = 0;
- size_t input = 0;
- if (AudioTrack::getMinFrameCount(&output, AUDIO_STREAM_VOICE_CALL,
- sampleRate) != NO_ERROR || output <= 0 ||
- AudioRecord::getMinFrameCount(&input, sampleRate,
- AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_IN_MONO) != NO_ERROR || input <= 0) {
- ALOGE("cannot compute frame count");
- return false;
- }
- ALOGD("reported frame count: output %d, input %d", output, input);
-
- if (output < sampleCount * 2) {
- output = sampleCount * 2;
- }
- if (input < sampleCount * 2) {
- input = sampleCount * 2;
- }
- ALOGD("adjusted frame count: output %d, input %d", output, input);
-
- // Initialize AudioTrack and AudioRecord.
- AudioTrack track;
- AudioRecord record;
- if (track.set(AUDIO_STREAM_VOICE_CALL, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
- AUDIO_CHANNEL_OUT_MONO, output) != NO_ERROR ||
- record.set(AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
- AUDIO_CHANNEL_IN_MONO, input) != NO_ERROR) {
- ALOGE("cannot initialize audio device");
- return false;
- }
- ALOGD("latency: output %d, input %d", track.latency(), record.latency());
-
- // Give device socket a reasonable buffer size.
- setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output));
- setsockopt(deviceSocket, SOL_SOCKET, SO_SNDBUF, &output, sizeof(output));
-
- // Drain device socket.
- char c;
- while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1);
-
- // check if platform supports echo cancellation and do not active local echo suppression in
- // this case
- EchoSuppressor *echo = NULL;
- AudioEffect *aec = NULL;
- if (mode == ECHO_SUPPRESSION) {
- if (mGroup->platformHasAec()) {
- aec = new AudioEffect(FX_IID_AEC,
- NULL,
- 0,
- 0,
- 0,
- record.getSessionId(),
- record.getInput());
- status_t status = aec->initCheck();
- if (status == NO_ERROR || status == ALREADY_EXISTS) {
- aec->setEnabled(true);
- } else {
- delete aec;
- aec = NULL;
- }
- }
- // Create local echo suppressor if platform AEC cannot be used.
- if (aec == NULL) {
- echo = new EchoSuppressor(sampleCount,
- (track.latency() + record.latency()) * sampleRate / 1000);
- }
- }
- // Start AudioRecord before AudioTrack. This prevents AudioTrack from being
- // disabled due to buffer underrun while waiting for AudioRecord.
- if (mode != MUTED) {
- record.start();
- int16_t one;
- record.read(&one, sizeof(one));
- }
- track.start();
-
- while (!exitPending()) {
- int16_t output[sampleCount];
- if (recv(deviceSocket, output, sizeof(output), 0) <= 0) {
- memset(output, 0, sizeof(output));
- }
-
- int16_t input[sampleCount];
- int toWrite = sampleCount;
- int toRead = (mode == MUTED) ? 0 : sampleCount;
- int chances = 100;
-
- while (--chances > 0 && (toWrite > 0 || toRead > 0)) {
- if (toWrite > 0) {
- AudioTrack::Buffer buffer;
- buffer.frameCount = toWrite;
-
- status_t status = track.obtainBuffer(&buffer, 1);
- if (status == NO_ERROR) {
- int offset = sampleCount - toWrite;
- memcpy(buffer.i8, &output[offset], buffer.size);
- toWrite -= buffer.frameCount;
- track.releaseBuffer(&buffer);
- } else if (status != TIMED_OUT && status != WOULD_BLOCK) {
- ALOGE("cannot write to AudioTrack");
- goto exit;
- }
- }
-
- if (toRead > 0) {
- AudioRecord::Buffer buffer;
- buffer.frameCount = toRead;
-
- status_t status = record.obtainBuffer(&buffer, 1);
- if (status == NO_ERROR) {
- int offset = sampleCount - toRead;
- memcpy(&input[offset], buffer.i8, buffer.size);
- toRead -= buffer.frameCount;
- record.releaseBuffer(&buffer);
- } else if (status != TIMED_OUT && status != WOULD_BLOCK) {
- ALOGE("cannot read from AudioRecord");
- goto exit;
- }
- }
- }
-
- if (chances <= 0) {
- ALOGW("device loop timeout");
- while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1);
- }
-
- if (mode != MUTED) {
- if (echo != NULL) {
- ALOGV("echo->run()");
- echo->run(output, input);
- }
- send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
- }
- }
-
-exit:
- delete echo;
- delete aec;
- return true;
-}
-
-//------------------------------------------------------------------------------
-
-static jfieldID gNative;
-static jfieldID gMode;
-
-int add(JNIEnv *env, jobject thiz, jint mode,
- jint socket, jstring jRemoteAddress, jint remotePort,
- jstring jCodecSpec, jint dtmfType)
-{
- AudioCodec *codec = NULL;
- AudioStream *stream = NULL;
- AudioGroup *group = NULL;
-
- // Sanity check.
- sockaddr_storage remote;
- if (parse(env, jRemoteAddress, remotePort, &remote) < 0) {
- // Exception already thrown.
- return 0;
- }
- if (!jCodecSpec) {
- jniThrowNullPointerException(env, "codecSpec");
- return 0;
- }
- const char *codecSpec = env->GetStringUTFChars(jCodecSpec, NULL);
- if (!codecSpec) {
- // Exception already thrown.
- return 0;
- }
- socket = dup(socket);
- if (socket == -1) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "cannot get stream socket");
- return 0;
- }
-
- // Create audio codec.
- int codecType = -1;
- char codecName[16];
- int sampleRate = -1;
- sscanf(codecSpec, "%d %15[^/]%*c%d", &codecType, codecName, &sampleRate);
- codec = newAudioCodec(codecName);
- int sampleCount = (codec ? codec->set(sampleRate, codecSpec) : -1);
- env->ReleaseStringUTFChars(jCodecSpec, codecSpec);
- if (sampleCount <= 0) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "cannot initialize audio codec");
- goto error;
- }
-
- // Create audio stream.
- stream = new AudioStream;
- if (!stream->set(mode, socket, &remote, codec, sampleRate, sampleCount,
- codecType, dtmfType)) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "cannot initialize audio stream");
- goto error;
- }
- socket = -1;
- codec = NULL;
-
- // Create audio group.
- group = (AudioGroup *)env->GetIntField(thiz, gNative);
- if (!group) {
- int mode = env->GetIntField(thiz, gMode);
- group = new AudioGroup;
- if (!group->set(8000, 256) || !group->setMode(mode)) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "cannot initialize audio group");
- goto error;
- }
- }
-
- // Add audio stream into audio group.
- if (!group->add(stream)) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "cannot add audio stream");
- goto error;
- }
-
- // Succeed.
- env->SetIntField(thiz, gNative, (int)group);
- return (int)stream;
-
-error:
- delete group;
- delete stream;
- delete codec;
- close(socket);
- env->SetIntField(thiz, gNative, 0);
- return 0;
-}
-
-void remove(JNIEnv *env, jobject thiz, jint stream)
-{
- AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
- if (group) {
- if (!stream || !group->remove((AudioStream *)stream)) {
- delete group;
- env->SetIntField(thiz, gNative, 0);
- }
- }
-}
-
-void setMode(JNIEnv *env, jobject thiz, jint mode)
-{
- AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
- if (group && !group->setMode(mode)) {
- jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
- }
-}
-
-void sendDtmf(JNIEnv *env, jobject thiz, jint event)
-{
- AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
- if (group && !group->sendDtmf(event)) {
- jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
- }
-}
-
-JNINativeMethod gMethods[] = {
- {"nativeAdd", "(IILjava/lang/String;ILjava/lang/String;I)I", (void *)add},
- {"nativeRemove", "(I)V", (void *)remove},
- {"nativeSetMode", "(I)V", (void *)setMode},
- {"nativeSendDtmf", "(I)V", (void *)sendDtmf},
-};
-
-} // namespace
-
-int registerAudioGroup(JNIEnv *env)
-{
- gRandom = open("/dev/urandom", O_RDONLY);
- if (gRandom == -1) {
- ALOGE("urandom: %s", strerror(errno));
- return -1;
- }
-
- jclass clazz;
- if ((clazz = env->FindClass("android/net/rtp/AudioGroup")) == NULL ||
- (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL ||
- (gMode = env->GetFieldID(clazz, "mMode", "I")) == NULL ||
- env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) {
- ALOGE("JNI registration failed");
- return -1;
- }
- return 0;
-}
diff --git a/voip/jni/rtp/EchoSuppressor.cpp b/voip/jni/rtp/EchoSuppressor.cpp
deleted file mode 100644
index e223136..0000000
--- a/voip/jni/rtp/EchoSuppressor.cpp
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyrightm (C) 2010 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.
- */
-
-#include <stdio.h>
-#include <string.h>
-#include <stdint.h>
-#include <string.h>
-#include <math.h>
-
-#define LOG_TAG "Echo"
-#include <utils/Log.h>
-
-#include "EchoSuppressor.h"
-
-// It is very difficult to do echo cancellation at this level due to the lack of
-// the timing information of the samples being played and recorded. Therefore,
-// for the first release only echo suppression is implemented.
-
-// The algorithm is derived from the "previous works" summarized in
-// A new class of doubletalk detectors based on cross-correlation,
-// J Benesty, DR Morgan, JH Cho, IEEE Trans. on Speech and Audio Processing.
-// The method proposed in that paper is not used because of its high complexity.
-
-// It is well known that cross-correlation can be computed using convolution,
-// but unfortunately not every mobile processor has a (fast enough) FPU. Thus
-// we use integer arithmetic as much as possible and do lots of bookkeeping.
-// Again, parameters and thresholds are chosen by experiments.
-
-EchoSuppressor::EchoSuppressor(int sampleCount, int tailLength)
-{
- tailLength += sampleCount * 4;
-
- int shift = 0;
- while ((sampleCount >> shift) > 1 && (tailLength >> shift) > 256) {
- ++shift;
- }
-
- mShift = shift + 4;
- mScale = 1 << shift;
- mSampleCount = sampleCount;
- mWindowSize = sampleCount >> shift;
- mTailLength = tailLength >> shift;
- mRecordLength = tailLength * 2 / sampleCount;
- mRecordOffset = 0;
-
- mXs = new uint16_t[mTailLength + mWindowSize];
- memset(mXs, 0, sizeof(*mXs) * (mTailLength + mWindowSize));
- mXSums = new uint32_t[mTailLength];
- memset(mXSums, 0, sizeof(*mXSums) * mTailLength);
- mX2Sums = new uint32_t[mTailLength];
- memset(mX2Sums, 0, sizeof(*mX2Sums) * mTailLength);
- mXRecords = new uint16_t[mRecordLength * mWindowSize];
- memset(mXRecords, 0, sizeof(*mXRecords) * mRecordLength * mWindowSize);
-
- mYSum = 0;
- mY2Sum = 0;
- mYRecords = new uint32_t[mRecordLength];
- memset(mYRecords, 0, sizeof(*mYRecords) * mRecordLength);
- mY2Records = new uint32_t[mRecordLength];
- memset(mY2Records, 0, sizeof(*mY2Records) * mRecordLength);
-
- mXYSums = new uint32_t[mTailLength];
- memset(mXYSums, 0, sizeof(*mXYSums) * mTailLength);
- mXYRecords = new uint32_t[mRecordLength * mTailLength];
- memset(mXYRecords, 0, sizeof(*mXYRecords) * mRecordLength * mTailLength);
-
- mLastX = 0;
- mLastY = 0;
- mWeight = 1.0f / (mRecordLength * mWindowSize);
-}
-
-EchoSuppressor::~EchoSuppressor()
-{
- delete [] mXs;
- delete [] mXSums;
- delete [] mX2Sums;
- delete [] mXRecords;
- delete [] mYRecords;
- delete [] mY2Records;
- delete [] mXYSums;
- delete [] mXYRecords;
-}
-
-void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded)
-{
- // Update Xs.
- for (int i = mTailLength - 1; i >= 0; --i) {
- mXs[i + mWindowSize] = mXs[i];
- }
- for (int i = mWindowSize - 1, j = 0; i >= 0; --i, j += mScale) {
- uint32_t sum = 0;
- for (int k = 0; k < mScale; ++k) {
- int32_t x = playbacked[j + k] << 15;
- mLastX += x;
- sum += ((mLastX >= 0) ? mLastX : -mLastX) >> 15;
- mLastX -= (mLastX >> 10) + x;
- }
- mXs[i] = sum >> mShift;
- }
-
- // Update XSums, X2Sums, and XRecords.
- for (int i = mTailLength - mWindowSize - 1; i >= 0; --i) {
- mXSums[i + mWindowSize] = mXSums[i];
- mX2Sums[i + mWindowSize] = mX2Sums[i];
- }
- uint16_t *xRecords = &mXRecords[mRecordOffset * mWindowSize];
- for (int i = mWindowSize - 1; i >= 0; --i) {
- uint16_t x = mXs[i];
- mXSums[i] = mXSums[i + 1] + x - xRecords[i];
- mX2Sums[i] = mX2Sums[i + 1] + x * x - xRecords[i] * xRecords[i];
- xRecords[i] = x;
- }
-
- // Compute Ys.
- uint16_t ys[mWindowSize];
- for (int i = mWindowSize - 1, j = 0; i >= 0; --i, j += mScale) {
- uint32_t sum = 0;
- for (int k = 0; k < mScale; ++k) {
- int32_t y = recorded[j + k] << 15;
- mLastY += y;
- sum += ((mLastY >= 0) ? mLastY : -mLastY) >> 15;
- mLastY -= (mLastY >> 10) + y;
- }
- ys[i] = sum >> mShift;
- }
-
- // Update YSum, Y2Sum, YRecords, and Y2Records.
- uint32_t ySum = 0;
- uint32_t y2Sum = 0;
- for (int i = mWindowSize - 1; i >= 0; --i) {
- ySum += ys[i];
- y2Sum += ys[i] * ys[i];
- }
- mYSum += ySum - mYRecords[mRecordOffset];
- mY2Sum += y2Sum - mY2Records[mRecordOffset];
- mYRecords[mRecordOffset] = ySum;
- mY2Records[mRecordOffset] = y2Sum;
-
- // Update XYSums and XYRecords.
- uint32_t *xyRecords = &mXYRecords[mRecordOffset * mTailLength];
- for (int i = mTailLength - 1; i >= 0; --i) {
- uint32_t xySum = 0;
- for (int j = mWindowSize - 1; j >= 0; --j) {
- xySum += mXs[i + j] * ys[j];
- }
- mXYSums[i] += xySum - xyRecords[i];
- xyRecords[i] = xySum;
- }
-
- // Compute correlations.
- int latency = 0;
- float corr2 = 0.0f;
- float varX = 0.0f;
- float varY = mY2Sum - mWeight * mYSum * mYSum;
- for (int i = mTailLength - 1; i >= 0; --i) {
- float cov = mXYSums[i] - mWeight * mXSums[i] * mYSum;
- if (cov > 0.0f) {
- float varXi = mX2Sums[i] - mWeight * mXSums[i] * mXSums[i];
- float corr2i = cov * cov / (varXi * varY + 1);
- if (corr2i > corr2) {
- varX = varXi;
- corr2 = corr2i;
- latency = i;
- }
- }
- }
- //ALOGI("corr^2 %.5f, var %8.0f %8.0f, latency %d", corr2, varX, varY,
- // latency * mScale);
-
- // Do echo suppression.
- if (corr2 > 0.1f && varX > 10000.0f) {
- int factor = (corr2 > 1.0f) ? 0 : (1.0f - sqrtf(corr2)) * 4096;
- for (int i = 0; i < mSampleCount; ++i) {
- recorded[i] = recorded[i] * factor >> 16;
- }
- }
-
- // Increase RecordOffset.
- ++mRecordOffset;
- if (mRecordOffset == mRecordLength) {
- mRecordOffset = 0;
- }
-}
diff --git a/voip/jni/rtp/EchoSuppressor.h b/voip/jni/rtp/EchoSuppressor.h
deleted file mode 100644
index 2f3b593..0000000
--- a/voip/jni/rtp/EchoSuppressor.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyrightm (C) 2010 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.
- */
-
-#ifndef __ECHO_SUPPRESSOR_H__
-#define __ECHO_SUPPRESSOR_H__
-
-#include <stdint.h>
-
-class EchoSuppressor
-{
-public:
- // The sampleCount must be power of 2.
- EchoSuppressor(int sampleCount, int tailLength);
- ~EchoSuppressor();
- void run(int16_t *playbacked, int16_t *recorded);
-
-private:
- int mShift;
- int mScale;
- int mSampleCount;
- int mWindowSize;
- int mTailLength;
- int mRecordLength;
- int mRecordOffset;
-
- uint16_t *mXs;
- uint32_t *mXSums;
- uint32_t *mX2Sums;
- uint16_t *mXRecords;
-
- uint32_t mYSum;
- uint32_t mY2Sum;
- uint32_t *mYRecords;
- uint32_t *mY2Records;
-
- uint32_t *mXYSums;
- uint32_t *mXYRecords;
-
- int32_t mLastX;
- int32_t mLastY;
-
- float mWeight;
-};
-
-#endif
diff --git a/voip/jni/rtp/G711Codec.cpp b/voip/jni/rtp/G711Codec.cpp
deleted file mode 100644
index ef54863..0000000
--- a/voip/jni/rtp/G711Codec.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyrightm (C) 2010 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.
- */
-
-#include "AudioCodec.h"
-
-namespace {
-
-const int8_t gExponents[128] = {
- 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
- 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
- 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
- 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
-};
-
-//------------------------------------------------------------------------------
-
-class UlawCodec : public AudioCodec
-{
-public:
- int set(int sampleRate, const char *fmtp) {
- mSampleCount = sampleRate / 50;
- return mSampleCount;
- }
- int encode(void *payload, int16_t *samples);
- int decode(int16_t *samples, int count, void *payload, int length);
-private:
- int mSampleCount;
-};
-
-int UlawCodec::encode(void *payload, int16_t *samples)
-{
- int8_t *ulaws = (int8_t *)payload;
- for (int i = 0; i < mSampleCount; ++i) {
- int sample = samples[i];
- int sign = (sample >> 8) & 0x80;
- if (sample < 0) {
- sample = -sample;
- }
- sample += 132;
- if (sample > 32767) {
- sample = 32767;
- }
- int exponent = gExponents[sample >> 8];
- int mantissa = (sample >> (exponent + 3)) & 0x0F;
- ulaws[i] = ~(sign | (exponent << 4) | mantissa);
- }
- return mSampleCount;
-}
-
-int UlawCodec::decode(int16_t *samples, int count, void *payload, int length)
-{
- int8_t *ulaws = (int8_t *)payload;
- if (length > count) {
- length = count;
- }
- for (int i = 0; i < length; ++i) {
- int ulaw = ~ulaws[i];
- int exponent = (ulaw >> 4) & 0x07;
- int mantissa = ulaw & 0x0F;
- int sample = (((mantissa << 3) + 132) << exponent) - 132;
- samples[i] = (ulaw < 0 ? -sample : sample);
- }
- return length;
-}
-
-//------------------------------------------------------------------------------
-
-class AlawCodec : public AudioCodec
-{
-public:
- int set(int sampleRate, const char *fmtp) {
- mSampleCount = sampleRate / 50;
- return mSampleCount;
- }
- int encode(void *payload, int16_t *samples);
- int decode(int16_t *samples, int count, void *payload, int length);
-private:
- int mSampleCount;
-};
-
-int AlawCodec::encode(void *payload, int16_t *samples)
-{
- int8_t *alaws = (int8_t *)payload;
- for (int i = 0; i < mSampleCount; ++i) {
- int sample = samples[i];
- int sign = (sample >> 8) & 0x80;
- if (sample < 0) {
- sample = -sample;
- }
- if (sample > 32767) {
- sample = 32767;
- }
- int exponent = gExponents[sample >> 8];
- int mantissa = (sample >> (exponent == 0 ? 4 : exponent + 3)) & 0x0F;
- alaws[i] = (sign | (exponent << 4) | mantissa) ^ 0xD5;
- }
- return mSampleCount;
-}
-
-int AlawCodec::decode(int16_t *samples, int count, void *payload, int length)
-{
- int8_t *alaws = (int8_t *)payload;
- if (length > count) {
- length = count;
- }
- for (int i = 0; i < length; ++i) {
- int alaw = alaws[i] ^ 0x55;
- int exponent = (alaw >> 4) & 0x07;
- int mantissa = alaw & 0x0F;
- int sample = (exponent == 0 ? (mantissa << 4) + 8 :
- ((mantissa << 3) + 132) << exponent);
- samples[i] = (alaw < 0 ? sample : -sample);
- }
- return length;
-}
-
-} // namespace
-
-AudioCodec *newUlawCodec()
-{
- return new UlawCodec;
-}
-
-AudioCodec *newAlawCodec()
-{
- return new AlawCodec;
-}
diff --git a/voip/jni/rtp/GsmCodec.cpp b/voip/jni/rtp/GsmCodec.cpp
deleted file mode 100644
index 61dfdc9..0000000
--- a/voip/jni/rtp/GsmCodec.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyrightm (C) 2010 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.
- */
-
-#include "AudioCodec.h"
-
-extern "C" {
-#include "gsm.h"
-}
-
-namespace {
-
-class GsmCodec : public AudioCodec
-{
-public:
- GsmCodec() {
- mEncode = gsm_create();
- mDecode = gsm_create();
- }
-
- ~GsmCodec() {
- if (mEncode) {
- gsm_destroy(mEncode);
- }
- if (mDecode) {
- gsm_destroy(mDecode);
- }
- }
-
- int set(int sampleRate, const char *fmtp) {
- return (sampleRate == 8000 && mEncode && mDecode) ? 160 : -1;
- }
-
- int encode(void *payload, int16_t *samples);
- int decode(int16_t *samples, int count, void *payload, int length);
-
-private:
- gsm mEncode;
- gsm mDecode;
-};
-
-int GsmCodec::encode(void *payload, int16_t *samples)
-{
- gsm_encode(mEncode, samples, (unsigned char *)payload);
- return 33;
-}
-
-int GsmCodec::decode(int16_t *samples, int count, void *payload, int length)
-{
- unsigned char *bytes = (unsigned char *)payload;
- int n = 0;
- while (n + 160 <= count && length >= 33 &&
- gsm_decode(mDecode, bytes, &samples[n]) == 0) {
- n += 160;
- length -= 33;
- bytes += 33;
- }
- return n;
-}
-
-} // namespace
-
-AudioCodec *newGsmCodec()
-{
- return new GsmCodec;
-}
diff --git a/voip/jni/rtp/RtpStream.cpp b/voip/jni/rtp/RtpStream.cpp
deleted file mode 100644
index bfe8e24..0000000
--- a/voip/jni/rtp/RtpStream.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-#include <stdio.h>
-#include <stdint.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-
-#define LOG_TAG "RtpStream"
-#include <utils/Log.h>
-
-#include "jni.h"
-#include "JNIHelp.h"
-
-extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss);
-
-namespace {
-
-jfieldID gSocket;
-
-jint create(JNIEnv *env, jobject thiz, jstring jAddress)
-{
- env->SetIntField(thiz, gSocket, -1);
-
- sockaddr_storage ss;
- if (parse(env, jAddress, 0, &ss) < 0) {
- // Exception already thrown.
- return -1;
- }
-
- int socket = ::socket(ss.ss_family, SOCK_DGRAM, 0);
- socklen_t len = sizeof(ss);
- if (socket == -1 || bind(socket, (sockaddr *)&ss, sizeof(ss)) != 0 ||
- getsockname(socket, (sockaddr *)&ss, &len) != 0) {
- jniThrowException(env, "java/net/SocketException", strerror(errno));
- ::close(socket);
- return -1;
- }
-
- uint16_t *p = (ss.ss_family == AF_INET) ?
- &((sockaddr_in *)&ss)->sin_port : &((sockaddr_in6 *)&ss)->sin6_port;
- uint16_t port = ntohs(*p);
- if ((port & 1) == 0) {
- env->SetIntField(thiz, gSocket, socket);
- return port;
- }
- ::close(socket);
-
- socket = ::socket(ss.ss_family, SOCK_DGRAM, 0);
- if (socket != -1) {
- uint16_t delta = port << 1;
- ++port;
-
- for (int i = 0; i < 1000; ++i) {
- do {
- port += delta;
- } while (port < 1024);
- *p = htons(port);
-
- if (bind(socket, (sockaddr *)&ss, sizeof(ss)) == 0) {
- env->SetIntField(thiz, gSocket, socket);
- return port;
- }
- }
- }
-
- jniThrowException(env, "java/net/SocketException", strerror(errno));
- ::close(socket);
- return -1;
-}
-
-void close(JNIEnv *env, jobject thiz)
-{
- int socket = env->GetIntField(thiz, gSocket);
- ::close(socket);
- env->SetIntField(thiz, gSocket, -1);
-}
-
-JNINativeMethod gMethods[] = {
- {"create", "(Ljava/lang/String;)I", (void *)create},
- {"close", "()V", (void *)close},
-};
-
-} // namespace
-
-int registerRtpStream(JNIEnv *env)
-{
- jclass clazz;
- if ((clazz = env->FindClass("android/net/rtp/RtpStream")) == NULL ||
- (gSocket = env->GetFieldID(clazz, "mSocket", "I")) == NULL ||
- env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) {
- ALOGE("JNI registration failed");
- return -1;
- }
- return 0;
-}
diff --git a/voip/jni/rtp/rtp_jni.cpp b/voip/jni/rtp/rtp_jni.cpp
deleted file mode 100644
index 9f4bff9..0000000
--- a/voip/jni/rtp/rtp_jni.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-#include <stdio.h>
-
-#include "jni.h"
-
-extern int registerRtpStream(JNIEnv *env);
-extern int registerAudioGroup(JNIEnv *env);
-
-__attribute__((visibility("default"))) jint JNI_OnLoad(JavaVM *vm, void *unused)
-{
- JNIEnv *env = NULL;
- if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK ||
- registerRtpStream(env) < 0 || registerAudioGroup(env) < 0) {
- return -1;
- }
- return JNI_VERSION_1_4;
-}
diff --git a/voip/jni/rtp/util.cpp b/voip/jni/rtp/util.cpp
deleted file mode 100644
index 1d702fc..0000000
--- a/voip/jni/rtp/util.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-#include <stdio.h>
-#include <string.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-
-#include "jni.h"
-#include "JNIHelp.h"
-
-int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss)
-{
- if (!jAddress) {
- jniThrowNullPointerException(env, "address");
- return -1;
- }
- if (port < 0 || port > 65535) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "port");
- return -1;
- }
- const char *address = env->GetStringUTFChars(jAddress, NULL);
- if (!address) {
- // Exception already thrown.
- return -1;
- }
- memset(ss, 0, sizeof(*ss));
-
- sockaddr_in *sin = (sockaddr_in *)ss;
- if (inet_pton(AF_INET, address, &(sin->sin_addr)) > 0) {
- sin->sin_family = AF_INET;
- sin->sin_port = htons(port);
- env->ReleaseStringUTFChars(jAddress, address);
- return 0;
- }
-
- sockaddr_in6 *sin6 = (sockaddr_in6 *)ss;
- if (inet_pton(AF_INET6, address, &(sin6->sin6_addr)) > 0) {
- sin6->sin6_family = AF_INET6;
- sin6->sin6_port = htons(port);
- env->ReleaseStringUTFChars(jAddress, address);
- return 0;
- }
-
- env->ReleaseStringUTFChars(jAddress, address);
- jniThrowException(env, "java/lang/IllegalArgumentException", "address");
- return -1;
-}