Merge "Telecom API updates for mainline support."
diff --git a/api/current.txt b/api/current.txt
index de4fe4c3..402fe89 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -28766,7 +28766,9 @@
method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]);
method @NonNull public static android.net.MacAddress fromString(@NonNull String);
method public int getAddressType();
+ method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac();
method public boolean isLocallyAssigned();
+ method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress);
method @NonNull public byte[] toByteArray();
method @NonNull public String toOuiString();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 5c65238..cfa3934 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -473,6 +473,14 @@
public static final int TETHERING_BLUETOOTH = 2;
/**
+ * Wifi P2p tethering type.
+ * Wifi P2p tethering is set through events automatically, and don't
+ * need to start from #startTethering(int, boolean, OnStartTetheringCallback).
+ * @hide
+ */
+ public static final int TETHERING_WIFI_P2P = 3;
+
+ /**
* Extra used for communicating with the TetherService. Includes the type of tethering to
* enable if any.
* @hide
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 2cf2a65..8729514 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -416,7 +416,6 @@
* @param mask MacAddress representing the mask to use during comparison.
* @return true if this MAC Address matches the given range.
*
- * @hide
*/
public boolean matches(@NonNull MacAddress baseAddress, @NonNull MacAddress mask) {
Preconditions.checkNotNull(baseAddress);
@@ -430,7 +429,6 @@
* IPv6 address per RFC 4862.
*
* @return A link-local Inet6Address constructed from the MAC address.
- * @hide
*/
public @Nullable Inet6Address getLinkLocalIpv6FromEui48Mac() {
byte[] macEui48Bytes = toByteArray();
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 6270d89..486d24c 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -169,7 +169,6 @@
"android_media_AudioVolumeGroups.cpp",
"android_media_AudioVolumeGroupCallback.cpp",
"android_media_DeviceCallback.cpp",
- "android_media_JetPlayer.cpp",
"android_media_MediaMetricsJNI.cpp",
"android_media_MicrophoneInfo.cpp",
"android_media_midi.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 571c2cb..9a90555 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -113,7 +113,6 @@
extern int register_android_media_AudioVolumeGroups(JNIEnv *env);
extern int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env);
extern int register_android_media_MicrophoneInfo(JNIEnv *env);
-extern int register_android_media_JetPlayer(JNIEnv *env);
extern int register_android_media_ToneGenerator(JNIEnv *env);
extern int register_android_media_midi(JNIEnv *env);
@@ -1589,7 +1588,6 @@
REG_JNI(register_android_media_AudioProductStrategies),
REG_JNI(register_android_media_AudioVolumeGroups),
REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
- REG_JNI(register_android_media_JetPlayer),
REG_JNI(register_android_media_MicrophoneInfo),
REG_JNI(register_android_media_RemoteDisplay),
REG_JNI(register_android_media_ToneGenerator),
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8336f54..5ccc17d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -387,6 +387,12 @@
</string-array>
<!-- List of regexpressions describing the interface (if any) that represent tetherable
+ Wifi P2P interfaces. If the device doesn't want to support tethering over Wifi P2p this
+ should be empty. An example would be "p2p-p2p.*" -->
+ <string-array translatable="false" name="config_tether_wifi_p2p_regexs">
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
WiMAX interfaces. If the device doesn't want to support tethering over Wifi this
should be empty. An example would be "softap.*" -->
<string-array translatable="false" name="config_tether_wimax_regexs">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0bd5e43..81e8f9f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1914,6 +1914,7 @@
<java-symbol type="bool" name="config_tether_upstream_automatic" />
<java-symbol type="array" name="config_tether_usb_regexs" />
<java-symbol type="array" name="config_tether_wifi_regexs" />
+ <java-symbol type="array" name="config_tether_wifi_p2p_regexs" />
<java-symbol type="array" name="config_usbHostBlacklist" />
<java-symbol type="array" name="config_serialPorts" />
<java-symbol type="array" name="radioAttributes" />
diff --git a/media/java/android/media/JetPlayer.java b/media/java/android/media/JetPlayer.java
index b12e647..e85b3ff9 100644
--- a/media/java/android/media/JetPlayer.java
+++ b/media/java/android/media/JetPlayer.java
@@ -17,18 +17,17 @@
package android.media;
-import java.io.FileDescriptor;
-import java.lang.ref.WeakReference;
-import java.lang.CloneNotSupportedException;
-
import android.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
-import android.os.Looper;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.util.AndroidRuntimeException;
import android.util.Log;
+import java.io.FileDescriptor;
+import java.lang.ref.WeakReference;
+
/**
* JetPlayer provides access to JET content playback and control.
*
@@ -120,6 +119,9 @@
private static JetPlayer singletonRef;
+ static {
+ System.loadLibrary("media_jni");
+ }
//--------------------------------
// Used exclusively by native code
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 45ee210..e6c1c67 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -4,6 +4,7 @@
srcs: [
"android_media_ImageWriter.cpp",
"android_media_ImageReader.cpp",
+ "android_media_JetPlayer.cpp",
"android_media_MediaCrypto.cpp",
"android_media_MediaCodec.cpp",
"android_media_MediaCodecList.cpp",
@@ -25,10 +26,12 @@
"android_mtp_MtpDatabase.cpp",
"android_mtp_MtpDevice.cpp",
"android_mtp_MtpServer.cpp",
+ "JetPlayer.cpp",
],
shared_libs: [
"libandroid_runtime",
+ "libaudioclient",
"libnativehelper",
"libnativewindow",
"libutils",
@@ -53,6 +56,7 @@
"libandroidfw",
"libhidlallocatorutils",
"libhidlbase",
+ "libsonivox",
"android.hardware.cas@1.0",
"android.hardware.cas.native@1.0",
"android.hidl.memory@1.0",
@@ -64,7 +68,10 @@
"libmediadrm_headers",
],
- static_libs: ["libgrallocusage"],
+ static_libs: [
+ "libgrallocusage",
+ "libmedia_midiiowrapper",
+ ],
include_dirs: [
"frameworks/base/core/jni",
diff --git a/media/jni/JetPlayer.cpp b/media/jni/JetPlayer.cpp
new file mode 100644
index 0000000..358feb7
--- /dev/null
+++ b/media/jni/JetPlayer.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "JetPlayer-C"
+
+#include <utils/Log.h>
+#include "JetPlayer.h"
+
+
+namespace android
+{
+
+static const int MIX_NUM_BUFFERS = 4;
+static const S_EAS_LIB_CONFIG* pLibConfig = NULL;
+
+//-------------------------------------------------------------------------------------------------
+JetPlayer::JetPlayer(void *javaJetPlayer, int maxTracks, int trackBufferSize) :
+ mEventCallback(NULL),
+ mJavaJetPlayerRef(javaJetPlayer),
+ mTid(-1),
+ mRender(false),
+ mPaused(false),
+ mMaxTracks(maxTracks),
+ mEasData(NULL),
+ mIoWrapper(NULL),
+ mTrackBufferSize(trackBufferSize)
+{
+ ALOGV("JetPlayer constructor");
+ mPreviousJetStatus.currentUserID = -1;
+ mPreviousJetStatus.segmentRepeatCount = -1;
+ mPreviousJetStatus.numQueuedSegments = -1;
+ mPreviousJetStatus.paused = true;
+}
+
+//-------------------------------------------------------------------------------------------------
+JetPlayer::~JetPlayer()
+{
+ ALOGV("~JetPlayer");
+ release();
+}
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::init()
+{
+ //Mutex::Autolock lock(&mMutex);
+
+ EAS_RESULT result;
+
+ // retrieve the EAS library settings
+ if (pLibConfig == NULL)
+ pLibConfig = EAS_Config();
+ if (pLibConfig == NULL) {
+ ALOGE("JetPlayer::init(): EAS library configuration could not be retrieved, aborting.");
+ return EAS_FAILURE;
+ }
+
+ // init the EAS library
+ result = EAS_Init(&mEasData);
+ if (result != EAS_SUCCESS) {
+ ALOGE("JetPlayer::init(): Error initializing Sonivox EAS library, aborting.");
+ mState = EAS_STATE_ERROR;
+ return result;
+ }
+ // init the JET library with the default app event controller range
+ result = JET_Init(mEasData, NULL, sizeof(S_JET_CONFIG));
+ if (result != EAS_SUCCESS) {
+ ALOGE("JetPlayer::init(): Error initializing JET library, aborting.");
+ mState = EAS_STATE_ERROR;
+ return result;
+ }
+
+ // create the output AudioTrack
+ mAudioTrack = new AudioTrack();
+ status_t status = mAudioTrack->set(AUDIO_STREAM_MUSIC, //TODO parameterize this
+ pLibConfig->sampleRate,
+ AUDIO_FORMAT_PCM_16_BIT,
+ audio_channel_out_mask_from_count(pLibConfig->numChannels),
+ (size_t) mTrackBufferSize,
+ AUDIO_OUTPUT_FLAG_NONE);
+ if (status != OK) {
+ ALOGE("JetPlayer::init(): Error initializing JET library; AudioTrack error %d", status);
+ mAudioTrack.clear();
+ mState = EAS_STATE_ERROR;
+ return EAS_FAILURE;
+ }
+
+ // create render and playback thread
+ {
+ Mutex::Autolock l(mMutex);
+ ALOGV("JetPlayer::init(): trying to start render thread");
+ mThread = new JetPlayerThread(this);
+ mThread->run("jetRenderThread", ANDROID_PRIORITY_AUDIO);
+ mCondition.wait(mMutex);
+ }
+ if (mTid > 0) {
+ // render thread started, we're ready
+ ALOGV("JetPlayer::init(): render thread(%d) successfully started.", mTid);
+ mState = EAS_STATE_READY;
+ } else {
+ ALOGE("JetPlayer::init(): failed to start render thread.");
+ mState = EAS_STATE_ERROR;
+ return EAS_FAILURE;
+ }
+
+ return EAS_SUCCESS;
+}
+
+void JetPlayer::setEventCallback(jetevent_callback eventCallback)
+{
+ Mutex::Autolock l(mMutex);
+ mEventCallback = eventCallback;
+}
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::release()
+{
+ ALOGV("JetPlayer::release()");
+ Mutex::Autolock lock(mMutex);
+ mPaused = true;
+ mRender = false;
+ if (mEasData) {
+ JET_Pause(mEasData);
+ JET_CloseFile(mEasData);
+ JET_Shutdown(mEasData);
+ EAS_Shutdown(mEasData);
+ }
+ delete mIoWrapper;
+ mIoWrapper = NULL;
+ if (mAudioTrack != 0) {
+ mAudioTrack->stop();
+ mAudioTrack->flush();
+ mAudioTrack.clear();
+ }
+ if (mAudioBuffer) {
+ delete mAudioBuffer;
+ mAudioBuffer = NULL;
+ }
+ mEasData = NULL;
+
+ return EAS_SUCCESS;
+}
+
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::render() {
+ EAS_RESULT result = EAS_FAILURE;
+ EAS_I32 count;
+ int temp;
+ bool audioStarted = false;
+
+ ALOGV("JetPlayer::render(): entering");
+
+ // allocate render buffer
+ mAudioBuffer =
+ new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * MIX_NUM_BUFFERS];
+
+ // signal main thread that we started
+ {
+ Mutex::Autolock l(mMutex);
+ mTid = gettid();
+ ALOGV("JetPlayer::render(): render thread(%d) signal", mTid);
+ mCondition.signal();
+ }
+
+ while (1) {
+
+ mMutex.lock(); // [[[[[[[[ LOCK ---------------------------------------
+
+ if (mEasData == NULL) {
+ mMutex.unlock();
+ ALOGV("JetPlayer::render(): NULL EAS data, exiting render.");
+ goto threadExit;
+ }
+
+ // nothing to render, wait for client thread to wake us up
+ while (!mRender)
+ {
+ ALOGV("JetPlayer::render(): signal wait");
+ if (audioStarted) {
+ mAudioTrack->pause();
+ // we have to restart the playback once we start rendering again
+ audioStarted = false;
+ }
+ mCondition.wait(mMutex);
+ ALOGV("JetPlayer::render(): signal rx'd");
+ }
+
+ // render midi data into the input buffer
+ int num_output = 0;
+ EAS_PCM* p = mAudioBuffer;
+ for (int i = 0; i < MIX_NUM_BUFFERS; i++) {
+ result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count);
+ if (result != EAS_SUCCESS) {
+ ALOGE("JetPlayer::render(): EAS_Render returned error %ld", result);
+ }
+ p += count * pLibConfig->numChannels;
+ num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
+
+ // send events that were generated (if any) to the event callback
+ fireEventsFromJetQueue();
+ }
+
+ // update playback state
+ //ALOGV("JetPlayer::render(): updating state");
+ JET_Status(mEasData, &mJetStatus);
+ fireUpdateOnStatusChange();
+ mPaused = mJetStatus.paused;
+
+ mMutex.unlock(); // UNLOCK ]]]]]]]] -----------------------------------
+
+ // check audio output track
+ if (mAudioTrack == NULL) {
+ ALOGE("JetPlayer::render(): output AudioTrack was not created");
+ goto threadExit;
+ }
+
+ // Write data to the audio hardware
+ //ALOGV("JetPlayer::render(): writing to audio output");
+ if ((temp = mAudioTrack->write(mAudioBuffer, num_output)) < 0) {
+ ALOGE("JetPlayer::render(): Error in writing:%d",temp);
+ return temp;
+ }
+
+ // start audio output if necessary
+ if (!audioStarted) {
+ ALOGV("JetPlayer::render(): starting audio playback");
+ mAudioTrack->start();
+ audioStarted = true;
+ }
+
+ }//while (1)
+
+threadExit:
+ if (mAudioTrack != NULL) {
+ mAudioTrack->stop();
+ mAudioTrack->flush();
+ }
+ delete [] mAudioBuffer;
+ mAudioBuffer = NULL;
+ mMutex.lock();
+ mTid = -1;
+ mCondition.signal();
+ mMutex.unlock();
+ return result;
+}
+
+
+//-------------------------------------------------------------------------------------------------
+// fire up an update if any of the status fields has changed
+// precondition: mMutex locked
+void JetPlayer::fireUpdateOnStatusChange()
+{
+ if ( (mJetStatus.currentUserID != mPreviousJetStatus.currentUserID)
+ ||(mJetStatus.segmentRepeatCount != mPreviousJetStatus.segmentRepeatCount) ) {
+ if (mEventCallback) {
+ mEventCallback(
+ JetPlayer::JET_USERID_UPDATE,
+ mJetStatus.currentUserID,
+ mJetStatus.segmentRepeatCount,
+ mJavaJetPlayerRef);
+ }
+ mPreviousJetStatus.currentUserID = mJetStatus.currentUserID;
+ mPreviousJetStatus.segmentRepeatCount = mJetStatus.segmentRepeatCount;
+ }
+
+ if (mJetStatus.numQueuedSegments != mPreviousJetStatus.numQueuedSegments) {
+ if (mEventCallback) {
+ mEventCallback(
+ JetPlayer::JET_NUMQUEUEDSEGMENT_UPDATE,
+ mJetStatus.numQueuedSegments,
+ -1,
+ mJavaJetPlayerRef);
+ }
+ mPreviousJetStatus.numQueuedSegments = mJetStatus.numQueuedSegments;
+ }
+
+ if (mJetStatus.paused != mPreviousJetStatus.paused) {
+ if (mEventCallback) {
+ mEventCallback(JetPlayer::JET_PAUSE_UPDATE,
+ mJetStatus.paused,
+ -1,
+ mJavaJetPlayerRef);
+ }
+ mPreviousJetStatus.paused = mJetStatus.paused;
+ }
+
+}
+
+
+//-------------------------------------------------------------------------------------------------
+// fire up all the JET events in the JET engine queue (until the queue is empty)
+// precondition: mMutex locked
+void JetPlayer::fireEventsFromJetQueue()
+{
+ if (!mEventCallback) {
+ // no callback, just empty the event queue
+ while (JET_GetEvent(mEasData, NULL, NULL)) { }
+ return;
+ }
+
+ EAS_U32 rawEvent;
+ while (JET_GetEvent(mEasData, &rawEvent, NULL)) {
+ mEventCallback(
+ JetPlayer::JET_EVENT,
+ rawEvent,
+ -1,
+ mJavaJetPlayerRef);
+ }
+}
+
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::loadFromFile(const char* path)
+{
+ ALOGV("JetPlayer::loadFromFile(): path=%s", path);
+
+ Mutex::Autolock lock(mMutex);
+
+ delete mIoWrapper;
+ mIoWrapper = new MidiIoWrapper(path);
+
+ EAS_RESULT result = JET_OpenFile(mEasData, mIoWrapper->getLocator());
+ if (result != EAS_SUCCESS)
+ mState = EAS_STATE_ERROR;
+ else
+ mState = EAS_STATE_OPEN;
+ return( result );
+}
+
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::loadFromFD(const int fd, const long long offset, const long long length)
+{
+ ALOGV("JetPlayer::loadFromFD(): fd=%d offset=%lld length=%lld", fd, offset, length);
+
+ Mutex::Autolock lock(mMutex);
+
+ delete mIoWrapper;
+ mIoWrapper = new MidiIoWrapper(fd, offset, length);
+
+ EAS_RESULT result = JET_OpenFile(mEasData, mIoWrapper->getLocator());
+ if (result != EAS_SUCCESS)
+ mState = EAS_STATE_ERROR;
+ else
+ mState = EAS_STATE_OPEN;
+ return( result );
+}
+
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::closeFile()
+{
+ Mutex::Autolock lock(mMutex);
+ return JET_CloseFile(mEasData);
+}
+
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::play()
+{
+ ALOGV("JetPlayer::play(): entering");
+ Mutex::Autolock lock(mMutex);
+
+ EAS_RESULT result = JET_Play(mEasData);
+
+ mPaused = false;
+ mRender = true;
+
+ JET_Status(mEasData, &mJetStatus);
+ this->dumpJetStatus(&mJetStatus);
+
+ fireUpdateOnStatusChange();
+
+ // wake up render thread
+ ALOGV("JetPlayer::play(): wakeup render thread");
+ mCondition.signal();
+
+ return result;
+}
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::pause()
+{
+ Mutex::Autolock lock(mMutex);
+ mPaused = true;
+ EAS_RESULT result = JET_Pause(mEasData);
+
+ mRender = false;
+
+ JET_Status(mEasData, &mJetStatus);
+ this->dumpJetStatus(&mJetStatus);
+ fireUpdateOnStatusChange();
+
+
+ return result;
+}
+
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::queueSegment(int segmentNum, int libNum, int repeatCount, int transpose,
+ EAS_U32 muteFlags, EAS_U8 userID)
+{
+ ALOGV("JetPlayer::queueSegment segmentNum=%d, libNum=%d, repeatCount=%d, transpose=%d",
+ segmentNum, libNum, repeatCount, transpose);
+ Mutex::Autolock lock(mMutex);
+ return JET_QueueSegment(mEasData, segmentNum, libNum, repeatCount, transpose, muteFlags,
+ userID);
+}
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::setMuteFlags(EAS_U32 muteFlags, bool sync)
+{
+ Mutex::Autolock lock(mMutex);
+ return JET_SetMuteFlags(mEasData, muteFlags, sync);
+}
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::setMuteFlag(int trackNum, bool muteFlag, bool sync)
+{
+ Mutex::Autolock lock(mMutex);
+ return JET_SetMuteFlag(mEasData, trackNum, muteFlag, sync);
+}
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::triggerClip(int clipId)
+{
+ ALOGV("JetPlayer::triggerClip clipId=%d", clipId);
+ Mutex::Autolock lock(mMutex);
+ return JET_TriggerClip(mEasData, clipId);
+}
+
+//-------------------------------------------------------------------------------------------------
+int JetPlayer::clearQueue()
+{
+ ALOGV("JetPlayer::clearQueue");
+ Mutex::Autolock lock(mMutex);
+ return JET_Clear_Queue(mEasData);
+}
+
+//-------------------------------------------------------------------------------------------------
+void JetPlayer::dump()
+{
+}
+
+void JetPlayer::dumpJetStatus(S_JET_STATUS* pJetStatus)
+{
+ if (pJetStatus!=NULL)
+ ALOGV(">> current JET player status: userID=%d segmentRepeatCount=%d numQueuedSegments=%d "
+ "paused=%d",
+ pJetStatus->currentUserID, pJetStatus->segmentRepeatCount,
+ pJetStatus->numQueuedSegments, pJetStatus->paused);
+ else
+ ALOGE(">> JET player status is NULL");
+}
+
+
+} // end namespace android
diff --git a/media/jni/JetPlayer.h b/media/jni/JetPlayer.h
new file mode 100644
index 0000000..bb569bc
--- /dev/null
+++ b/media/jni/JetPlayer.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JETPLAYER_H_
+#define JETPLAYER_H_
+
+#include <utils/threads.h>
+
+#include <libsonivox/jet.h>
+#include <libsonivox/eas_types.h>
+#include <media/AudioTrack.h>
+#include <media/MidiIoWrapper.h>
+
+
+namespace android {
+
+typedef void (*jetevent_callback)(int eventType, int val1, int val2, void *cookie);
+
+class JetPlayer {
+
+public:
+
+ // to keep in sync with the JetPlayer class constants
+ // defined in frameworks/base/media/java/android/media/JetPlayer.java
+ static const int JET_EVENT = 1;
+ static const int JET_USERID_UPDATE = 2;
+ static const int JET_NUMQUEUEDSEGMENT_UPDATE = 3;
+ static const int JET_PAUSE_UPDATE = 4;
+
+ JetPlayer(void *javaJetPlayer,
+ int maxTracks = 32,
+ int trackBufferSize = 1200);
+ ~JetPlayer();
+ int init();
+ int release();
+
+ int loadFromFile(const char* url);
+ int loadFromFD(const int fd, const long long offset, const long long length);
+ int closeFile();
+ int play();
+ int pause();
+ int queueSegment(int segmentNum, int libNum, int repeatCount, int transpose,
+ EAS_U32 muteFlags, EAS_U8 userID);
+ int setMuteFlags(EAS_U32 muteFlags, bool sync);
+ int setMuteFlag(int trackNum, bool muteFlag, bool sync);
+ int triggerClip(int clipId);
+ int clearQueue();
+
+ void setEventCallback(jetevent_callback callback);
+
+ int getMaxTracks() { return mMaxTracks; };
+
+
+private:
+ int render();
+ void fireUpdateOnStatusChange();
+ void fireEventsFromJetQueue();
+
+ JetPlayer() {} // no default constructor
+ void dump();
+ void dumpJetStatus(S_JET_STATUS* pJetStatus);
+
+ jetevent_callback mEventCallback;
+
+ void* mJavaJetPlayerRef;
+ Mutex mMutex; // mutex to sync the render and playback thread with the JET calls
+ pid_t mTid;
+ Condition mCondition;
+ volatile bool mRender;
+ bool mPaused;
+
+ EAS_STATE mState;
+ int* mMemFailedVar;
+
+ int mMaxTracks; // max number of MIDI tracks, usually 32
+ EAS_DATA_HANDLE mEasData;
+ MidiIoWrapper* mIoWrapper;
+ EAS_PCM* mAudioBuffer;// EAS renders the MIDI data into this buffer,
+ sp<AudioTrack> mAudioTrack; // and we play it in this audio track
+ int mTrackBufferSize;
+ S_JET_STATUS mJetStatus;
+ S_JET_STATUS mPreviousJetStatus;
+
+ class JetPlayerThread : public Thread {
+ public:
+ JetPlayerThread(JetPlayer *player) : mPlayer(player) {
+ }
+
+ protected:
+ virtual ~JetPlayerThread() {}
+
+ private:
+ JetPlayer *mPlayer;
+
+ bool threadLoop() {
+ int result;
+ result = mPlayer->render();
+ return false;
+ }
+
+ JetPlayerThread(const JetPlayerThread &);
+ JetPlayerThread &operator=(const JetPlayerThread &);
+ };
+
+ sp<JetPlayerThread> mThread;
+
+}; // end class JetPlayer
+
+} // end namespace android
+
+
+
+#endif /*JETPLAYER_H_*/
diff --git a/core/jni/android_media_JetPlayer.cpp b/media/jni/android_media_JetPlayer.cpp
similarity index 99%
rename from core/jni/android_media_JetPlayer.cpp
rename to media/jni/android_media_JetPlayer.cpp
index da116bf..8a05f85 100644
--- a/core/jni/android_media_JetPlayer.cpp
+++ b/media/jni/android_media_JetPlayer.cpp
@@ -27,7 +27,7 @@
#include "core_jni_helpers.h"
#include <utils/Log.h>
-#include <media/JetPlayer.h>
+#include "JetPlayer.h"
using namespace android;
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index d24edc7..ec0ce87 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1436,6 +1436,7 @@
}
extern int register_android_media_ImageReader(JNIEnv *env);
extern int register_android_media_ImageWriter(JNIEnv *env);
+extern int register_android_media_JetPlayer(JNIEnv *env);
extern int register_android_media_Crypto(JNIEnv *env);
extern int register_android_media_Drm(JNIEnv *env);
extern int register_android_media_Descrambler(JNIEnv *env);
@@ -1475,6 +1476,11 @@
goto bail;
}
+ if (register_android_media_JetPlayer(env) < 0) {
+ ALOGE("ERROR: JetPlayer native registration failed");
+ goto bail;
+ }
+
if (register_android_media_MediaPlayer(env) < 0) {
ALOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 5fd5c4b..b3804c4 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -30,6 +30,7 @@
import static android.net.ConnectivityManager.TETHERING_INVALID;
import static android.net.ConnectivityManager.TETHERING_USB;
import static android.net.ConnectivityManager.TETHERING_WIFI;
+import static android.net.ConnectivityManager.TETHERING_WIFI_P2P;
import static android.net.ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL;
@@ -77,6 +78,9 @@
import android.net.util.SharedLog;
import android.net.util.VersionedBroadcastListener;
import android.net.wifi.WifiManager;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pInfo;
+import android.net.wifi.p2p.WifiP2pManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -290,6 +294,7 @@
filter.addAction(CONNECTIVITY_ACTION);
filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mContext.registerReceiver(mStateReceiver, filter, null, handler);
filter = new IntentFilter();
@@ -354,6 +359,8 @@
if (cfg.isWifi(iface)) {
return TETHERING_WIFI;
+ } else if (cfg.isWifiP2p(iface)) {
+ return TETHERING_WIFI_P2P;
} else if (cfg.isUsb(iface)) {
return TETHERING_USB;
} else if (cfg.isBluetooth(iface)) {
@@ -527,6 +534,7 @@
public void untetherAll() {
stopTethering(TETHERING_WIFI);
+ stopTethering(TETHERING_WIFI_P2P);
stopTethering(TETHERING_USB);
stopTethering(TETHERING_BLUETOOTH);
}
@@ -713,6 +721,8 @@
handleConnectivityAction(intent);
} else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
handleWifiApAction(intent);
+ } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
+ handleWifiP2pAction(intent);
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
mLog.log("OBSERVED configuration changed");
updateConfiguration();
@@ -789,6 +799,39 @@
}
}
}
+
+ private void handleWifiP2pAction(Intent intent) {
+ if (mConfig.isWifiP2pLegacyTetheringMode()) return;
+
+ final WifiP2pInfo p2pInfo =
+ (WifiP2pInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO);
+ final WifiP2pGroup group =
+ (WifiP2pGroup) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP);
+
+ if (VDBG) {
+ Log.d(TAG, "WifiP2pAction: P2pInfo: " + p2pInfo + " Group: " + group);
+ }
+
+ if (p2pInfo == null) return;
+ // When a p2p group is disconnected, p2pInfo would be cleared.
+ // group is still valid for detecting whether this device is group owner.
+ if (group == null || !group.isGroupOwner()
+ || TextUtils.isEmpty(group.getInterface())) return;
+
+ synchronized (Tethering.this.mPublicSync) {
+ // Enter below only if this device is Group Owner with a valid interface.
+ if (p2pInfo.groupFormed) {
+ TetherState tetherState = mTetherStates.get(group.getInterface());
+ if (tetherState == null
+ || (tetherState.lastState != IpServer.STATE_TETHERED
+ && tetherState.lastState != IpServer.STATE_LOCAL_ONLY)) {
+ enableWifiIpServingLocked(group.getInterface(), IFACE_IP_MODE_LOCAL_ONLY);
+ }
+ } else {
+ disableWifiP2pIpServingLocked(group.getInterface());
+ }
+ }
+ }
}
@VisibleForTesting
@@ -823,14 +866,11 @@
}
}
- private void disableWifiIpServingLocked(String ifname, int apState) {
- mLog.log("Canceling WiFi tethering request - AP_STATE=" + apState);
-
- // Regardless of whether we requested this transition, the AP has gone
- // down. Don't try to tether again unless we're requested to do so.
- // TODO: Remove this altogether, once Wi-Fi reliably gives us an
- // interface name with every broadcast.
- mWifiTetherRequested = false;
+ private void disableWifiIpServingLockedCommon(int tetheringType, String ifname, int apState) {
+ mLog.log("Canceling WiFi tethering request -"
+ + " type=" + tetheringType
+ + " interface=" + ifname
+ + " state=" + apState);
if (!TextUtils.isEmpty(ifname)) {
final TetherState ts = mTetherStates.get(ifname);
@@ -842,7 +882,7 @@
for (int i = 0; i < mTetherStates.size(); i++) {
final IpServer ipServer = mTetherStates.valueAt(i).ipServer;
- if (ipServer.interfaceType() == TETHERING_WIFI) {
+ if (ipServer.interfaceType() == tetheringType) {
ipServer.unwanted();
return;
}
@@ -853,6 +893,20 @@
: "specified interface: " + ifname));
}
+ private void disableWifiIpServingLocked(String ifname, int apState) {
+ // Regardless of whether we requested this transition, the AP has gone
+ // down. Don't try to tether again unless we're requested to do so.
+ // TODO: Remove this altogether, once Wi-Fi reliably gives us an
+ // interface name with every broadcast.
+ mWifiTetherRequested = false;
+
+ disableWifiIpServingLockedCommon(TETHERING_WIFI, ifname, apState);
+ }
+
+ private void disableWifiP2pIpServingLocked(String ifname) {
+ disableWifiIpServingLockedCommon(TETHERING_WIFI_P2P, ifname, /* dummy */ 0);
+ }
+
private void enableWifiIpServingLocked(String ifname, int wifiIpMode) {
// Map wifiIpMode values to IpServer.Callback serving states, inferring
// from mWifiTetherRequested as a final "best guess".
@@ -870,7 +924,7 @@
}
if (!TextUtils.isEmpty(ifname)) {
- maybeTrackNewInterfaceLocked(ifname, TETHERING_WIFI);
+ maybeTrackNewInterfaceLocked(ifname);
changeInterfaceState(ifname, ipServingMode);
} else {
mLog.e(String.format(
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 1907892..a1b94ca 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -28,6 +28,7 @@
import static com.android.internal.R.array.config_tether_dhcp_range;
import static com.android.internal.R.array.config_tether_upstream_types;
import static com.android.internal.R.array.config_tether_usb_regexs;
+import static com.android.internal.R.array.config_tether_wifi_p2p_regexs;
import static com.android.internal.R.array.config_tether_wifi_regexs;
import static com.android.internal.R.bool.config_tether_upstream_automatic;
import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
@@ -85,6 +86,7 @@
public final String[] tetherableUsbRegexs;
public final String[] tetherableWifiRegexs;
+ public final String[] tetherableWifiP2pRegexs;
public final String[] tetherableBluetoothRegexs;
public final boolean isDunRequired;
public final boolean chooseUpstreamAutomatically;
@@ -110,6 +112,7 @@
// us an interface name. Careful consideration needs to be given to
// implications for Settings and for provisioning checks.
tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs);
+ tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs);
tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
isDunRequired = checkDunRequired(ctx, subId);
@@ -138,6 +141,15 @@
return matchesDownstreamRegexs(iface, tetherableWifiRegexs);
}
+ /** Check whether this interface is Wifi P2P interface. */
+ public boolean isWifiP2p(String iface) {
+ return matchesDownstreamRegexs(iface, tetherableWifiP2pRegexs);
+ }
+
+ public boolean isWifiP2pLegacyTetheringMode() {
+ return (tetherableWifiP2pRegexs == null || tetherableWifiP2pRegexs.length == 0);
+ }
+
public boolean isBluetooth(String iface) {
return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
}
@@ -152,6 +164,7 @@
dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
+ dumpStringArray(pw, "tetherableWifiP2pRegexs", tetherableWifiP2pRegexs);
dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs);
pw.print("isDunRequired: ");
@@ -178,6 +191,7 @@
sj.add(String.format("subId:%d", subId));
sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs)));
sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs)));
+ sj.add(String.format("tetherableWifiP2pRegexs:%s", makeString(tetherableWifiP2pRegexs)));
sj.add(String.format("tetherableBluetoothRegexs:%s",
makeString(tetherableBluetoothRegexs)));
sj.add(String.format("isDunRequired:%s", isDunRequired));
diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java
index 6a6a130..3d79bba 100644
--- a/services/net/java/android/net/ip/IpServer.java
+++ b/services/net/java/android/net/ip/IpServer.java
@@ -93,6 +93,8 @@
private static final int USB_PREFIX_LENGTH = 24;
private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1";
private static final int WIFI_HOST_IFACE_PREFIX_LENGTH = 24;
+ private static final String WIFI_P2P_IFACE_ADDR = "192.168.49.1";
+ private static final int WIFI_P2P_IFACE_PREFIX_LENGTH = 24;
// TODO: have PanService use some visible version of this constant
private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1";
@@ -403,6 +405,9 @@
} else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
ipAsString = getRandomWifiIPv4Address();
prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
+ } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI_P2P) {
+ ipAsString = WIFI_P2P_IFACE_ADDR;
+ prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH;
} else {
// BT configures the interface elsewhere: only start DHCP.
final Inet4Address srvAddr = (Inet4Address) numericToInetAddress(BLUETOOTH_IFACE_ADDR);
diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java
index 05912e8..b6ccebb 100644
--- a/tests/net/java/android/net/ip/IpServerTest.java
+++ b/tests/net/java/android/net/ip/IpServerTest.java
@@ -19,11 +19,13 @@
import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
import static android.net.ConnectivityManager.TETHERING_USB;
import static android.net.ConnectivityManager.TETHERING_WIFI;
+import static android.net.ConnectivityManager.TETHERING_WIFI_P2P;
import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.ip.IpServer.STATE_AVAILABLE;
+import static android.net.ip.IpServer.STATE_LOCAL_ONLY;
import static android.net.ip.IpServer.STATE_TETHERED;
import static android.net.ip.IpServer.STATE_UNAVAILABLE;
import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
@@ -256,6 +258,23 @@
}
@Test
+ public void canBeTetheredAsWifiP2p() throws Exception {
+ initStateMachine(TETHERING_WIFI_P2P);
+
+ dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
+ InOrder inOrder = inOrder(mCallback, mNMService);
+ inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
+ inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+ inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+ inOrder.verify(mCallback).updateInterfaceState(
+ mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
+ inOrder.verify(mCallback).updateLinkProperties(
+ eq(mIpServer), mLinkPropertiesCaptor.capture());
+ assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
+ verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ }
+
+ @Test
public void handlesFirstUpstreamChange() throws Exception {
initTetheredStateMachine(TETHERING_BLUETOOTH, null);
@@ -419,6 +438,14 @@
}
@Test
+ public void startsDhcpServerOnWifiP2p() throws Exception {
+ initTetheredStateMachine(TETHERING_WIFI_P2P, UPSTREAM_IFACE);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+
+ assertDhcpStarted(new IpPrefix("192.168.49.0/24"));
+ }
+
+ @Test
public void doesNotStartDhcpServerIfDisabled() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */);
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index c030c3e..5f62c08 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -25,8 +25,10 @@
import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER;
import static android.net.ConnectivityManager.TETHERING_USB;
import static android.net.ConnectivityManager.TETHERING_WIFI;
+import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -90,6 +92,9 @@
import android.net.util.SharedLog;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pInfo;
+import android.net.wifi.p2p.WifiP2pManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.INetworkManagementService;
@@ -140,6 +145,7 @@
private static final String TEST_XLAT_MOBILE_IFNAME = "v4-test_rmnet_data0";
private static final String TEST_USB_IFNAME = "test_rndis0";
private static final String TEST_WLAN_IFNAME = "test_wlan0";
+ private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
@@ -216,9 +222,10 @@
assertTrue("Non-mocked interface " + ifName,
ifName.equals(TEST_USB_IFNAME)
|| ifName.equals(TEST_WLAN_IFNAME)
- || ifName.equals(TEST_MOBILE_IFNAME));
+ || ifName.equals(TEST_MOBILE_IFNAME)
+ || ifName.equals(TEST_P2P_IFNAME));
final String[] ifaces = new String[] {
- TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME };
+ TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME, TEST_P2P_IFNAME};
return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET,
MacAddress.ALL_ZEROS_ADDRESS);
}
@@ -361,6 +368,8 @@
.thenReturn(new String[] { "test_rndis\\d" });
when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
.thenReturn(new String[]{ "test_wlan\\d" });
+ when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+ .thenReturn(new String[]{ "test_p2p-p2p\\d-.*" });
when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
.thenReturn(new String[0]);
when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
@@ -369,7 +378,7 @@
.thenReturn(false);
when(mNMService.listInterfaces())
.thenReturn(new String[] {
- TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME});
+ TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME});
when(mNMService.getInterfaceConfig(anyString()))
.thenReturn(new InterfaceConfiguration());
when(mRouterAdvertisementDaemon.start())
@@ -423,6 +432,31 @@
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
+ private static final String[] P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST = {
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_WIFI_STATE
+ };
+
+ private void sendWifiP2pConnectionChanged(
+ boolean isGroupFormed, boolean isGroupOwner, String ifname) {
+ WifiP2pInfo p2pInfo = new WifiP2pInfo();
+ p2pInfo.groupFormed = isGroupFormed;
+ p2pInfo.isGroupOwner = isGroupOwner;
+
+ NetworkInfo networkInfo = new NetworkInfo(TYPE_WIFI_P2P, 0, null, null);
+
+ WifiP2pGroup group = new WifiP2pGroup();
+ group.setIsGroupOwner(isGroupOwner);
+ group.setInterface(ifname);
+
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, p2pInfo);
+ intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, networkInfo);
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group);
+ mServiceContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL,
+ P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST);
+ }
+
private void sendUsbBroadcast(boolean connected, boolean configured, boolean rndisFunction) {
final Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
intent.putExtra(USB_CONNECTED, connected);
@@ -436,11 +470,11 @@
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
- private void verifyInterfaceServingModeStarted() throws Exception {
- verify(mNMService, times(1)).getInterfaceConfig(TEST_WLAN_IFNAME);
+ private void verifyInterfaceServingModeStarted(String ifname) throws Exception {
+ verify(mNMService, times(1)).getInterfaceConfig(ifname);
verify(mNMService, times(1))
- .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
- verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME);
+ .setInterfaceConfig(eq(ifname), any(InterfaceConfiguration.class));
+ verify(mNMService, times(1)).tetherInterface(ifname);
}
private void verifyTetheringBroadcast(String ifname, String whichExtra) {
@@ -530,7 +564,7 @@
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
mLooper.dispatchAll();
- verifyInterfaceServingModeStarted();
+ verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
verify(mNMService, times(1)).setIpForwardingEnabled(true);
verify(mNMService, times(1)).startTethering(any(String[].class));
@@ -542,8 +576,9 @@
verifyNoMoreInteractions(mWifiManager);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
- // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
- assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls);
+ // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
+ // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
+ assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls);
// Emulate externally-visible WifiManager effects, when hotspot mode
// is being torn down.
@@ -552,9 +587,9 @@
mLooper.dispatchAll();
verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
- // TODO: Why is {g,s}etInterfaceConfig() called more than once?
- verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME);
- verify(mNMService, atLeastOnce())
+ // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
+ verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME);
+ verify(mNMService, times(2))
.setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
verify(mNMService, times(1)).stopTethering();
verify(mNMService, times(1)).setIpForwardingEnabled(false);
@@ -770,7 +805,7 @@
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
mLooper.dispatchAll();
- verifyInterfaceServingModeStarted();
+ verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
verify(mNMService, times(1)).setIpForwardingEnabled(true);
verify(mNMService, times(1)).startTethering(any(String[].class));
@@ -785,8 +820,9 @@
// In tethering mode, in the default configuration, an explicit request
// for a mobile network is also made.
verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
- // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
- assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls);
+ // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
+ // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
+ assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls);
/////
// We do not currently emulate any upstream being found.
@@ -809,7 +845,7 @@
mLooper.dispatchAll();
verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
- // TODO: Why is {g,s}etInterfaceConfig() called more than once?
+ // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME);
verify(mNMService, atLeastOnce())
.setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
@@ -857,8 +893,9 @@
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
- // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
- assertTrue(1 <= mTetheringDependencies.isTetheringSupportedCalls);
+ // There are 3 state change event:
+ // AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE.
+ assertEquals(3, mTetheringDependencies.isTetheringSupportedCalls);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
// This is called, but will throw.
verify(mNMService, times(1)).setIpForwardingEnabled(true);
@@ -1031,6 +1068,133 @@
assertEquals(fakeSubId, newConfig.subId);
}
+ private void workingWifiP2pGroupOwner(
+ boolean emulateInterfaceStatusChanged) throws Exception {
+ if (emulateInterfaceStatusChanged) {
+ mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
+ }
+ sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
+ mLooper.dispatchAll();
+
+ verifyInterfaceServingModeStarted(TEST_P2P_IFNAME);
+ verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_AVAILABLE_TETHER);
+ verify(mNMService, times(1)).setIpForwardingEnabled(true);
+ verify(mNMService, times(1)).startTethering(any(String[].class));
+ verifyNoMoreInteractions(mNMService);
+ verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
+ verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
+ // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
+ // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
+ assertEquals(2, mTetheringDependencies.isTetheringSupportedCalls);
+
+ assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastTetherError(TEST_P2P_IFNAME));
+
+ // Emulate externally-visible WifiP2pManager effects, when wifi p2p group
+ // is being removed.
+ sendWifiP2pConnectionChanged(false, true, TEST_P2P_IFNAME);
+ mTethering.interfaceRemoved(TEST_P2P_IFNAME);
+ mLooper.dispatchAll();
+
+ verify(mNMService, times(1)).untetherInterface(TEST_P2P_IFNAME);
+ // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
+ verify(mNMService, times(2)).getInterfaceConfig(TEST_P2P_IFNAME);
+ verify(mNMService, times(2))
+ .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
+ verify(mNMService, times(1)).stopTethering();
+ verify(mNMService, times(1)).setIpForwardingEnabled(false);
+ verify(mUpstreamNetworkMonitor, never()).getCurrentPreferredUpstream();
+ verify(mUpstreamNetworkMonitor, never()).selectPreferredUpstreamType(any());
+ verifyNoMoreInteractions(mNMService);
+ // Asking for the last error after the per-interface state machine
+ // has been reaped yields an unknown interface error.
+ assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
+ }
+
+ private void workingWifiP2pGroupClient(
+ boolean emulateInterfaceStatusChanged) throws Exception {
+ if (emulateInterfaceStatusChanged) {
+ mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
+ }
+ sendWifiP2pConnectionChanged(true, false, TEST_P2P_IFNAME);
+ mLooper.dispatchAll();
+
+ verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
+ verify(mNMService, never())
+ .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
+ verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME);
+ verify(mNMService, never()).setIpForwardingEnabled(true);
+ verify(mNMService, never()).startTethering(any(String[].class));
+
+ // Emulate externally-visible WifiP2pManager effects, when wifi p2p group
+ // is being removed.
+ sendWifiP2pConnectionChanged(false, false, TEST_P2P_IFNAME);
+ mTethering.interfaceRemoved(TEST_P2P_IFNAME);
+ mLooper.dispatchAll();
+
+ verify(mNMService, never()).untetherInterface(TEST_P2P_IFNAME);
+ verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
+ verify(mNMService, never())
+ .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
+ verify(mNMService, never()).stopTethering();
+ verify(mNMService, never()).setIpForwardingEnabled(false);
+ verifyNoMoreInteractions(mNMService);
+ // Asking for the last error after the per-interface state machine
+ // has been reaped yields an unknown interface error.
+ assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
+ }
+
+ @Test
+ public void workingWifiP2pGroupOwnerWithIfaceChanged() throws Exception {
+ workingWifiP2pGroupOwner(true);
+ }
+
+ @Test
+ public void workingWifiP2pGroupOwnerSansIfaceChanged() throws Exception {
+ workingWifiP2pGroupOwner(false);
+ }
+
+ private void workingWifiP2pGroupOwnerLegacyMode(
+ boolean emulateInterfaceStatusChanged) throws Exception {
+ // change to legacy mode and update tethering information by chaning SIM
+ when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+ .thenReturn(new String[]{});
+ final int fakeSubId = 1234;
+ mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
+
+ if (emulateInterfaceStatusChanged) {
+ mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true);
+ }
+ sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
+ mLooper.dispatchAll();
+
+ verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
+ verify(mNMService, never())
+ .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
+ verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME);
+ verify(mNMService, never()).setIpForwardingEnabled(true);
+ verify(mNMService, never()).startTethering(any(String[].class));
+ assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
+ }
+ @Test
+ public void workingWifiP2pGroupOwnerLegacyModeWithIfaceChanged() throws Exception {
+ workingWifiP2pGroupOwnerLegacyMode(true);
+ }
+
+ @Test
+ public void workingWifiP2pGroupOwnerLegacyModeSansIfaceChanged() throws Exception {
+ workingWifiP2pGroupOwnerLegacyMode(false);
+ }
+
+ @Test
+ public void workingWifiP2pGroupClientWithIfaceChanged() throws Exception {
+ workingWifiP2pGroupClient(true);
+ }
+
+ @Test
+ public void workingWifiP2pGroupClientSansIfaceChanged() throws Exception {
+ workingWifiP2pGroupClient(false);
+ }
+
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
}