Merge "Add test for ascent and descent of serif and mono fonts"
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 3ab60f8..adfdfba 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -409,24 +409,26 @@
         if (alias == null) {
             throw new NullPointerException("alias == null");
         }
-        KeyChainConnection keyChainConnection = bind(context.getApplicationContext());
-        try {
-            final IKeyChainService keyChainService = keyChainConnection.getService();
-            final String keyId = keyChainService.requestPrivateKey(alias);
-            if (keyId == null) {
-                return null;
-            }
-            return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
-                    KeyStore.getInstance(), keyId, KeyStore.UID_SELF);
+
+        final String keyId;
+        try (KeyChainConnection keyChainConnection = bind(context.getApplicationContext())) {
+            keyId = keyChainConnection.getService().requestPrivateKey(alias);
         } catch (RemoteException e) {
             throw new KeyChainException(e);
         } catch (RuntimeException e) {
             // only certain RuntimeExceptions can be propagated across the IKeyChainService call
             throw new KeyChainException(e);
-        } catch (UnrecoverableKeyException e) {
-            throw new KeyChainException(e);
-        } finally {
-            keyChainConnection.close();
+        }
+
+        if (keyId == null) {
+            return null;
+        } else {
+            try {
+                return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+                        KeyStore.getInstance(), keyId, KeyStore.UID_SELF);
+            } catch (RuntimeException | UnrecoverableKeyException e) {
+                throw new KeyChainException(e);
+            }
         }
     }
 
@@ -453,16 +455,25 @@
         if (alias == null) {
             throw new NullPointerException("alias == null");
         }
-        KeyChainConnection keyChainConnection = bind(context.getApplicationContext());
-        try {
-            IKeyChainService keyChainService = keyChainConnection.getService();
 
-            final byte[] certificateBytes = keyChainService.getCertificate(alias);
+        final byte[] certificateBytes;
+        final byte[] certChainBytes;
+        try (KeyChainConnection keyChainConnection = bind(context.getApplicationContext())) {
+            IKeyChainService keyChainService = keyChainConnection.getService();
+            certificateBytes = keyChainService.getCertificate(alias);
             if (certificateBytes == null) {
                 return null;
             }
+            certChainBytes = keyChainService.getCaCertificates(alias);
+        } catch (RemoteException e) {
+            throw new KeyChainException(e);
+        } catch (RuntimeException e) {
+            // only certain RuntimeExceptions can be propagated across the IKeyChainService call
+            throw new KeyChainException(e);
+        }
+
+        try {
             X509Certificate leafCert = toCertificate(certificateBytes);
-            final byte[] certChainBytes = keyChainService.getCaCertificates(alias);
             // If the keypair is installed with a certificate chain by either
             // DevicePolicyManager.installKeyPair or CertInstaller, return that chain.
             if (certChainBytes != null && certChainBytes.length != 0) {
@@ -486,15 +497,8 @@
                 List<X509Certificate> chain = store.getCertificateChain(leafCert);
                 return chain.toArray(new X509Certificate[chain.size()]);
             }
-        } catch (CertificateException e) {
+        } catch (CertificateException | RuntimeException e) {
             throw new KeyChainException(e);
-        } catch (RemoteException e) {
-            throw new KeyChainException(e);
-        } catch (RuntimeException e) {
-            // only certain RuntimeExceptions can be propagated across the IKeyChainService call
-            throw new KeyChainException(e);
-        } finally {
-            keyChainConnection.close();
         }
     }
 
diff --git a/libs/hwui/ShadowTessellator.h b/libs/hwui/ShadowTessellator.h
index 5f4c9c5..2eaf187 100644
--- a/libs/hwui/ShadowTessellator.h
+++ b/libs/hwui/ShadowTessellator.h
@@ -80,8 +80,6 @@
 
     static Vector2 centroid2d(const Vector2* poly, int polyLength);
 
