From dba8a81d935cd5647343d3fd8341c0ed159c87b5 Mon Sep 17 00:00:00 2001 From: Jake Hamby Date: Tue, 28 Aug 2012 20:11:34 -0700 Subject: [PATCH 1/6] Fix CDMA decoding of multipart UTF-16 SMS messages. Recent changes to support CMAS over CDMA introduced a bug causing an exception to be thrown when decoding multipart UTF-16 encoded messages. This change fixes the exception by correctly subtracting the header size from the number of bytes to decode. It also adds more robust error handling to try to decode the maximum length possible instead of throwing an exception if the length is still larger than the user data length after subtracting the header. This also fixes a bug in the encoder, which was padding the UTF-16 user data to 16-bit alignment, which is incorrect (should be padded to an 8-bit boundary). The code happened to work because we always generated a UDH that was an even number of bytes (including length) so the padding was a no-op. The decoder works correctly. Bug: 6939151 Change-Id: Iba9e7156bd7df94e972963959a7ce1c78464f7f5 --- .../telephony/cdma/sms/BearerData.java | 48 ++++++------ .../telephony/cdma/sms/CdmaSmsTest.java | 73 +++++++++++++++++-- 2 files changed, 91 insertions(+), 30 deletions(-) diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index 5cacd23b8bf65..3d88f5088c2ad 100755 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -595,7 +595,6 @@ public final class BearerData { byte[] payload = encodeUtf16(uData.payloadStr); int udhBytes = udhData.length + 1; // Add length octet. int udhCodeUnits = (udhBytes + 1) / 2; - int udhPadding = udhBytes % 2; int payloadCodeUnits = payload.length / 2; uData.msgEncoding = UserData.ENCODING_UNICODE_16; uData.msgEncodingSet = true; @@ -603,7 +602,7 @@ public final class BearerData { uData.payload = new byte[uData.numFields * 2]; uData.payload[0] = (byte)udhData.length; System.arraycopy(udhData, 0, uData.payload, 1, udhData.length); - System.arraycopy(payload, 0, uData.payload, udhBytes + udhPadding, payload.length); + System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length); } private static void encodeEmsUserDataPayload(UserData uData) @@ -997,27 +996,37 @@ public final class BearerData { private static String decodeUtf8(byte[] data, int offset, int numFields) throws CodingException { - if (numFields < 0 || (numFields + offset) > data.length) { - throw new CodingException("UTF-8 decode failed: offset or length out of range"); - } - try { - return new String(data, offset, numFields, "UTF-8"); - } catch (java.io.UnsupportedEncodingException ex) { - throw new CodingException("UTF-8 decode failed: " + ex); - } + return decodeCharset(data, offset, numFields, 1, "UTF-8"); } private static String decodeUtf16(byte[] data, int offset, int numFields) throws CodingException { - int byteCount = numFields * 2; - if (byteCount < 0 || (byteCount + offset) > data.length) { - throw new CodingException("UTF-16 decode failed: offset or length out of range"); + // Subtract header and possible padding byte (at end) from num fields. + int padding = offset % 2; + numFields -= (offset + padding) / 2; + return decodeCharset(data, offset, numFields, 2, "utf-16be"); + } + + private static String decodeCharset(byte[] data, int offset, int numFields, int width, + String charset) throws CodingException + { + if (numFields < 0 || (numFields * width + offset) > data.length) { + // Try to decode the max number of characters in payload + int padding = offset % width; + int maxNumFields = (data.length - offset - padding) / width; + if (maxNumFields < 0) { + throw new CodingException(charset + " decode failed: offset out of range"); + } + Log.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = " + + numFields + " data.length = " + data.length + " maxNumFields = " + + maxNumFields); + numFields = maxNumFields; } try { - return new String(data, offset, byteCount, "utf-16be"); + return new String(data, offset, numFields * width, charset); } catch (java.io.UnsupportedEncodingException ex) { - throw new CodingException("UTF-16 decode failed: " + ex); + throw new CodingException(charset + " decode failed: " + ex); } } @@ -1073,14 +1082,7 @@ public final class BearerData { private static String decodeLatin(byte[] data, int offset, int numFields) throws CodingException { - if (numFields < 0 || (numFields + offset) > data.length) { - throw new CodingException("ISO-8859-1 decode failed: offset or length out of range"); - } - try { - return new String(data, offset, numFields, "ISO-8859-1"); - } catch (java.io.UnsupportedEncodingException ex) { - throw new CodingException("ISO-8859-1 decode failed: " + ex); - } + return decodeCharset(data, offset, numFields, 1, "ISO-8859-1"); } private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader) diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java index 58e73e0dabbcd..f1bc2688f6f78 100644 --- a/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java @@ -35,10 +35,21 @@ import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import java.util.ArrayList; +import java.util.Arrays; public class CdmaSmsTest extends AndroidTestCase { private final static String LOG_TAG = "XXX CdmaSmsTest XXX"; + // CJK ideographs, Hiragana, Katakana, full width letters, Cyrillic, etc. + private static final String sUnicodeChars = "\u4e00\u4e01\u4e02\u4e03" + + "\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d" + + "\u4e0e\u4e0f\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048" + + "\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8" + + "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18" + + "\uff70\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78" + + "\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408" + + "\u00a2\u00a9\u00ae\u2122"; + @SmallTest public void testCdmaSmsAddrParsing() throws Exception { CdmaSmsAddress addr = CdmaSmsAddress.parse("6502531000"); @@ -811,23 +822,51 @@ public class CdmaSmsTest extends AndroidTestCase { @SmallTest public void testUserDataHeaderWithEightCharMsg() throws Exception { + encodeDecodeAssertEquals("01234567", 2, 2, false); + } + + private void encodeDecodeAssertEquals(String payload, int index, int total, + boolean oddLengthHeader) throws Exception { BearerData bearerData = new BearerData(); bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER; bearerData.messageId = 55; - SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); - concatRef.refNumber = 0xEE; - concatRef.msgCount = 2; - concatRef.seqNumber = 2; - concatRef.isEightBits = true; SmsHeader smsHeader = new SmsHeader(); - smsHeader.concatRef = concatRef; + if (oddLengthHeader) { + // Odd length header to verify correct UTF-16 header padding + SmsHeader.MiscElt miscElt = new SmsHeader.MiscElt(); + miscElt.id = 0x27; // reserved for future use; ignored on decode + miscElt.data = new byte[]{0x12, 0x34}; + smsHeader.miscEltList.add(miscElt); + } else { + // Even length header normally generated for concatenated SMS. + SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); + concatRef.refNumber = 0xEE; + concatRef.msgCount = total; + concatRef.seqNumber = index; + concatRef.isEightBits = true; + smsHeader.concatRef = concatRef; + } + byte[] encodeHeader = SmsHeader.toByteArray(smsHeader); + if (oddLengthHeader) { + assertEquals(4, encodeHeader.length); // 5 bytes with UDH length + } else { + assertEquals(5, encodeHeader.length); // 6 bytes with UDH length + } UserData userData = new UserData(); - userData.payloadStr = "01234567"; + userData.payloadStr = payload; userData.userDataHeader = smsHeader; bearerData.userData = userData; byte[] encodedSms = BearerData.encode(bearerData); BearerData revBearerData = BearerData.decode(encodedSms); assertEquals(userData.payloadStr, revBearerData.userData.payloadStr); + assertTrue(revBearerData.hasUserDataHeader); + byte[] header = SmsHeader.toByteArray(revBearerData.userData.userDataHeader); + if (oddLengthHeader) { + assertEquals(4, header.length); // 5 bytes with UDH length + } else { + assertEquals(5, header.length); // 6 bytes with UDH length + } + assertTrue(Arrays.equals(encodeHeader, header)); } @SmallTest @@ -881,7 +920,27 @@ public class CdmaSmsTest extends AndroidTestCase { if (isCdmaPhone) { ArrayList fragments = android.telephony.SmsMessage.fragmentText(text2); assertEquals(3, fragments.size()); + + for (int i = 0; i < 3; i++) { + encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, false); + encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, true); + } } + // Test case for multi-part UTF-16 message. + String text3 = sUnicodeChars + sUnicodeChars + sUnicodeChars; + ted = SmsMessage.calculateLength(text3, false); + assertEquals(3, ted.msgCount); + assertEquals(189, ted.codeUnitCount); + assertEquals(3, ted.codeUnitSize); + if (isCdmaPhone) { + ArrayList fragments = android.telephony.SmsMessage.fragmentText(text3); + assertEquals(3, fragments.size()); + + for (int i = 0; i < 3; i++) { + encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, false); + encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, true); + } + } } } From 48c7c6c19aaa1a17a870cb7c7b55712689662ea4 Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Mon, 27 Aug 2012 17:44:59 -0700 Subject: [PATCH 2/6] DO NOT MERGE - New implementation for ScaleGestureDetector This solves the problems around active pointer tracking when the caller may skip events in the MotionEvent stream and replaces the old implementation with a much simpler algorithm. Change-Id: I97c0bfad03a6190e403e843d382e05ff2257b66f --- .../android/view/ScaleGestureDetector.java | 454 ++++++------------ 1 file changed, 137 insertions(+), 317 deletions(-) diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index 73f94bc2f5309..dc36088941150 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -17,14 +17,13 @@ package android.view; import android.content.Context; -import android.util.DisplayMetrics; import android.util.FloatMath; -import android.util.Log; /** - * Detects transformation gestures involving more than one pointer ("multitouch") - * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener} - * callback will notify users when a particular gesture event has occurred. + * Detects scaling transformation gestures using the supplied {@link MotionEvent}s. + * The {@link OnScaleGestureListener} callback will notify users when a particular + * gesture event has occurred. + * * This class should only be used with {@link MotionEvent}s reported via touch. * * To use this class: @@ -121,43 +120,21 @@ public class ScaleGestureDetector { } } - /** - * This value is the threshold ratio between our previous combined pressure - * and the current combined pressure. We will only fire an onScale event if - * the computed ratio between the current and previous event pressures is - * greater than this value. When pressure decreases rapidly between events - * the position values can often be imprecise, as it usually indicates - * that the user is in the process of lifting a pointer off of the device. - * Its value was tuned experimentally. - */ - private static final float PRESSURE_THRESHOLD = 0.67f; - private final Context mContext; private final OnScaleGestureListener mListener; - private boolean mGestureInProgress; - - private MotionEvent mPrevEvent; - private MotionEvent mCurrEvent; private float mFocusX; private float mFocusY; - private float mPrevFingerDiffX; - private float mPrevFingerDiffY; - private float mCurrFingerDiffX; - private float mCurrFingerDiffY; - private float mCurrLen; - private float mPrevLen; - private float mScaleFactor; - private float mCurrPressure; - private float mPrevPressure; - private long mTimeDelta; - private boolean mInvalidGesture; - - // Pointer IDs currently responsible for the two fingers controlling the gesture - private int mActiveId0; - private int mActiveId1; - private boolean mActive0MostRecent; + private float mCurrSpan; + private float mPrevSpan; + private float mCurrSpanX; + private float mCurrSpanY; + private float mPrevSpanX; + private float mPrevSpanY; + private long mCurrTime; + private long mPrevTime; + private boolean mInProgress; /** * Consistency verifier for debugging purposes. @@ -171,6 +148,18 @@ public class ScaleGestureDetector { mListener = listener; } + /** + * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener} + * when appropriate. + * + *

Applications should pass a complete and consistent event stream to this method. + * A complete and consistent event stream involves all MotionEvents from the initial + * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.

+ * + * @param event The event to process + * @return true if the event was processed and the detector wants to receive the + * rest of the MotionEvents in this event stream. + */ public boolean onTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); @@ -178,265 +167,110 @@ public class ScaleGestureDetector { final int action = event.getActionMasked(); - if (action == MotionEvent.ACTION_DOWN) { - reset(); // Start fresh - } - - boolean handled = true; - if (mInvalidGesture) { - handled = false; - } else if (!mGestureInProgress) { - switch (action) { - case MotionEvent.ACTION_DOWN: { - mActiveId0 = event.getPointerId(0); - mActive0MostRecent = true; - } - break; - - case MotionEvent.ACTION_UP: - reset(); - break; - - case MotionEvent.ACTION_POINTER_DOWN: { - // We have a new multi-finger gesture - if (mPrevEvent != null) mPrevEvent.recycle(); - mPrevEvent = MotionEvent.obtain(event); - mTimeDelta = 0; - - int index1 = event.getActionIndex(); - int index0 = event.findPointerIndex(mActiveId0); - mActiveId1 = event.getPointerId(index1); - if (index0 < 0 || index0 == index1) { - // Probably someone sending us a broken event stream. - index0 = findNewActiveIndex(event, mActiveId1, -1); - mActiveId0 = event.getPointerId(index0); - } - mActive0MostRecent = false; - - setContext(event); - - mGestureInProgress = mListener.onScaleBegin(this); - break; - } - } - } else { - // Transform gesture in progress - attempt to handle it - switch (action) { - case MotionEvent.ACTION_POINTER_DOWN: { - // End the old gesture and begin a new one with the most recent two fingers. - mListener.onScaleEnd(this); - final int oldActive0 = mActiveId0; - final int oldActive1 = mActiveId1; - reset(); - - mPrevEvent = MotionEvent.obtain(event); - mActiveId0 = mActive0MostRecent ? oldActive0 : oldActive1; - mActiveId1 = event.getPointerId(event.getActionIndex()); - mActive0MostRecent = false; - - int index0 = event.findPointerIndex(mActiveId0); - if (index0 < 0 || mActiveId0 == mActiveId1) { - // Probably someone sending us a broken event stream. - Log.e(TAG, "Got " + MotionEvent.actionToString(action) + - " with bad state while a gesture was in progress. " + - "Did you forget to pass an event to " + - "ScaleGestureDetector#onTouchEvent?"); - index0 = findNewActiveIndex(event, mActiveId1, -1); - mActiveId0 = event.getPointerId(index0); - } - - setContext(event); - - mGestureInProgress = mListener.onScaleBegin(this); - } - break; - - case MotionEvent.ACTION_POINTER_UP: { - final int pointerCount = event.getPointerCount(); - final int actionIndex = event.getActionIndex(); - final int actionId = event.getPointerId(actionIndex); - - boolean gestureEnded = false; - if (pointerCount > 2) { - if (actionId == mActiveId0) { - final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex); - if (newIndex >= 0) { - mListener.onScaleEnd(this); - mActiveId0 = event.getPointerId(newIndex); - mActive0MostRecent = true; - mPrevEvent = MotionEvent.obtain(event); - setContext(event); - mGestureInProgress = mListener.onScaleBegin(this); - } else { - gestureEnded = true; - } - } else if (actionId == mActiveId1) { - final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex); - if (newIndex >= 0) { - mListener.onScaleEnd(this); - mActiveId1 = event.getPointerId(newIndex); - mActive0MostRecent = false; - mPrevEvent = MotionEvent.obtain(event); - setContext(event); - mGestureInProgress = mListener.onScaleBegin(this); - } else { - gestureEnded = true; - } - } - mPrevEvent.recycle(); - mPrevEvent = MotionEvent.obtain(event); - setContext(event); - } else { - gestureEnded = true; - } - - if (gestureEnded) { - // Gesture ended - setContext(event); - - // Set focus point to the remaining finger - final int activeId = actionId == mActiveId0 ? mActiveId1 : mActiveId0; - final int index = event.findPointerIndex(activeId); - mFocusX = event.getX(index); - mFocusY = event.getY(index); - - mListener.onScaleEnd(this); - reset(); - mActiveId0 = activeId; - mActive0MostRecent = true; - } - } - break; - - case MotionEvent.ACTION_CANCEL: - mListener.onScaleEnd(this); - reset(); - break; - - case MotionEvent.ACTION_UP: - reset(); - break; - - case MotionEvent.ACTION_MOVE: { - setContext(event); - - // Only accept the event if our relative pressure is within - // a certain limit - this can help filter shaky data as a - // finger is lifted. - if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { - final boolean updatePrevious = mListener.onScale(this); - - if (updatePrevious) { - mPrevEvent.recycle(); - mPrevEvent = MotionEvent.obtain(event); - } - } - } - break; - } - } - - if (!handled && mInputEventConsistencyVerifier != null) { - mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); - } - return handled; - } - - private int findNewActiveIndex(MotionEvent ev, int otherActiveId, int removedPointerIndex) { - final int pointerCount = ev.getPointerCount(); - - // It's ok if this isn't found and returns -1, it simply won't match. - final int otherActiveIndex = ev.findPointerIndex(otherActiveId); - - // Pick a new id and update tracking state. - for (int i = 0; i < pointerCount; i++) { - if (i != removedPointerIndex && i != otherActiveIndex) { - return i; - } - } - return -1; - } - - private void setContext(MotionEvent curr) { - if (mCurrEvent != null) { - mCurrEvent.recycle(); - } - mCurrEvent = MotionEvent.obtain(curr); - - mCurrLen = -1; - mPrevLen = -1; - mScaleFactor = -1; - - final MotionEvent prev = mPrevEvent; - - final int prevIndex0 = prev.findPointerIndex(mActiveId0); - final int prevIndex1 = prev.findPointerIndex(mActiveId1); - final int currIndex0 = curr.findPointerIndex(mActiveId0); - final int currIndex1 = curr.findPointerIndex(mActiveId1); - - if (prevIndex0 < 0 || prevIndex1 < 0 || currIndex0 < 0 || currIndex1 < 0) { - mInvalidGesture = true; - Log.e(TAG, "Invalid MotionEvent stream detected.", new Throwable()); - if (mGestureInProgress) { + final boolean streamComplete = action == MotionEvent.ACTION_UP || + action == MotionEvent.ACTION_CANCEL; + if (action == MotionEvent.ACTION_DOWN || streamComplete) { + // Reset any scale in progress with the listener. + // If it's an ACTION_DOWN we're beginning a new event stream. + // This means the app probably didn't give us all the events. Shame on it. + if (mInProgress) { mListener.onScaleEnd(this); + mInProgress = false; + } + + if (streamComplete) { + return true; } - return; } - final float px0 = prev.getX(prevIndex0); - final float py0 = prev.getY(prevIndex0); - final float px1 = prev.getX(prevIndex1); - final float py1 = prev.getY(prevIndex1); - final float cx0 = curr.getX(currIndex0); - final float cy0 = curr.getY(currIndex0); - final float cx1 = curr.getX(currIndex1); - final float cy1 = curr.getY(currIndex1); + final boolean configChanged = + action == MotionEvent.ACTION_POINTER_UP || + action == MotionEvent.ACTION_POINTER_DOWN; + final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; + final int skipIndex = pointerUp ? event.getActionIndex() : -1; - final float pvx = px1 - px0; - final float pvy = py1 - py0; - final float cvx = cx1 - cx0; - final float cvy = cy1 - cy0; - mPrevFingerDiffX = pvx; - mPrevFingerDiffY = pvy; - mCurrFingerDiffX = cvx; - mCurrFingerDiffY = cvy; - - mFocusX = cx0 + cvx * 0.5f; - mFocusY = cy0 + cvy * 0.5f; - mTimeDelta = curr.getEventTime() - prev.getEventTime(); - mCurrPressure = curr.getPressure(currIndex0) + curr.getPressure(currIndex1); - mPrevPressure = prev.getPressure(prevIndex0) + prev.getPressure(prevIndex1); - } - - private void reset() { - if (mPrevEvent != null) { - mPrevEvent.recycle(); - mPrevEvent = null; + // Determine focal point + float sumX = 0, sumY = 0; + final int count = event.getPointerCount(); + for (int i = 0; i < count; i++) { + if (skipIndex == i) continue; + sumX += event.getX(i); + sumY += event.getY(i); } - if (mCurrEvent != null) { - mCurrEvent.recycle(); - mCurrEvent = null; + final int div = pointerUp ? count - 1 : count; + final float focusX = sumX / div; + final float focusY = sumY / div; + + // Determine average deviation from focal point + float devSumX = 0, devSumY = 0; + for (int i = 0; i < count; i++) { + if (skipIndex == i) continue; + devSumX += Math.abs(event.getX(i) - focusX); + devSumY += Math.abs(event.getY(i) - focusY); } - mGestureInProgress = false; - mActiveId0 = -1; - mActiveId1 = -1; - mInvalidGesture = false; + final float devX = devSumX / div; + final float devY = devSumY / div; + + // Span is the average distance between touch points through the focal point; + // i.e. the diameter of the circle with a radius of the average deviation from + // the focal point. + final float spanX = devX * 2; + final float spanY = devY * 2; + final float span = FloatMath.sqrt(spanX * spanX + spanY * spanY); + + // Dispatch begin/end events as needed. + // If the configuration changes, notify the app to reset its current state by beginning + // a fresh scale event stream. + if (mInProgress && (span == 0 || configChanged)) { + mListener.onScaleEnd(this); + mInProgress = false; + } + if (configChanged) { + mPrevSpanX = mCurrSpanX = spanX; + mPrevSpanY = mCurrSpanY = spanY; + mPrevSpan = mCurrSpan = span; + } + if (!mInProgress && span != 0) { + mFocusX = focusX; + mFocusY = focusY; + mInProgress = mListener.onScaleBegin(this); + } + + // Handle motion; focal point and span/scale factor are changing. + if (action == MotionEvent.ACTION_MOVE) { + mCurrSpanX = spanX; + mCurrSpanY = spanY; + mCurrSpan = span; + mFocusX = focusX; + mFocusY = focusY; + + boolean updatePrev = true; + if (mInProgress) { + updatePrev = mListener.onScale(this); + } + + if (updatePrev) { + mPrevSpanX = mCurrSpanX; + mPrevSpanY = mCurrSpanY; + mPrevSpan = mCurrSpan; + } + } + + return true; } /** - * Returns {@code true} if a two-finger scale gesture is in progress. - * @return {@code true} if a scale gesture is in progress, {@code false} otherwise. + * Returns {@code true} if a scale gesture is in progress. */ public boolean isInProgress() { - return mGestureInProgress; + return mInProgress; } /** * Get the X coordinate of the current gesture's focal point. - * If a gesture is in progress, the focal point is directly between - * the two pointers forming the gesture. - * If a gesture is ending, the focal point is the location of the - * remaining pointer on the screen. + * If a gesture is in progress, the focal point is between + * each of the pointers forming the gesture. + * * If {@link #isInProgress()} would return false, the result of this * function is undefined. * @@ -448,10 +282,9 @@ public class ScaleGestureDetector { /** * Get the Y coordinate of the current gesture's focal point. - * If a gesture is in progress, the focal point is directly between - * the two pointers forming the gesture. - * If a gesture is ending, the focal point is the location of the - * remaining pointer on the screen. + * If a gesture is in progress, the focal point is between + * each of the pointers forming the gesture. + * * If {@link #isInProgress()} would return false, the result of this * function is undefined. * @@ -462,73 +295,63 @@ public class ScaleGestureDetector { } /** - * Return the current distance between the two pointers forming the - * gesture in progress. + * Return the average distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Distance between pointers in pixels. */ public float getCurrentSpan() { - if (mCurrLen == -1) { - final float cvx = mCurrFingerDiffX; - final float cvy = mCurrFingerDiffY; - mCurrLen = FloatMath.sqrt(cvx*cvx + cvy*cvy); - } - return mCurrLen; + return mCurrSpan; } /** - * Return the current x distance between the two pointers forming the - * gesture in progress. + * Return the average X distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Distance between pointers in pixels. */ public float getCurrentSpanX() { - return mCurrFingerDiffX; + return mCurrSpanX; } /** - * Return the current y distance between the two pointers forming the - * gesture in progress. + * Return the average Y distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Distance between pointers in pixels. */ public float getCurrentSpanY() { - return mCurrFingerDiffY; + return mCurrSpanY; } /** - * Return the previous distance between the two pointers forming the - * gesture in progress. + * Return the previous average distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Previous distance between pointers in pixels. */ public float getPreviousSpan() { - if (mPrevLen == -1) { - final float pvx = mPrevFingerDiffX; - final float pvy = mPrevFingerDiffY; - mPrevLen = FloatMath.sqrt(pvx*pvx + pvy*pvy); - } - return mPrevLen; + return mPrevSpan; } /** - * Return the previous x distance between the two pointers forming the - * gesture in progress. + * Return the previous average X distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Previous distance between pointers in pixels. */ public float getPreviousSpanX() { - return mPrevFingerDiffX; + return mPrevSpanX; } /** - * Return the previous y distance between the two pointers forming the - * gesture in progress. + * Return the previous average Y distance between each of the pointers forming the + * gesture in progress through the focal point. * * @return Previous distance between pointers in pixels. */ public float getPreviousSpanY() { - return mPrevFingerDiffY; + return mPrevSpanY; } /** @@ -539,10 +362,7 @@ public class ScaleGestureDetector { * @return The current scaling factor. */ public float getScaleFactor() { - if (mScaleFactor == -1) { - mScaleFactor = getCurrentSpan() / getPreviousSpan(); - } - return mScaleFactor; + return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1; } /** @@ -552,7 +372,7 @@ public class ScaleGestureDetector { * @return Time difference since the last scaling event in milliseconds. */ public long getTimeDelta() { - return mTimeDelta; + return mCurrTime - mPrevTime; } /** @@ -561,6 +381,6 @@ public class ScaleGestureDetector { * @return Current event time in milliseconds. */ public long getEventTime() { - return mCurrEvent.getEventTime(); + return mCurrTime; } } From 8be03ace9500e03e6aa482713cdd07caf5ca6421 Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Wed, 29 Aug 2012 13:54:44 -0700 Subject: [PATCH 3/6] DO NOT MERGE - Use focal point for scrolling in GestureDetector Remove workaround for obsolete touchscreen hardware. Provide a better focal point for scroll events. Change-Id: I173cd6696dace379437b56597c4a6ac5c7fbf60d --- core/java/android/view/GestureDetector.java | 118 +++++++++++--------- 1 file changed, 64 insertions(+), 54 deletions(-) diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 0114a419f23c9..23337f03792c2 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -226,17 +226,12 @@ public class GestureDetector { */ private boolean mIsDoubleTapping; - private float mLastMotionY; - private float mLastMotionX; + private float mLastFocusX; + private float mLastFocusY; + private float mDownFocusX; + private float mDownFocusY; private boolean mIsLongpressEnabled; - - /** - * True if we are at a target API level of >= Froyo or the developer can - * explicitly set it. If true, input events with > 1 pointer will be ignored - * so we can work side by side with multitouch gesture detectors. - */ - private boolean mIgnoreMultitouch; /** * Determines speed during touch scrolling @@ -349,8 +344,16 @@ public class GestureDetector { * @throws NullPointerException if {@code listener} is null. */ public GestureDetector(Context context, OnGestureListener listener, Handler handler) { - this(context, listener, handler, context != null && - context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO); + if (handler != null) { + mHandler = new GestureHandler(handler); + } else { + mHandler = new GestureHandler(); + } + mListener = listener; + if (listener instanceof OnDoubleTapListener) { + setOnDoubleTapListener((OnDoubleTapListener) listener); + } + init(context); } /** @@ -362,31 +365,19 @@ public class GestureDetector { * @param listener the listener invoked for all the callbacks, this must * not be null. * @param handler the handler to use - * @param ignoreMultitouch whether events involving more than one pointer should - * be ignored. * * @throws NullPointerException if {@code listener} is null. */ public GestureDetector(Context context, OnGestureListener listener, Handler handler, - boolean ignoreMultitouch) { - if (handler != null) { - mHandler = new GestureHandler(handler); - } else { - mHandler = new GestureHandler(); - } - mListener = listener; - if (listener instanceof OnDoubleTapListener) { - setOnDoubleTapListener((OnDoubleTapListener) listener); - } - init(context, ignoreMultitouch); + boolean unused) { + this(context, listener, handler); } - private void init(Context context, boolean ignoreMultitouch) { + private void init(Context context) { if (mListener == null) { throw new NullPointerException("OnGestureListener must not be null"); } mIsLongpressEnabled = true; - mIgnoreMultitouch = ignoreMultitouch; // Fallback to support pre-donuts releases int touchSlop, doubleTapSlop, doubleTapTouchSlop; @@ -456,34 +447,40 @@ public class GestureDetector { } final int action = ev.getAction(); - final float y = ev.getY(); - final float x = ev.getX(); if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); + final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; + final int skipIndex = pointerUp ? ev.getActionIndex() : -1; + + // Determine focal point + float sumX = 0, sumY = 0; + final int count = ev.getPointerCount(); + for (int i = 0; i < count; i++) { + if (skipIndex == i) continue; + sumX += ev.getX(i); + sumY += ev.getY(i); + } + final int div = pointerUp ? count - 1 : count; + final float focusX = sumX / div; + final float focusY = sumY / div; + boolean handled = false; switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_POINTER_DOWN: - if (mIgnoreMultitouch) { - // Multitouch event - abort. - cancel(); - } + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + // Cancel long press and taps + cancelTaps(); break; case MotionEvent.ACTION_POINTER_UP: - // Ending a multitouch gesture and going back to 1 finger - if (mIgnoreMultitouch && ev.getPointerCount() == 2) { - int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK) - >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0; - mLastMotionX = ev.getX(index); - mLastMotionY = ev.getY(index); - mVelocityTracker.recycle(); - mVelocityTracker = VelocityTracker.obtain(); - } + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; break; case MotionEvent.ACTION_DOWN: @@ -504,8 +501,8 @@ public class GestureDetector { } } - mLastMotionX = x; - mLastMotionY = y; + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; if (mCurrentDownEvent != null) { mCurrentDownEvent.recycle(); } @@ -525,22 +522,22 @@ public class GestureDetector { break; case MotionEvent.ACTION_MOVE: - if (mInLongPress || (mIgnoreMultitouch && ev.getPointerCount() > 1)) { + if (mInLongPress) { break; } - final float scrollX = mLastMotionX - x; - final float scrollY = mLastMotionY - y; + final float scrollX = mLastFocusX - focusX; + final float scrollY = mLastFocusY - focusY; if (mIsDoubleTapping) { // Give the move events of the double-tap handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else if (mAlwaysInTapRegion) { - final int deltaX = (int) (x - mCurrentDownEvent.getX()); - final int deltaY = (int) (y - mCurrentDownEvent.getY()); + final int deltaX = (int) (focusX - mDownFocusX); + final int deltaY = (int) (focusY - mDownFocusY); int distance = (deltaX * deltaX) + (deltaY * deltaY); if (distance > mTouchSlopSquare) { handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); - mLastMotionX = x; - mLastMotionY = y; + mLastFocusX = focusX; + mLastFocusY = focusY; mAlwaysInTapRegion = false; mHandler.removeMessages(TAP); mHandler.removeMessages(SHOW_PRESS); @@ -551,8 +548,8 @@ public class GestureDetector { } } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); - mLastMotionX = x; - mLastMotionY = y; + mLastFocusX = focusX; + mLastFocusY = focusY; } break; @@ -571,9 +568,10 @@ public class GestureDetector { // A fling must travel the minimum tap distance final VelocityTracker velocityTracker = mVelocityTracker; + final int pointerId = ev.getPointerId(0); velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); - final float velocityY = velocityTracker.getYVelocity(); - final float velocityX = velocityTracker.getXVelocity(); + final float velocityY = velocityTracker.getYVelocity(pointerId); + final float velocityX = velocityTracker.getXVelocity(pointerId); if ((Math.abs(velocityY) > mMinimumFlingVelocity) || (Math.abs(velocityX) > mMinimumFlingVelocity)){ @@ -622,6 +620,18 @@ public class GestureDetector { } } + private void cancelTaps() { + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + mHandler.removeMessages(TAP); + mIsDoubleTapping = false; + mAlwaysInTapRegion = false; + mAlwaysInBiggerTapRegion = false; + if (mInLongPress) { + mInLongPress = false; + } + } + private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown) { if (!mAlwaysInBiggerTapRegion) { From 21ee2ca458b11ee948794109022257777d4f16de Mon Sep 17 00:00:00 2001 From: John Reck Date: Tue, 28 Aug 2012 16:22:11 -0700 Subject: [PATCH 4/6] DO NOT MERGE Remove dead code Cherry pick Change-Id: I19603a6e234b0b2592ef90fd426b2973cff8e4fd --- core/java/android/webkit/WebViewClassic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index 84a6129e8c4de..d8541fadc2d12 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -1364,7 +1364,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (action == MotionEvent.ACTION_POINTER_DOWN) { cancelTouch(); action = MotionEvent.ACTION_DOWN; - } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) { + } else if (action == MotionEvent.ACTION_POINTER_UP) { // set mLastTouchX/Y to the remaining points for multi-touch. mLastTouchX = Math.round(x); mLastTouchY = Math.round(y); From c104d168789450d655f7efbab209dbf4d125bac4 Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Fri, 31 Aug 2012 11:11:39 -0700 Subject: [PATCH 5/6] DO NOT MERGE GestureDetector - Mask action when checking POINTER_UP Bug 7088494 Change-Id: I723e9b77f0d0473f9d769e53aaa568c4aaac90aa --- core/java/android/view/GestureDetector.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 23337f03792c2..4bbdd4e3b534a 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -453,7 +453,8 @@ public class GestureDetector { } mVelocityTracker.addMovement(ev); - final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; + final boolean pointerUp = + (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP; final int skipIndex = pointerUp ? ev.getActionIndex() : -1; // Determine focal point From 77bf72a5cd1b4ec70d9ef969db7d30c778a8303b Mon Sep 17 00:00:00 2001 From: Sascha Prueter Date: Fri, 31 Aug 2012 11:39:44 -0700 Subject: [PATCH 6/6] Revert "Update Back softkey icon Bug: 6020915" This reverts commit 962daf9b1c4b12aff68a137472b6a7f4db123c65 Change-Id: If60099643336e35fe5bf7dcbf68a419bcd7ebae3 --- .../res/drawable-hdpi/ic_sysbar_back.png | Bin 1343 -> 1053 bytes .../res/drawable-hdpi/ic_sysbar_back_land.png | Bin 1345 -> 1063 bytes .../res/drawable-mdpi/ic_sysbar_back.png | Bin 970 -> 774 bytes .../res/drawable-mdpi/ic_sysbar_back_land.png | Bin 965 -> 769 bytes .../drawable-sw600dp-hdpi/ic_sysbar_back.png | Bin 1584 -> 1480 bytes .../drawable-sw600dp-mdpi/ic_sysbar_back.png | Bin 1183 -> 910 bytes .../drawable-sw600dp-xhdpi/ic_sysbar_back.png | Bin 2117 -> 2126 bytes .../res/drawable-xhdpi/ic_sysbar_back.png | Bin 1830 -> 1421 bytes .../drawable-xhdpi/ic_sysbar_back_land.png | Bin 1796 -> 1429 bytes 9 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back.png index 6ae32f184393ce201df9544dd4f338bf5214a3c1..84e6bc89c0829a1dd61d9045baeca62b12199c4a 100644 GIT binary patch delta 1024 zcmdnbHJ4+8WIYQ51H*LjpI?9!QYF@I+$n7@*r&N{=Faam-z(p- z9yZ`XMvJ>D^16e+eBu<4Q#{HQGD$9R>Zy=hCfZ6euh~Q_Ph_U8cx)V7p)vPWXK8DI z!j|y)ApwrgR%%aH-&z%nN_6jP|5~d}_eyOS_Es&+{F)t;{`g;-tk&^e`vP{? z7rnZ=C2vjnU61#DK<1ivoT*P#Ydlh$r@oB4I^~zi+HXr5S8Y*B@w)Y{U_&xTjg#N! zmP)gaecWdzs`^A^*m*^L@IIDjw*AP`BfH!yy>u&?igY$9F4Pl|c%prGPtuXKo46DC zKQ;yTY&>D`J0R(<#wG@d2ZsDJ`ZsOg!tuLBrv8B??~{`Thnt$^zO7u?$HaZ*^DMEp z7|q?Y539d$O8I%F^4N;b^ar<+xNiCgw_BVpTz2E2_pCs#Nmn+7{+e^lbb)56R=}eh zkBZi3d;Fah>>c8+r4gKWbxW1$>UJl`L$No)-5M|W`esDjJ*Ad^#7kql=JNNaUY2e0 z@%G;NQ*7_``i?pm_K8`u|4AlHk+lu-+dJ!0>O!AMTOMxzRO_4N6y9QVQn~a>TAav2 z_AQ^<-u_wAJ6Ytz^?iC(OMm?0JhADl?~-$Do$LaSj1QYuWh`pc^Ano%r$M4&_n%7# z6@E5PXmHdkY_r+=re*8<6H5Q8&0U?`WZhpe>CWvIF7vp#D0F>2ztpt_e7hsJ%=uPO zQ>|W8BkAQq#z}+L@m`S8Kf+g!N|bSP}k5v*T^iy z(A3J<(#qIC*TCG$z+kH7@VJ0Ql40p%HWui kpOmWLmRXcqo?nz*tl%7MTDbMG8BiUAr>mdKI;Vst0E|V(VgLXD literal 1343 zcmeAS@N?(olHy`uVBq!ia0vp^6+rC4!3HF4T7Vj}UCXtMlxcQ-EEAI}; zNvGD=YK5DPIz8wUFxeMzrZaQfa<0PV9TR_9e9$Ri+#zhiC*zkp z!8P99<5a4ato-CJdzUN?@$D=NNM`7k+;6H{jCzNp+q|`wc~|Gp%R#+Q{|sp5~Fg8ZY-Q z+P6C9Pl4|#hdVDz?r6WPiOLth?6L0NO6LCwRkj^>U2-N`EoVRGqVa|EgZKv4A7T}p zJ6L)ynO^nrEz=c${;MSJLR?SW-C2!0b~pGp<{n5*kcwcK*5G?ZRNU!?@PYmVfe*YN z@bGrW+?}LSa4w{l(S~^rTN;OBdPjPYO^U2EOonSoCGuo>xmwg}8H^$`|K2Ls$70f;1FRbYy$Ry7m$5N+mRbE+I z9=Utwfv^XOO1Zn(E4m__-YGA8{rqxi?Df*zs#wzqMVBhY)OB^f_r5G#wC70j^=F5{Y@w@$JCr?;+#FG)VxZKwFMbK0CWYVHNg zBu-ZDde`{rj_>_dGB!NhlNP?~n&kdo=YvK;>xn@5)7#hIt$${3G{e`-yGpy&z4+wi z`kh-{j<@k0>-M|nk`uLN+x6oIVh`9JX#O*M)g8|zTJj8hf^|$6+~lOB<}b<*{5`Fs zx1qcDA;t#SvF7Kjy)Gy&*iALNUUFYw({-S`PC29 zA229>FzS7)va#*hI&(dy{Yy8^GW&icWDmm}<)lyf4a!GmtKCe_^NRT(WuqzkP&HmL zt?^U+vQx#nowIq(g$+J*6+e3=wJ};)+-%2z|IRWzz1&&Y@?q3-`JZ#_{wrAJP6U<_ zswJ)wB`Jv|saDBFsX&Us$iUD@*T7WQ&>+On(8|En%D`CHz}(8f!0_@T3lt5x`6-!c zmFOBwtc=Vc8ukZDCIB@^f@}!RPb(=;EJ|f?Ovz75Rq)JBOiv9;O-!jQJedkA$QV3b L{an^LB{Ts5_TD@o diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_land.png index fdc56bbd4f60fcc4e5f991d4c5ca85f386acae68..782d2147fdcc0963e6a3af4d5997c9826dacc789 100644 GIT binary patch delta 1016 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 X;2dmPxb?9aP#uG(tDnm{r-UW|qzpwz delta 922 zcmZo;JH14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>86u7%?fY+9@0DQvzg4VjZAP;0jTLKq zn-(Rg-{@En!PV5NAl9Uiw7er};xRuF*KF@@6UQkFd|6bV-M@Q2DXr*nRlDLjot;1I zBEHRdbZ=k&_q)}b+1nB&;K;3b$1)~QDUr?lUr6nqvc{z7D)SRgxA3TXua7)gpICYr zr5pG@$j)g0s%UII=}}-=6fw>D1s6pxF#9o= zH?ls^`oM65Q74V>Hf!A_`QD_O`SlqK)k3`z_WW3UKzYv|y?w9CCaDxZGi3K+%x^sY z@bji4K}?&cUKChlF;RC!6cnj3lSTBgsG!Hy<{9~&21F3@cKdL$jGB=p# zu$*I*ZWKOHyx{4a*V9gH#7ASnBK7DG0$#{TllGKr>HE;ZPopEEXAd}KJaFXS#-;%TK(Yr z8$HQ6u6nIT%!F+%`5M0)B_9;q9y?)}5I?7O&5C-{iQb8~e<=0FDs5lnpOA83vPAl3 z`?wWq-m^cf-}^5u>c_-X=?&5rt@5dL57M1hy!CSPOVG+-b&C3NX`iIanU<}I8cz)Q za*lh?XIaOfuc^T=B@!`#Yd`xuHao`kjp+x(rEZ*ByYpd@s47p~C7XSXE?Nu3>+SwY zH?CHk7}q*SrOM>X{?oIBfAA;stZADSr1<%~X^wgl##FWaylc}Iw!{F)a=d#Wzp$Pzu2YBKD diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_land.png index 49d5101a3e45d3dec36f0baee7e6b39a1985bdc2..860570124ccf54f077e140cc8947e7e7f28ae595 100644 GIT binary patch delta 756 zcmX@g-pDpVvYv&3f#K_ayD%Wdl;rL1!tlSn|IB_MkF&rdvY3H^TL^?1FWs&C!@$7! z%hSa%B;xSfsk^g90!7;Q&y+Pg{fMiJaih`R35TLw0$8kdb#m6cNm|(YzoA2;X=$s% zt)GXkxd}EMIpnuNcuI$uQ;o|--!kL*N6*;ZIsRrtUcF+)e$Dr``|t1kKBtN#_dEm-|>%cWMfoqIxNCilsk{6ClG5!Z7h*Y3mQ{x#j3FQ0#^ zten4ceXPLao2Qap_oSTO{!V=IoeeKeE1#BBc{IuX?~8{kR)h+DVhu>T?y;Sbs|;ttdIRMCOs)?3@c~nuS@NKh9atE1u3j)78J?%g5C! zOO~Xro3du^bIW6<(<~jmK2Gi0SaGWKx{dO?Ert(o=N-0{`_T5f?)0j(g^t&ppZ|SY z%|9*sqRrakZ8fu|?zy3`c}LQ1bG>OfmtEHUKBWI{_Jok{3yZkV#!CdK^v%E{AYL^Yqbg_TQ%=I%Hh^{wWOyz=68E1q@5$LfS`Ul+16 z?rb;Eujy`oUHy)=y3DX$$nnN6Utyu$14%_*F^iL#vtnGea(qo*tak0^ik5lEH=q5{ zl4CZ-Yl<3=AF=)E)O^X~jFf)lL%aIba|1uR&w4u5EiN$q24nN&47dLW_qd2&a{QU; zXP~#Nzz`fCz-alw|AW_RTjb_@7MHz&$wRcnHKN41C^fMpGe1uuBr`Xa!BF4OSl`g_ z-^F$1Kn1EG1u2OosaDA#RjCX{28M>Zh6cJuW+8^AR>qcA#s<0ux#m^|2J3yTno)G* z=BH$)Rif)Kw=y)Nn{1`8H8cP$+X82|fR>yvoQU=;DvK^Ey`ZUe76`#YDR-1e8(to*mN&BRzhdnFrWI#>ylr! z-1%GA9nUGRO#G_q`Fhelk6ZbVeKK&H{lQ$@8rb_+Qcq zxg5p(fw_X)hV>njd=rPpxxB?Xe`PKhw7)LdC9R#nXu%c3Jdfe{*`GI>Zkn$+!^N_Y zvw&-d4XFvcYu7s;2us+0Gty+XdV{FZ?aVEazl|?gS|53@D!anCwr%GcyX={H zs?`Tp`^^$BbPbBWl-bSX-f{8~b<=u4swrO4W8ijz< zX^%ZJ&i8Ak8f<>=Qk5t4fae3l4aOMuD2JfWt6hX*Zme*=S=}(9JNHtDNw40dwB;5VGK1!x)~MqujN~ukOGJrlw}a4CY;?9{Fz$AIN<$du#3vh9$St z8cU-0F`r}Z_t+k~{6*=aNe!HW%<&E42aF%g>y-GRWw=pZUUXXk%b%llS5sY7@A6$1 zF=%@*q0{8d&rYrtGu#s0{^wcrJw`#nPviv-xA;`mU-ty2RMisKh?11Vl2ohYqEsNo zU}Ruuq-$WRYiJN+XlP|%X=Q4uYhZ3=U~s%Xcn*q&-29Zxv`S13Cgun$C6>rta>;dBmoW`v78x@xlL#|t7}xc8*%4!?K}l1)47o>?`y@k> zvJxVcQDR2ZNN6+R$hF0~<+9rGanIS#d7t+=?>WEcyx*QQH&;hVaTRd@03@+a7!QG$ z2;?s&BG{L5A14S*IM~?{1AI%(9_SQYhsaKZSOAbXvM-^xA206$fJiqMgTlv;eYt&x z?&+!e^5o38#S&W`&Ktt86c|qV%E#@b9k8_KM|7f7NU@K$NWD)p)_@cg?4(?u&PaAU zKoi$g@sePl49u)qb>t&@Gi2Gto8w7*g-s+(VV=}Jvk|pxLxod6e_Z9vlx>yrw@M)? zK}z45T|;6sd#;m&1jPyFx<$)(edFCEawbUMn6*~ftr~SHCnoxYvV>7(seTS7exIcO zh)+MLtX0uPy~GjYOb_|#LXEk@EQe;ZY^09V--hzUtkja^-%3i-$7;&YV>{aJLwuNw zl7SEF?w3S;>XzU&YLfGM)|D4{7tnIq7EK=fa5w9=D%=>u)p8o%0(Q4F>uftg=6P4z z)*Gu|QdcvV`nrWAMqj@e5>uX%wF%KKlY5IVOmdiYC+4-XKU=dQ4wUsJ({qv{nePWPG{}ew&BojrEV0qRXhq+|gW-BqZ^T8N*G|7)!?mRY znqURTSt703e7G^wK0{_wDM&g2n{dp##nz&eoPYV=iuZCorx4-zDk(C|(o;C4_F;~g z`gL4tUJYUThn=kq-cWAz`90l(?o3wzAuKL-lC&K!XAutcpY2T?pO#3;<%iE~2CNH> zhsb!^RXnFN?_SlaDmAvdcS7F-Jp5EYz;~}d8$phHNb|mBUvRwhQRcNKu2V;A&aFRL z9ruGoc{MUemQ~crAjQZ~Pn(ot3tPL&nPY6@hna1@sK%S*T7R0IJly*Xsoc268-lDo z|7-u;wl+65)!)5y`&~ohMtDs7DHj6qzOfr>bHUjUpPlj;9XlnAmcoh{z40}uJEJ&M z_vLnBM(dgh)0U>jlPT~Vj;UWG&{lkq)R>lqFg?D9HX}3Bs(%eZBmWqX)(P2l!t|(m zaVJcAw)nBqS+I7tCbEXhf<-bQxrw`2f1Z+~Qn!0xLKe&N$^mkQ1;RBA`gz&vs6J{t zq7*Xo>8CrkuS(@s%2 z>UEskw3>e~ypqXvJDn)MRpJ2~ouWAPIU1c`yR(3E zaMU2S$EgNeEa^4c&G&uGS4tkDAZhef{69LBTcyXaDK~o^QP$5cqD65LAwr~C3?A{i zd~f^4s0i-xt#-X&Yum@btVjIoxXEQac87o-DW4ZwM7@xW#z?N>_z?j zB=+bk;kidBPK!}GwOI9GNxXBtN0ctVSk0|Mq-tq&xicMT2B`_D=%LAgZN_?!%PiTY zj-(-6(M#A^h3ZXGY6p*pE~HD`o_})3^>@1Xi#2k_i`}m9-M;py;nVV(oK=4u)Kvtp z6r7=%I9FLn*9HfsH$DxuvcmuR`r**IDG)F-g+h&>rbcGwcrzFhW{HF%j7(1;O--enbz}Y$I2Rof5t{J*K+r-N yTM$s)|A31QjiUxpKmZjI9SmwXL{LC#G=&tafyTjO9#5GIH~?$!ig{occ=cb9m7Br< literal 1584 zcmZvceLNF*9LIlqU>=X62`SrZdU>2ktfaBAT^^zd<*qFo7gy#X&oZo;kfE4Rc_?|v zQ@K2qbv@v!d8j;etClN|C8t}~gj;ui-NWnq@Ogc|-`D%~{qK{x&(lp)%|HzRph+PU zeH0p}D6)#OqC0N1PEiOvgy2B{pgC9l3tdSuL!x}#oI!(V!?a>B4kG(_0FZzMfXx9w zu2`|B062jIV1@<&UI2g|tF*&=m*Sv&WUm_$tbUD4x2g*i8&wuLAWEU+zsj{QY&QT( zfI=itHn`F>F*CO=kON?^wao2rCE2NIX^$UA+ zH6bMN)-~yOPBqf(3@{ZB==t`s6EBbJRg0(|)rhjbvf7#U$@B?3#~0JJa^EuiB=v+4 zr*ZfHGn>;UR^rvn;w}9SjfJL20=-FAg+~t7pezM^opRoA81E45CM;JT#nfXVWri$K zdxB1*;Gn!+eE>aoT*9O|HO(|WhFyeY$^GXdV^7s%?AQ8DLmGh=#7|}Xq*7_1*^T1& zs~%P(S%%}aNBUt}u#u#(XvjF#QFU`2^FE3syE~lfC`)zEJfA-yF@q!O%pPH${IZLMVAVA~+=l)`13j~ovDQJVGOQ+oHbP@{ zx~4YHqX=(0qOCgU#WAp02!f5^5AISv?u=jfl@sp}@FFsYmXm%M@rjkZA&IMvC=xxl zEa1U}Q8JcRae@fqHdX_@%Pp=S(cW+8OpJjS97w{}F<-+EPnc-uWxyXwk*rLPAu|7p zDJIX>MQS!w7e*8}uP&j8U&d_$MD4-1Oo;>id7k*$+3g5%b?)U_uZ8j^Bmv3wZ^zaS%Bg|fHnvA!U!SYZK0q_Bw=LV_`ulP(7O+E-T$15*=@nu$DYMCi zI?`q|?royE+!9`rLu^RKn8SKhmnx(kE}ZU;)*Y9x6FQ!wl|#p-yU{;<=6>5R)e3s^q(o2qW7l|!71T+dSW7)g^oGW!7WxD?jA=ZjQRV)QKQ_%Fm`?Ki{y?gzjX(y$;e;Ivu#YCdVp|5Wc2H^_tY{ z@th~d^D#qN-$>7{I=4iGEXTiNZ&_TaCP>ap-nl2h8;v`+7qVaF<-WBv?lb5$Z$Q%A zS{{2ARvZd0F{A680>#zV$P+n|4+IbtwJkyoNuk7UszC>;+ANVe>8(0 z&BO;qG8F>0;jFh=;A}0n*-@>n@wV3Z?KT#;9e5m0Zz`wsAHcD2#?j#Te*-uL^EWW$ x>{X)x^uK=CA08YXOOIp%0weqg)0lFM9>Vlt(i!m)eax@U4Jaf}VuLd+?N4Fov&{ei diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/ic_sysbar_back.png index 2fb191db9c9c588932b23742bf5096792af308a6..0c12c1601e56253485beb3f61eeb20b4c9900b78 100644 GIT binary patch literal 910 zcmeAS@N?(olHy`uVBq!ia0vp^89;2u!2%?64^2A&q?nSt-CY>|xA&jf59DzcctjR6 zFmMZjFyp1Wb$@^=+C5zyLo)8Yo#E{f5-4%JzWms+nk6^Ac{F)gyfvB@`8sMVisy1= zhR$A;A?Vn>O>apri|!&b&vi{5a#!?vm~M-R1hzfOiE8QBWL5P&KJ$0}!(Jxox$nxq zM$Nf@^X%N}bKk9>-+6Cy;9-G<3}5?U5Y>0JLp;>AYu~8=m89HS$&Mw~TOUmS;kHs` z)2yH?lN1iJJl=TIrSaKmskxV=zrLKwcXIOHTQgd1WUp4IPPyi}^oYHVNY8o4y+LmC zOCJ8eaC5C(?~Bd1LN#?#OJA70*k>elsWf-X%Oh(lib=l49h-dnjTTy^FCC zra51qx}>R8<>aS1d~806lC2LI!ZTIQ%jOvfZ(3!vZDYUk8x>Ff=@NaZuSIH(aBh9E z*3ERqWi3OM=9XDk)++M8^y&Gj$FbCxSt@jY-$~=t9co`+t(ME!>w2QD=0oKX(`7df zr1H&v<~3`&1GBZx!x=42FE*E4J}PrNGyVN}_4;rI{`n?$p*fqrbQQKVElb~VGWL_n zWyj(dfkHbM==01t?|ko1jr{V-{2>nl<)YYMeVmbWNow-Uqvi&#>a9_>7iY{{rFU=1 zfkUCnSLUpIrc-_5^63?-(>nSd$nDP)tGj;jQk&+fl?x|6-m+nu-Mfd;V%J{06X9aN zc%m|hHC}sZ*V!wOa%t;&BDt-@U( zNrt~FedX`J>DB$L7r>wzqIaooo{VMDkBcl@gq)5Z4RrYz!RmC>qq6=?e_~{3eun3x zz4yxDDdOly=7pD*Pk(eoW-~CIik7%WlsFfqCYEI8=P86_=B6?j>KhvC8yfz*xUL+i zKoz7QC9x#cDjB3ImBGls&`{UVK-b7D#K_pn*wV_-Lf62=%D_Nin_3}?hTQy=%(P0} z8WLa6I}6kx1F|7FKdq!Zu_%?nF(p4KRlzN@D78GlD7#p}IoPyt>ti#ZItEWyKbLh* G2~7YvQ-n_d literal 1183 zcmeAS@N?(olHy`uVBq!ia0vp^0YGfP!3HGTp8ov~q*&4&eH|GXHuiJ>Nn{1`8H90ib(!I|Tj1!|P*!sH+ z{&C7r;ckz$(c-pPa9G6uQR1JO{nt0PP6>L&Z}W@mqOP5b!H0cH_atrlPAg5he@Opl z)&B17a#pJ<9>{!@J97EPx$hR;`5UIKUp{mH>a&-+t{q}JC-`2^rk}I#L2Qkt%j_Q8 zl@Fa?{@&+uV@plg?|FmY`kA~{ck#NpVRKCl`*U8K z!a%|7rw59D?c;d*+l6n|16$GgKlU32U&#}m_|Ze`t>vE%@3ccd4}4yq@uA@5H!Z(T z_7b`Lj)1sn;$r>UW^)B|uS)GZ@hv3MOK;8tb~ce_{qBAF-XfFRk94I>+HZc;WU0xi ziO2Q%wVqnm>0IKx(>s~DI)6<=vCA75MN4g=AKM}(&N(;vwZ!_lo|?RGCB@dS-;{8? z(EaBxFAfXkB*B-~dvp(_N&7#nYo2;y#u?7DYir(@PE1=Bu+@FG@*~zj7qd?vjowW? zoPP54!%}O@jZx8|wdTu0o=&-BVt(_vi$^|3zRu2qkgv~jUd=o8e*J^(QD!|`=Z2~U z2z`BKzj>$M)!83UNG0CuWG^$86PUMMHEL$keWxC!2R```T;772*YCTeofDrG^X*?} z#)^26zb~ci&d)F3nYDgiL-COtlNTDxRa|h{uQzRidO`n<3$~hJ&f>1#o6~21E7~0T zL?TXD_jsN9EzXU7&#tfhu*Y|gW|+s-vLoTX2ZB_s?`Z8=@n+GR_DO2%9;Mf``G2mQ zWbE0y^ywRe{j)#G&t=o!t}plEF)$CRmbgZgq$HN4S|t~y0x1R~14AQS0~1{%vk*f= zD-%O2Q)68Nb1MS_@fW>wP&DM`r(~v8qH8b!>Vasu|;OXk;vd$@?2>`kF4H^Id diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/ic_sysbar_back.png index ce008f39865b123bc8ca2e4d992fc831ab40c375..477df5f4c8606eb78194b8bd208f59e7b3cffe63 100644 GIT binary patch literal 2126 zcma)-do&Xc8^=c&)gqUa+TPF07c$T!x9w zC98D9uUE)5#)zC;7ILRtUj6y|<2~O%l^`v#-(w*<-TNl)ciGMcu$P|mv1Naf3AOi*000ij|CU&z*k~jl|3fI9Uy%gxEWltLqJF*Bg#4KChbQ>IBmpx@Wg6rq^P- z{;E6M@&6a%bP4!%S?dDC52#de=pkLDTS^$Ze(XowubPFu9w18xT&jbRgJ(S(^ z6B3M`DvunpXfip8W_zaQBE}Q@QVsW6Lh&hDWfd-a!o%9ORV=>XlcL8Bdz?JFQa1Ca za4Kv=#no+QiBgkS@AJz9cNATTmq1Qbsz9XGm0EJsZ1bjLW#y&pJvLT>GL?xZb-hUvGE- z-8d#y(Oz^>Zgc|Hm_d)T{iATT?q^UT-@tw_*@x*|(cY;Zmd^H8N^RT`baYAk0V$#t zpP&t1nT=TeY2eGKnEpmwpaYsZ6tPQ;oXAA@7DssrkGke2`8+r(;Tiwh+L(R}lO+%} zI%T?=2NLc24YXKulgXNb?_|q*4;;p&v7K?{xT2Z5vzy!SEAqV}2?q14n8BRGVEk() z2czJwq(ZJmIkLV!P$+;;@Z6kCNmqyU{l8B^H-{0lok2DiIz_fz2eC@aOG(+TI>-yq zc?FG=)LwkL>XK7%rty~cd3R;c-rL17- z3akq~skl6FlqV*J%&A-3GNR8FyFzC?%o5tUyI;I2F*2-~$!{F9>tzhSGo$%A@gT@H z7Yvrx*Vk8>b5Q!nl%#Wf=S{&Ku)DHyr|C?zl#o@dh%tJx0P>Hc{^V5q1T#BAsHs$T zkUl4>M&0d5r|o?is|Yi5dYs)GOr^lg6 ze3O5WxN}K4+ej?`kaS=+y+L>k#8US>vHTFlx_EQmczbW}V`EM)&+W)#-n+Y{z0VBC zR%p#h^4Ul{`P~^JB81_{<`}ZbOtb9R-lK%}XfCrTa_|=2V1b$%k&Jyg2Zs&l-v1tE zEyqLDDXb4oTbt+Ae;{x0OTS|Awe?bGj8tperIWosH@`tQv(UZMNIWgheA4^Z$8y~V z#_^*#aGyi1+s@Uz%nHT(zDsE+_nxF+T#a_m6|=Pi{H?LX_mmezkCwFN5ZctQpYkEP zc0F0vci6X+I~%IMC4JMT!zVp`UHWSJc^&T-`YcT_RC1Tvx4n$9Xa{$_p)cXKY$x!N znGAknb|~vnNRQ}oTtBF3*uPEf2kOS7jQNlh8HMWnsU+$wd1?xh{jbzcM%wfmmQuz( zv9l0*sT0XXyj;=_g%~pK0$&kmJh=o_Y`BM*RgPOf?IkTuM_Leem4%K@5AnPf4scU?T2q#3|eu{m1%w zHaLotJh?sYmh!xcXp@Bm46ZCY^+bvAh% z8R7@;_9h~;EKJMU|JCc}$BCMWDZxgH4jPE!e6~k3UM=#~mf(mQLlv!S<33L1BSERC z_7}|jITA&=Uh{0B%=^@CT)(W>aO>q&zL0EY^l7ok1go&{XY<)29m)~kXG%gr*ay1= z;}JsR$YFLx-a2GYOQ@I0IYjJA^=QlOY!2-aqKVGd2UFR;Bf?qoHP7;j+rEwCW_sP5 zRFajlp6=>d(JzOcw8K6WZgtcU;W4PZvK%p?2ka~4ZY~2cx}c^HvLR4z7Rq@b$zo?v zKxD%8i(OmZYIP4$uJkI`yxJC%TpeD(W3PZk8#2`uR5|#4eYOS6k74fAxrnc7_{@3b z6;Y=3F#eQwLyL*NVo;mvk0(2AeHw3H7>WBmr9*ss1oa&0F4I}ZMDDm<^ULEb1ACFtHfFROD*8+35K zwhF8N&Gkfz*H(~DspZXOk}#ZE^{Fnn{$*RnbV4&W(i!CuKxo|2ah36YNcf*MJhyU9 z4MJ*V18n#9|EyA^tyiRVH~}9?qL4K)q!0oC27wtuV6fdDPV_#Y^BV}jM-mYJzbOLX z925pKfI$t;8Dq{FA`H(XV5SC8BLoyG*KjKAZ-Ed>04Xr`zX$N5!p{AG>hBG1l)%Vn pd^iDs450WCG%u0D3DK1B;0R4?H+We6SL1ySfU; literal 2117 zcmai#dpy&98^?bevx#DE=QFos4MSE|hB1fCS+zykY;)`&tvjbQijmtPr>KbBi@L?o z^W>JpkLIwPl8S1vQYb5;a#(S%=lSb-{&=3(^}4Rl>$%e`xdr6~D^9b+; zfJ74jU|j%!b;*b|2LPwh0PukV0N7FhP>a3R>c3aAkO}j0cLg?oMrC_Vp=2T(>p?yx zQICJ68$SjI0B{vgSLdMk7az-CW@{6a*_{=yh|h)yT>~b)a+&_qFk^kUbfFj8%xv-o zE_3~D^QNz^GT~9JYmjS}p`mlmDCI^P(x5iWgGdw@-M=a-QmFB5ZLKlo_qPT*EQ>4O zmsK38wM;pEdzh2xj4-&Mi2wh^aDS8YE=m=`fIb^vk~z(Yl_yQgHmY0U71&`l-fEm^pQ*jP%j;*F~ps7M}tlVxf>GEO@SSx)waG)S>8t!feqpmp(W zI;h@zFj5w*{d#B_h(-m#>BE5BD`6O^3eEmtQY)fI&Ds+aKER~!} z_BgXIqd0#Bt}JEo&u?YrP;$xZS(AL?ju^K^htjXh`TL5GjI%G<3#DUa{uHfA?Y{7? zWApb<%Nvn{*xhBSGf?L28w{#%zPi1>ypcehvERt6=U=zN^)Ahyqu~YF@7f$NhaR4- zLGkE^X2uY)8jDcOKN4FAMVHk#681V`L8Q=27*mI?M!v44A5tZQm{><{>hZqnFQ)P< ztqp2qX0Fr{jWqQmjbo?~P{%1$q1xQJeG*>*J8?Wv~YW3!A|m z69+EP38g_X(YMPFLPw}kE#!-oQ*v7G8p5&{{j1YuV|6fIA?b`vtn%|}4k2|f!f!y>{zAj;^QkRg@6@H6IlB8kO^^q&WTH^KauPM8#?*Gunas8~;h*+sg4T&%FK*kAU)9!niu-yO z?bV_?pd)Sv`!f|UzU!-hXlE4>taI2agASWGFTV<`mNX&GVkZ#-@5T*{#O)=DDBx^q z*!FD`gItr&gHRFs%C>Fb9un_d8vX)hY$#k9e(_dP?H$S!_l^9%M(vaPS(oKo%TF6w zIn60m1>e8N-ItH)xjWzKnDJ3hLjyU}GM;p?iL;!u&L2fU@OKb-6tR7 zzp3tk#GgUBm#p~5?Q~=L?dWzXMflE+*j(SlWLvc9RV7`~h4n_)8$XBVDCsk^xaoIb z_h>N{Dx_XIVdICJkc{HMvn)Wh))=!A!_Sq=^QQ1uAhy7qzW}$|Q!}@?!1W2uCcBtY zCYPR)x@OF{(3VoeW3m`qZ7t@550QTWRYv&hxt$O5PFXXv!7I?*{S!$_(_6}H!LHiQ zf~rO+GtOY0DVVoAYB11*e(RIzC`fE|5JsVJ>Qk4cb5tuE;LFPiYR}D$U#q1>I^|Hu zMBbzqLtabms2Im$n;$`ucLDd}NO786v~G(`d}gh@g6-o%~Ntg7jbq6WlVi2#}gPOuAsp)yJHeqVrVY3HZ@)j%x*Gju? z_d0e>s%j-Y;2TOfb{FE2)JGgFqIAqUr@eq?HS=-3-Ln(fbqNqh*k^}QZS%-2e|=Au zZ;pd=9rVZ6&VDWuARg=LW+CyBRI7oI!E-6ocDc5ssvddOBkzMg*=x;?i6QJFUK}5X zDEQ50n0-^Yp+Vou*b6Nfi2mOmlsPI4t)!U>iswef5~nm~5=cxsmC3-;;usPEY|z#= z7U-Q8Hkcr5YwS*Itevd|dKVUrR-4PY_8-8p6ZEL?g#QN666W8)W4!Mz383-wLg0yT n<{4@n18}CF2xI7b9-~Gu0vJ?!!bv{kr=0_y1RvLWJSFXK=fIn( diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back.png index b971088cb60b0d94d0daac2e16db0b747c48e077..bd60cd65878a39a6389697084a3843d0ef727f81 100644 GIT binary patch delta 1413 zcmZXUdpOez7{`Akk#p2!%B`~$$9j?Ve#oTq=@_j#Z1=kvV(eXlELDdnoO_4 zmMqE584GNt<@7w*x)iA{$4&!)(pI9RA~P_?+yDT%-(9he1iE-e>=B_w*6LiCLm(KD z8c{LZUscoZ8p;UjRdE?mTyvvNuoBu02a+q$IXB^$v==(jf#K`(A$G2@o`&gSHBHqj zQr>8L=!}?ERk~L=I$DudJa#)N;^Hh~G;6chq1HCdws-MWxY=Se8~%7-NapurFd^`A zfnr+8bUtR)g8Qy=qsDvf>5Id>UH6i|9*dGeT7+%~DJogNkt;v)7kH=L9eB_%R8$F? zQQJ8ziq*bi(E9UoskCp7Wmr?4jD9E*ReX)eptu|~H1Km|O?B{M_@yIKPVOFY%^h<- z984Id+Sx13>v4`kZrGO(pUH}JUre$z5`H%}vR9p26;CwF8Z`3OGmR$kzon!P667L%F5W+>0^Dc!BByr6f!(Ze_G z5(LXxTAvAG{z^j1>RETmeWk%WPiR$538ZU9l2tIQ2oTy2DX5QwuLrzE>1N+8Dk)MM zVmqp6`5s}e7Y;t*e6&KRNVIGeK{_!sRzoUbb3p-&Hf;Zd(tbV@;jF>U)4H%}8e8yq z9Q5+hps|Xey^;PStn;+YtP?aSp`K9a?ZUQt1i3(qD^{vIn{WAaf$F6}lz8xOeth87 zv=;UJ69&K8LG0khJTJM$Cy+h@&sQQ5` zpB`XhLfAHp+_8^Dgz(T3yqC$GZz(I7Hfc`r^JNs~7?|Eq1|0y|Ve{`fwa2P5Ztkym zbG_kKF7tiin7!B4BdMHVi@bT=KR3!cNm1+aO#@6hD;?4K)sj7DuX?rC5s$IW4q68$;@Y>#EN39x*@$OLs+F!E4yx)FV=oNFc9HAg@*>v?^K!Bv`! zaG%HZvnk&Rrza3~xBg~O%&w-dJj z-ETlBkw!*^e51&K84M0*n!sTuW)=iE5{0lvAz&sjOB4)t#{>1-|BP5lXk-}UALBoQ zB?^xGSLkam@!Jx#zP<3FgwYa-@npa;ltLmyoFe1NiIn*0(-4dgGOk`?vBd$dI6U?? H`ee%Av6+cg literal 1830 zcma)7XIPU768=IW2}MFtl-@+}AZ$WtQp7-j1Qw7;7a^1oq=ybm2`rCEc28XcQWOED zgt7!(h(>AQggw|rxkVs!BP$@%vVsUKAs6p{-Tk=dW}cb%d1vOG`8kYBUT()_)nx$y zIF83*2@~ec@c5S8VfZl#2c;6+oB;MK%~c7}jl>Z=0U+lr z08ono;6PHMz6XGGBmjIM0)TA=0Gvv$dhTN{X-G$SxM2bD7q9JMl}IWw$+)0Y3F7`0 zuETK`0N~gz9*gnKn%S7c(>47x|NckP%d}mt97Cgs8){Lk`yE%yKKq9lVGHC`k$x6^ zQ94Jada%T(|9F6+xALd@B8aDj(xCC(ktWpXl8O;58oi&r*H_Lib*T7ywEj;{AKYTT znZ47^|I}XK*Ymku8vTD6xFyt@$i_Wv`O6tkzW37S)>vZS^%@cYN- z*fr*%RuD~)>O_EEM)x^^qCh^T!;O$H&swo$X1LFot=i!o@dv?UEjzt+qtR|OK^JE1 z7Bjzmr?q~5m2xGgpy@X1Wptyn@T%Umd-aZIZs62C+xb?LYw54s(l~k;7 zLSG&8sC1|P+tFx0#NKrRU{mZikwEGT2sHyFuh7~z=0}QQ)b5&KxN>9hNb$? zNA+5{hDu1;bx5z26(~$mt!E*#_E6W9{fX1uu}$R{nov>t&f}b>gr>iB5(-omwfwLykH)&^(CIUqk2>r8)?eIWH*9L65lpd^4wy13-7-72`fONiQ6jg*fItdCwCn2iosldHhh<`z#qILqM0gEFObmbdyrS<2~LF2@>ol3LxapBIMIt$j>%nx~w|R~%-NUP6(fCt$Y)%HmrEb1C@O@j7 z`N)&Gd}o|hIA-iR)X&j-30;XO&SP4++t&GX{$p*q1}9GOw|AJZ*<_PmXrzk2nO~?h z{^_@%->cl39lgOxAQo#0N{yqgFjTT|E4?D^V$RXh^-ZvqGI;&+z_ZO?Izw+8QC*hJ5V|qHZVS~EkH%ABcX9&9znLFaW&3eY>Mn&9e-o( zXI-(m(*jNhWD=wEWZ-d7eY>1$EDN?j=srUttMG8Yiyq2{0L!Ko-&x;jAsVYFb~~u|=<(I$ybEzrLl1XEZ^^3f+5#=L^IBgZp!-tqd;}p8w7bw;czohchcUPu z)KMj4_t%E>kgdLVDSNJ#uRnBcIjK*L5>k`03#L)h919 zB7z6@`KLR?A_lApKl8N0>Kt;93YAXMyO&1(M6LA-R zeIKJM+sG7>1ORg+${c~TM4*tqD3mSI(iVwEAgyhYNYv{^oBt3b zBt>0|&i-$Kg{`^OzXg3hYD9@Z?aPJBNzrMU;VC2l6O|M}(#0o)lSu?pcvLoJnDpge N0eBZLEZd1#^iSW5HoE`- diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_land.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_land.png index d2d78429f1f6c908d48d97f0c54056e9335d87db..5272c91a21791776910539a7cf83a6dbb47a1012 100644 GIT binary patch delta 1402 zcmZqSo60>wvYv&3fg!STAv=&_O7eDhVff$Pe`Y_B$64SJSbI<$ofBu4S@!ICV%oNsiJLdi z_PliHzwz`9H)sC*KmYslNP}Y%$vuWxi5c5swwrUcPqTC6EB(J?o#-xJ`Gy9Kzr}k? zLQ}lni>lfh2se^(S2ChSc=*1bwLa$!@ zv6c(`%j;?egeW`p%TuR$m`sZn|)jn#rQ+m&BjdRQ`NZt93cZiGfk^_m4S3 zrA$ocJ}A#Fj`gyTGU?!8{Lu1ocI@ogn^YeN^cOVRo(Vj#`Gkq>&TE@FRuptQvh6r0 z>hx%9*ldN(st+3G)*N(9)7W%CqILtAc=Ejevy4nR8|fG%P>C=5;3M_~x1a zEzWH}ZN6j;=aIQt_J2hpRTdoZ{B?6i$qpNfBWB+|@UT}jx~~+xblPV9XYVNKC!7i+SluIa{x8BTw$u4%E* z-*j!_RMVy0w`Qz!{k(AZ`^pIBSr=9u+p_;_-umW;7ml7UXPNU%$y&gaueeX)U%H*= zjxV+W582HwEI3ln81?O8-?3tL0mU@SqoUiCmRTHXubjVphsosEBAhQD7EbqP;(WOC z=xv^jogJR{o>~NMXE)Jh?Fh6vv^ja_`qDTv0U?7Evwb#pG8NBxs?i%dW3KdBrPJDt zXdBK=}|>jO19 zF9|F7eUd2G2zr(tbuI6Da(nY`I<}ZfM&2i5x&c1zms($ME z!=VMdwa27Gj6|m{_vnmEn|XNi%*ejN!k@fnqploa+qR+M{3LzB(q)>Pcz?|OzVlXI z@_sSSl;q9v{oCC){ui6xo&c?uz!xv30>`i92(hKBzxt}CBh$R=KIsB37TYh)H; zXli9_X=QAnYhZ3=V6fiTsu`$X6{J5Uu_Va>QWe}Xi&D$;i?WLqoP$jZw>~xls$=kU^>bP0l+XkK3$ccs literal 1796 zcmb7FYgm#A7XD0y2F5Wn)-Y>oH*F;?6p}R5fxMxksfm*J3y@~s6|W_lcqzxs9F@sI zncOM!KA@ZCgh^#B&`=Ij7$e~#LoJ2%zJ7x6CV zlGN<-?{pfBwgUj|rxXX0Puj~Bsas?q)kv(A|0u1C5l&)qsXkYAF*LvNtAz`q@9t0h zSsUdQbqN)?C)xOz*4O(`S_yZ26P|I7Igqk)XiZi^r@#-c(E$Nv`1+{vAGm$jwylR3 zSA>VTr*MQtxn_S1Gge-`VLPAwo?ZC=SXGUn89W?gZg z@WOSP>MV0T{O&>2<#Py52u|>Z*W^Qq=E~Ts*bkr#(`12^-ExbVHsfyqe z8a-u|W~Q4b>n`F12{`H3i0p?F7UvVmpVH%zG4;LOmnd znWx5#XD^BVcDiBByJhkxg&dgf_j`3D=Q0!mk)Sn?a{q!k+4En2kvwiJ94G5hWK#-J zpI(}N$chJNBN{}4e&7cm!k!zlo)nY1Hr^Z#zO)$s!h=yhsY%WaFO+#hry+mn zhFHP#e3W}Vs!`)M_!^8;UD!GTWp;blqK8&Vp?N%Kj>t3H!86E@e+CUGE?2p7KRk z*lInBO%HOJTPiK$;I$mvONSY0x>XP54C~9=hT0uKjqwLw((%7O*irk!d}{Xl(Snhz z@YKtg?!Ic~-Y(afOUq>F0+bP|8Gke$JnzjTo zzkmqQ+^nLr4$+WA7;)EX(heGWwN0{*NgV|8PZ8MIR}wo*LC!t+EFyhSvOF_-O}Bs-KPs6NC%sKvDY^IwH2|7r zjo?5r=%~7HUs8Yxb{oI(=u1CJSxr@|9^{tA!t0mp6Qw6%CQ4Qwm=u-B@+qN575nY_ zftEaXa7|YIxLL1klP+sFsF6MES3aIwJNIs3AeH}S4h)05AYG_i@w*okm6boXbDr94 zaQH)!F3PC3^WI1fb*+;rt|7W$4mBHQ!w-l8wt)k?h+lOVpHaRH4q{Pyw%n3UZ*ydK zVaqTccqJ&I(;h)YzT#E;nezPx_=zW;Tq=h3esfC@Ohg+XH}oN-zkQWsVq+fNrFcV_ zqS84j-muA^txxKVox&Vk&u!(7VFiZ0KYZ&hPHITjjZ_-F!$8BO(=?4M6kTq6Fl+6( zNFZvsHx$B!()G2?G~64qxR3bGJL~dVrQHrCQw4w z%qVzdw|rK~KWi1myj^+5x$+4@Y}xWfD=cmAbE9ed9C_wuu{?96VaC8s`=15f$GlvEhs;KSkzyt^-nbkg#o^Q&CY+abi~FwOgwZ zD!Bm*kVbisr2cxxD-cx!;_x2SRcC~lc zwfdYT5Etwvc%&>__WT*+S#9COIV^5e{V0|nsY_QfgC#=n}1RE??oPD