diff options
| -rw-r--r-- | core/java/android/widget/PopupWindow.java | 30 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 60 | ||||
| -rw-r--r-- | voip/java/android/net/sip/SipManager.java | 22 | ||||
| -rw-r--r-- | voip/java/com/android/server/sip/SipService.java | 80 | ||||
| -rw-r--r-- | voip/jni/rtp/AudioGroup.cpp | 58 |
5 files changed, 186 insertions, 64 deletions
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 7fe6190..ed15e25 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -86,6 +86,7 @@ public class PopupWindow { private boolean mOutsideTouchable = false; private boolean mClippingEnabled = true; private boolean mSplitTouchEnabled; + private boolean mLayoutInScreen; private OnTouchListener mTouchInterceptor; @@ -596,6 +597,29 @@ public class PopupWindow { } /** + * <p>Indicates whether the popup window will be forced into using absolute screen coordinates + * for positioning.</p> + * + * @return true if the window will always be positioned in screen coordinates. + * @hide + */ + public boolean isLayoutInScreenEnabled() { + return mLayoutInScreen; + } + + /** + * <p>Allows the popup window to force the flag + * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}, overriding default behavior. + * This will cause the popup to be positioned in absolute screen coordinates.</p> + * + * @param enabled true if the popup should always be positioned in screen coordinates + * @hide + */ + public void setLayoutInScreenEnabled(boolean enabled) { + mLayoutInScreen = enabled; + } + + /** * <p>Change the width and height measure specs that are given to the * window manager by the popup. By default these are 0, meaning that * the current width or height is requested as an explicit size from @@ -899,7 +923,8 @@ public class PopupWindow { WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | - WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | + WindowManager.LayoutParams.FLAG_SPLIT_TOUCH); if(mIgnoreCheekPress) { curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; } @@ -923,6 +948,9 @@ public class PopupWindow { if (mSplitTouchEnabled) { curFlags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; } + if (mLayoutInScreen) { + curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + } return curFlags; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 6278192..8291d57 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -3768,6 +3768,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mError != null) { hideError(); } + + hideControllers(); } @Override @@ -4118,6 +4120,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ canvas.restore(); + + if (mInsertionPointCursorController != null && + mInsertionPointCursorController.isShowing()) { + mInsertionPointCursorController.updatePosition(); + } + if (mSelectionModifierCursorController != null && + mSelectionModifierCursorController.isShowing()) { + mSelectionModifierCursorController.updatePosition(); + } } @Override @@ -4736,6 +4747,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mInputMethodState != null) { mInputMethodState.mExtracting = req; } + hideControllers(); } /** @@ -6266,7 +6278,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(buffer, start, before, after); onTextChanged(buffer, start, before, after); - hideControllers(); + + // Hide the controller if the amount of content changed + if (before != after) { + hideControllers(); + } } /** @@ -6605,11 +6621,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mInputContentType != null) { mInputContentType.enterDown = false; } + hideControllers(); } startStopMarquee(hasWindowFocus); } + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + if (visibility != VISIBLE) { + hideControllers(); + } + } + /** * Use {@link BaseInputConnection#removeComposingSpans * BaseInputConnection.removeComposingSpans()} to remove any IME composing @@ -6679,8 +6704,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (hasSelection()) { startTextSelectionMode(); - } else if (mInsertionPointCursorController != null) { - mInsertionPointCursorController.show(); } } } @@ -7645,6 +7668,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mPositionY; private CursorController mController; private boolean mIsDragging; + private int mOffsetX; + private int mOffsetY; public HandleView(CursorController controller, Drawable handle) { super(TextView.this.mContext); @@ -7653,6 +7678,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mContainer = new PopupWindow(TextView.this.mContext, null, com.android.internal.R.attr.textSelectHandleWindowStyle); mContainer.setSplitTouchEnabled(true); + mContainer.setClippingEnabled(false); + mContainer.setLayoutInScreenEnabled(true); } @Override @@ -7690,19 +7717,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int compoundPaddingRight = getCompoundPaddingRight(); final TextView hostView = TextView.this; - final int right = hostView.mRight; - final int left = hostView.mLeft; - final int bottom = hostView.mBottom; - final int top = hostView.mTop; + final int handleWidth = mDrawable.getIntrinsicWidth(); + final int left = 0; + final int right = hostView.getWidth(); + final int top = 0; + final int bottom = hostView.getHeight(); - final int clipLeft = left + compoundPaddingLeft; + final int clipLeft = left + compoundPaddingLeft - (int) (handleWidth * 0.75f); final int clipTop = top + extendedPaddingTop; - final int clipRight = right - compoundPaddingRight; + final int clipRight = right - compoundPaddingRight + (int) (handleWidth * 0.25f); final int clipBottom = bottom - extendedPaddingBottom; - final int handleWidth = mDrawable.getIntrinsicWidth(); - return mPositionX >= clipLeft - handleWidth * 0.75f && - mPositionX <= clipRight + handleWidth * 0.25f && + return mPositionX >= clipLeft && mPositionX <= clipRight && mPositionY >= clipTop && mPositionY <= clipBottom; } @@ -7741,6 +7767,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public boolean onTouchEvent(MotionEvent ev) { switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: + mOffsetX = (int) (ev.getX() - mDrawable.getIntrinsicWidth() / 2.f + 0.5f); + mOffsetY = (int) (ev.getY() - mDrawable.getIntrinsicHeight() / 2.f + 0.5f); mIsDragging = true; break; @@ -7749,8 +7777,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final float rawY = ev.getRawY(); final int[] coords = mTempCoords; TextView.this.getLocationOnScreen(coords); - final int x = (int) (rawX - coords[0] + 0.5f); - final int y = (int) (rawY - coords[1] + 0.5f); + final int x = (int) (rawX - coords[0] + 0.5f) - mOffsetX; + final int y = (int) (rawY - coords[1] + 0.5f) - + (int) (mDrawable.getIntrinsicHeight() * 0.8f) - mOffsetY; + mController.updatePosition(this, x, y); break; @@ -8059,7 +8089,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int previousLine = layout.getLineForOffset(previousOffset); final int previousLineTop = layout.getLineTop(previousLine); final int previousLineBottom = layout.getLineBottom(previousLine); - final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2; + final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 6; // If new line is just before or after previous line and y position is less than // hysteresisThreshold away from previous line, keep cursor on previous line. diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java index 52f5716..a589fe9 100644 --- a/voip/java/android/net/sip/SipManager.java +++ b/voip/java/android/net/sip/SipManager.java @@ -173,7 +173,7 @@ public class SipManager { SipRegistrationListener listener) throws SipException { try { mSipService.open3(localProfile, incomingCallBroadcastAction, - createRelay(listener)); + createRelay(listener, localProfile.getUriString())); } catch (RemoteException e) { throw new SipException("open()", e); } @@ -191,7 +191,7 @@ public class SipManager { SipRegistrationListener listener) throws SipException { try { mSipService.setRegistrationListener( - localProfileUri, createRelay(listener)); + localProfileUri, createRelay(listener, localProfileUri)); } catch (RemoteException e) { throw new SipException("setRegistrationListener()", e); } @@ -425,8 +425,8 @@ public class SipManager { public void register(SipProfile localProfile, int expiryTime, SipRegistrationListener listener) throws SipException { try { - ISipSession session = mSipService.createSession( - localProfile, createRelay(listener)); + ISipSession session = mSipService.createSession(localProfile, + createRelay(listener, localProfile.getUriString())); session.register(expiryTime); } catch (RemoteException e) { throw new SipException("register()", e); @@ -446,8 +446,8 @@ public class SipManager { public void unregister(SipProfile localProfile, SipRegistrationListener listener) throws SipException { try { - ISipSession session = mSipService.createSession( - localProfile, createRelay(listener)); + ISipSession session = mSipService.createSession(localProfile, + createRelay(listener, localProfile.getUriString())); session.unregister(); } catch (RemoteException e) { throw new SipException("unregister()", e); @@ -475,8 +475,8 @@ public class SipManager { } private static ISipSessionListener createRelay( - SipRegistrationListener listener) { - return ((listener == null) ? null : new ListenerRelay(listener)); + SipRegistrationListener listener, String uri) { + return ((listener == null) ? null : new ListenerRelay(listener, uri)); } /** @@ -512,16 +512,18 @@ public class SipManager { private static class ListenerRelay extends SipSessionAdapter { private SipRegistrationListener mListener; + private String mUri; // listener must not be null - public ListenerRelay(SipRegistrationListener listener) { + public ListenerRelay(SipRegistrationListener listener, String uri) { mListener = listener; + mUri = uri; } private String getUri(ISipSession session) { try { return ((session == null) - ? "no session" + ? mUri : session.getLocalProfile().getUriString()); } catch (RemoteException e) { throw new RuntimeException(e); diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java index 130fe9f..aff1439 100644 --- a/voip/java/com/android/server/sip/SipService.java +++ b/voip/java/com/android/server/sip/SipService.java @@ -39,6 +39,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -49,6 +50,7 @@ import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; @@ -119,17 +121,19 @@ public final class SipService extends ISipService.Stub { } public synchronized SipProfile[] getListOfProfiles() { - SipProfile[] profiles = new SipProfile[mSipGroups.size()]; - int i = 0; + boolean isCallerRadio = isCallerRadio(); + ArrayList<SipProfile> profiles = new ArrayList<SipProfile>(); for (SipSessionGroupExt group : mSipGroups.values()) { - profiles[i++] = group.getLocalProfile(); + if (isCallerRadio || isCallerCreator(group)) { + profiles.add(group.getLocalProfile()); + } } - return profiles; + return profiles.toArray(new SipProfile[profiles.size()]); } public void open(SipProfile localProfile) { localProfile.setCallingUid(Binder.getCallingUid()); - if (localProfile.getAutoRegistration()) { + if (localProfile.getAutoRegistration() && isCallerRadio()) { openToReceiveCalls(localProfile); } else { openToMakeCalls(localProfile); @@ -153,8 +157,14 @@ public final class SipService extends ISipService.Stub { String incomingCallBroadcastAction, ISipSessionListener listener) { localProfile.setCallingUid(Binder.getCallingUid()); if (TextUtils.isEmpty(incomingCallBroadcastAction)) { - throw new RuntimeException( - "empty broadcast action for incoming call"); + Log.w(TAG, "empty broadcast action for incoming call"); + return; + } + if (incomingCallBroadcastAction.equals( + SipManager.ACTION_SIP_INCOMING_CALL) && !isCallerRadio()) { + Log.w(TAG, "failed to open the profile; " + + "the action string is reserved"); + return; } if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " + incomingCallBroadcastAction + ": " + listener); @@ -171,29 +181,64 @@ public final class SipService extends ISipService.Stub { } } + 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); + } + public synchronized void close(String localProfileUri) { - SipSessionGroupExt group = mSipGroups.remove(localProfileUri); - if (group != null) { - notifyProfileRemoved(group.getLocalProfile()); - group.close(); - if (isWifiOn() && !anyOpened()) releaseWifiLock(); + SipSessionGroupExt group = mSipGroups.get(localProfileUri); + if (group == null) return; + if (!isCallerCreatorOrRadio(group)) { + Log.d(TAG, "only creator or radio can close this profile"); + return; } + + group = mSipGroups.remove(localProfileUri); + notifyProfileRemoved(group.getLocalProfile()); + group.close(); + if (isWifiOn() && !anyOpened()) releaseWifiLock(); } public synchronized boolean isOpened(String localProfileUri) { SipSessionGroupExt group = mSipGroups.get(localProfileUri); - return ((group != null) ? group.isOpened() : false); + if (group == null) return false; + if (isCallerCreatorOrRadio(group)) { + return group.isOpened(); + } else { + Log.i(TAG, "only creator or radio can query on the profile"); + return false; + } } public synchronized boolean isRegistered(String localProfileUri) { SipSessionGroupExt group = mSipGroups.get(localProfileUri); - return ((group != null) ? group.isRegistered() : false); + if (group == null) return false; + if (isCallerCreatorOrRadio(group)) { + return group.isRegistered(); + } else { + Log.i(TAG, "only creator or radio can query on the profile"); + return false; + } } public synchronized void setRegistrationListener(String localProfileUri, ISipSessionListener listener) { SipSessionGroupExt group = mSipGroups.get(localProfileUri); - if (group != null) group.setListener(listener); + if (group == null) return; + if (isCallerCreator(group)) { + group.setListener(listener); + } else { + Log.i(TAG, "only creator can set listener on the profile"); + } } public synchronized ISipSession createSession(SipProfile localProfile, @@ -234,6 +279,8 @@ public final class SipService extends ISipService.Stub { 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; } @@ -244,6 +291,9 @@ public final class SipService extends ISipService.Stub { 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.setIncomingCallBroadcastAction( incomingCallBroadcastAction); group.setListener(listener); diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp index f09edb6..a1caa1f 100644 --- a/voip/jni/rtp/AudioGroup.cpp +++ b/voip/jni/rtp/AudioGroup.cpp @@ -57,9 +57,9 @@ int gRandom = -1; // 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 256ms long and assume packet interval is 32ms or less. -// The first 64ms is the place where samples get mixed. The rest 192ms is the -// real jitter buffer. For a stream at 8000Hz it takes 4096 bytes. These numbers +// Currently we make it 512ms long and assume packet interval is 40ms or less. +// The first 80ms is the place where samples get mixed. The rest 432ms is the +// real jitter buffer. For a stream at 8000Hz it takes 8192 bytes. These numbers // are chosen by experiments and each of them can be adjusted as needed. // Other notes: @@ -69,7 +69,11 @@ int gRandom = -1; // 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 we might only support 8kHz and 16kHz. +// For the first release only 8000Hz is supported. + +#define BUFFER_SIZE 512 +#define HISTORY_SIZE 80 +#define MEASURE_PERIOD 2000 class AudioStream { @@ -111,6 +115,7 @@ private: int mBufferMask; int mBufferHead; int mBufferTail; + int mLatencyTimer; int mLatencyScore; uint16_t mSequence; @@ -159,12 +164,13 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, mInterval = mSampleCount / mSampleRate; // Allocate jitter buffer. - for (mBufferMask = 8192; mBufferMask < sampleRate; mBufferMask <<= 1); - mBufferMask >>= 2; + 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. @@ -340,29 +346,35 @@ void AudioStream::decode(int tick) } // Make sure mBufferHead and mBufferTail are reasonable. - if ((unsigned int)(tick + 256 - mBufferHead) > 1024) { - mBufferHead = tick - 64; + if ((unsigned int)(tick + BUFFER_SIZE - mBufferHead) > BUFFER_SIZE * 2) { + mBufferHead = tick - HISTORY_SIZE; mBufferTail = mBufferHead; } - if (tick - mBufferHead > 64) { + if (tick - mBufferHead > HISTORY_SIZE) { // Throw away outdated samples. - mBufferHead = tick - 64; + mBufferHead = tick - HISTORY_SIZE; if (mBufferTail - mBufferHead < 0) { mBufferTail = mBufferHead; } } - if (mBufferTail - tick <= 80) { - mLatencyScore = tick; - } else if (tick - mLatencyScore >= 5000) { - // Reset the jitter buffer to 40ms if the latency keeps larger than 80ms - // in the past 5s. This rarely happens, so let us just keep it simple. - LOGD("stream[%d] latency control", mSocket); - mBufferTail = tick + 40; + // Adjust the jitter buffer if the latency keeps larger than two times of the + // packet interval in the past two seconds. + int score = mBufferTail - tick - mInterval * 2; + if (mLatencyScore > score) { + mLatencyScore = score; + } + if (mLatencyScore <= 0) { + mLatencyTimer = tick; + mLatencyScore = score; + } else if (tick - mLatencyTimer >= MEASURE_PERIOD) { + LOGD("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); + mBufferTail -= mLatencyScore; + mLatencyTimer = tick; } - if (mBufferTail - mBufferHead > 256 - mInterval) { + if (mBufferTail - mBufferHead > BUFFER_SIZE - mInterval) { // Buffer overflow. Drop the packet. LOGD("stream[%d] buffer overflow", mSocket); recv(mSocket, &c, 1, MSG_DONTWAIT); @@ -413,17 +425,17 @@ void AudioStream::decode(int tick) } if (tick - mBufferTail > 0) { - // Buffer underrun. Reset the jitter buffer to 40ms. + // Buffer underrun. Reset the jitter buffer. LOGD("stream[%d] buffer underrun", mSocket); if (mBufferTail - mBufferHead <= 0) { - mBufferHead = tick + 40; + mBufferHead = tick + mInterval; mBufferTail = mBufferHead; } else { - int tail = (tick + 40) * mSampleRate; + int tail = (tick + mInterval) * mSampleRate; for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) { mBuffer[i & mBufferMask] = 0; } - mBufferTail = tick + 40; + mBufferTail = tick + mInterval; } } @@ -680,7 +692,7 @@ bool AudioGroup::NetworkThread::threadLoop() int count = 0; for (AudioStream *stream = chain; stream; stream = stream->mNext) { - if (!stream->mTick || tick - stream->mTick >= 0) { + if (tick - stream->mTick >= 0) { stream->encode(tick, chain); } if (deadline - stream->mTick > 0) { |