-    static bool isClockwise(const Vector2* polygon, int len);
-
     static Vector2 calculateNormal(const Vector2& p1, const Vector2& p2);
 
     static int getExtraVertexNumber(const Vector2& vector1, const Vector2& vector2,
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index 760d814..e2ee5bf 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -302,21 +302,6 @@
 }
 
 /**
- * Make the polygon turn clockwise.
- *
- * @param polygon the polygon as a Vector2 array.
- * @param len the number of points of the polygon
- */
-void SpotShadow::makeClockwise(Vector2* polygon, int len) {
-    if (polygon == nullptr  || len == 0) {
-        return;
-    }
-    if (!ShadowTessellator::isClockwise(polygon, len)) {
-        reverse(polygon, len);
-    }
-}
-
-/**
  * Reverse the polygon
  *
  * @param polygon the polygon as a Vector2 array
diff --git a/libs/hwui/SpotShadow.h b/libs/hwui/SpotShadow.h
index 62a7e5d..6108bb6 100644
--- a/libs/hwui/SpotShadow.h
+++ b/libs/hwui/SpotShadow.h
@@ -54,7 +54,6 @@
     static void quicksortX(Vector2* points, int low, int high);
 
     static bool testPointInsidePolygon(const Vector2 testPoint, const Vector2* poly, int len);
-    static void makeClockwise(Vector2* polygon, int len);
     static void reverse(Vector2* polygon, int len);
 
     static void generateTriangleStrip(bool isCasterOpaque, float shadowStrengthScale,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3174c70..64dea57 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -630,7 +630,11 @@
                 MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);
         if (maxVolume != MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]) {
             MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = maxVolume;
-            AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = (maxVolume * 3) / 4;
+            if (isPlatformTelevision()) {
+                AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = maxVolume / 4;
+            } else {
+                AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = (maxVolume * 3) / 4;
+            }
         }
 
         sSoundEffectVolumeDb = context.getResources().getInteger(
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 872ebe8..1cf5097 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -16,13 +16,18 @@
 
 package com.android.server.am;
 
+import android.app.IUserSwitchObserver;
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.UserInfo;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserManagerInternal;
 import android.test.AndroidTestCase;
@@ -35,16 +40,30 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.server.am.ActivityManagerService.CONTINUE_USER_SWITCH_MSG;
+import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
+import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
+import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
+import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
+import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
 public class UserControllerTest extends AndroidTestCase {
+    private static final int TEST_USER_ID = 10;
     private static String TAG = UserControllerTest.class.getSimpleName();
     private UserController mUserController;
     private TestInjector mInjector;
@@ -54,7 +73,7 @@
         super.setUp();
         mInjector = new TestInjector(getContext());
         mUserController = new UserController(mInjector);
-        setUpUser(10, 0);
+        setUpUser(TEST_USER_ID, 0);
     }
 
     @Override
@@ -65,13 +84,100 @@
     }
 
     public void testStartUser() throws RemoteException {
-        mUserController.startUser(10, true);
-
+        mUserController.startUser(TEST_USER_ID, true);
         Mockito.verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt());
         Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
         List<String> expectedActions = Arrays.asList(Intent.ACTION_USER_STARTED,
                 Intent.ACTION_USER_SWITCHED, Intent.ACTION_USER_STARTING);
         assertEquals(expectedActions, getActions(mInjector.sentIntents));
+        Set<Integer> expectedCodes = new HashSet<>(
+                Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG,
+                        SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG));
+        Set<Integer> actualCodes = mInjector.handler.getMessageCodes();
+        assertEquals("Unexpected message sent", expectedCodes, actualCodes);
+        Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        UserState userState = (UserState) reportMsg.obj;
+        assertNotNull(userState);
+        assertEquals(TEST_USER_ID, userState.mHandle.getIdentifier());
+        assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state);
+        assertEquals("Unexpected old user id", 0, reportMsg.arg1);
+        assertEquals("Unexpected new user id", TEST_USER_ID, reportMsg.arg2);
+    }
+
+    public void testDispatchUserSwitch() throws RemoteException {
+        // Prepare mock observer and register it
+        IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
+        when(observer.asBinder()).thenReturn(new Binder());
+        doAnswer(invocation -> {
+            IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1];
+            callback.sendResult(null);
+            return null;
+        }).when(observer).onUserSwitching(anyInt(), any());
+        mUserController.registerUserSwitchObserver(observer, "mock");
+        // Start user -- this will update state of mUserController
+        mUserController.startUser(TEST_USER_ID, true);
+        Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        UserState userState = (UserState) reportMsg.obj;
+        int oldUserId = reportMsg.arg1;
+        int newUserId = reportMsg.arg2;
+        // Call dispatchUserSwitch and verify that observer was called only once
+        mInjector.handler.clearAllRecordedMessages();
+        mUserController.dispatchUserSwitch(userState, oldUserId, newUserId);
+        Mockito.verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any());
+        Set<Integer> expectedCodes = Collections.singleton(CONTINUE_USER_SWITCH_MSG);
+        Set<Integer> actualCodes = mInjector.handler.getMessageCodes();
+        assertEquals("Unexpected message sent", expectedCodes, actualCodes);
+        Message conMsg = mInjector.handler.getMessageForCode(CONTINUE_USER_SWITCH_MSG);
+        assertNotNull(conMsg);
+        userState = (UserState) conMsg.obj;
+        assertNotNull(userState);
+        assertEquals(TEST_USER_ID, userState.mHandle.getIdentifier());
+        assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state);
+        assertEquals("Unexpected old user id", 0, conMsg.arg1);
+        assertEquals("Unexpected new user id", TEST_USER_ID, conMsg.arg2);
+    }
+
+    public void testDispatchUserSwitchBadReceiver() throws RemoteException {
+        // Prepare mock observer which doesn't notify the callback and register it
+        IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
+        when(observer.asBinder()).thenReturn(new Binder());
+        mUserController.registerUserSwitchObserver(observer, "mock");
+        // Start user -- this will update state of mUserController
+        mUserController.startUser(TEST_USER_ID, true);
+        Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        UserState userState = (UserState) reportMsg.obj;
+        int oldUserId = reportMsg.arg1;
+        int newUserId = reportMsg.arg2;
+        // Call dispatchUserSwitch and verify that observer was called only once
+        mInjector.handler.clearAllRecordedMessages();
+        mUserController.dispatchUserSwitch(userState, oldUserId, newUserId);
+        Mockito.verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any());
+        // Verify that CONTINUE_USER_SWITCH_MSG is not sent (triggers timeout)
+        Set<Integer> actualCodes = mInjector.handler.getMessageCodes();
+        assertTrue("No messages should be sent", actualCodes.isEmpty());
+    }
+
+    public void testContinueUserSwitch() throws RemoteException {
+        // Start user -- this will update state of mUserController
+        mUserController.startUser(TEST_USER_ID, true);
+        Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        UserState userState = (UserState) reportMsg.obj;
+        int oldUserId = reportMsg.arg1;
+        int newUserId = reportMsg.arg2;
+        mInjector.handler.clearAllRecordedMessages();
+        // Verify that continueUserSwitch worked as expected
+        mUserController.continueUserSwitch(userState, oldUserId, newUserId);
+        Mockito.verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
+        Set<Integer> expectedCodes = Collections.singleton(REPORT_USER_SWITCH_COMPLETE_MSG);
+        Set<Integer> actualCodes = mInjector.handler.getMessageCodes();
+        assertEquals("Unexpected message sent", expectedCodes, actualCodes);
+        Message msg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG);
+        assertNotNull(msg);
+        assertEquals("Unexpected userId", TEST_USER_ID, msg.arg1);
     }
 
     private void setUpUser(int userId, int flags) {
@@ -89,7 +195,7 @@
 
     private static class TestInjector extends UserController.Injector {
         final Object lock = new Object();
-        Handler handler;
+        TestHandler handler;
         HandlerThread handlerThread;
         UserManagerService userManagerMock;
         UserManagerInternal userManagerInternalMock;
@@ -102,7 +208,7 @@
             mCtx = ctx;
             handlerThread = new HandlerThread(TAG);
             handlerThread.start();
-            handler = new Handler(handlerThread.getLooper());
+            handler = new TestHandler(handlerThread.getLooper());
             userManagerMock = mock(UserManagerService.class);
             userManagerInternalMock = mock(UserManagerInternal.class);
             windowManagerMock = mock(WindowManagerService.class);
@@ -176,4 +282,41 @@
             Log.i(TAG, "startHomeActivityLocked " + userId);
         }
    }
+
+    private static class TestHandler extends Handler {
+        private final List<Message> mMessages = new ArrayList<>();
+
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        Set<Integer> getMessageCodes() {
+            Set<Integer> result = new LinkedHashSet<>();
+            for (Message msg : mMessages) {
+                result.add(msg.what);
+            }
+            return result;
+        }
+
+        Message getMessageForCode(int what) {
+            for (Message msg : mMessages) {
+                if (msg.what == what) {
+                    return msg;
+                }
+            }
+            return null;
+        }
+
+        void clearAllRecordedMessages() {
+            mMessages.clear();
+        }
+
+        @Override
+        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+            Message copy = new Message();
+            copy.copyFrom(msg);
+            mMessages.add(copy);
+            return super.sendMessageAtTime(msg, uptimeMillis);
+        }
+    }
 }
\ No newline at end of file