/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.os.storage;

import android.content.Context;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.test.AndroidTestCase;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;

import junit.framework.Assert;

public class AsecTests extends AndroidTestCase {
    private static final boolean localLOGV = true;
    public static final String TAG="AsecTests";

    void failStr(String errMsg) {
        Log.w(TAG, "errMsg="+errMsg);
    }

    void failStr(Exception e) {
        Log.w(TAG, "e.getMessage="+e.getMessage());
        Log.w(TAG, "e="+e);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
        cleanupContainers();
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
        if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
        cleanupContainers();
    }

    private void cleanupContainers() throws RemoteException {
        IMountService ms = getMs();
        String[] containers = ms.getSecureContainerList();

        for (int i = 0; i < containers.length; i++) {
            if (containers[i].startsWith("com.android.unittests.AsecTests.")) {
                ms.destroySecureContainer(containers[i], true);
            }
        }
    }

    private boolean containerExists(String localId) throws RemoteException {
        IMountService ms = getMs();
        String[] containers = ms.getSecureContainerList();
        String fullId = "com.android.unittests.AsecTests." + localId;

        for (int i = 0; i < containers.length; i++) {
            if (containers[i].equals(fullId)) {
                return true;
            }
        }
        return false;
    }

    private int createContainer(String localId, int size, String key) throws RemoteException {
        Assert.assertTrue(isMediaMounted());
        String fullId = "com.android.unittests.AsecTests." + localId;

        IMountService ms = getMs();
        return ms.createSecureContainer(fullId, size, "fat", key, android.os.Process.myUid());
    }

    private int mountContainer(String localId, String key) throws RemoteException {
        Assert.assertTrue(isMediaMounted());
        String fullId = "com.android.unittests.AsecTests." + localId;

        IMountService ms = getMs();
        return ms.mountSecureContainer(fullId, key, android.os.Process.myUid());
    }

    private int renameContainer(String localId1, String localId2) throws RemoteException {
        Assert.assertTrue(isMediaMounted());
        String fullId1 = "com.android.unittests.AsecTests." + localId1;
        String fullId2 = "com.android.unittests.AsecTests." + localId2;

        IMountService ms = getMs();
        return ms.renameSecureContainer(fullId1, fullId2);
    }

    private int unmountContainer(String localId, boolean force) throws RemoteException {
        Assert.assertTrue(isMediaMounted());
        String fullId = "com.android.unittests.AsecTests." + localId;

        IMountService ms = getMs();
        return ms.unmountSecureContainer(fullId, force);
    }

    private int destroyContainer(String localId, boolean force) throws RemoteException {
        Assert.assertTrue(isMediaMounted());
        String fullId = "com.android.unittests.AsecTests." + localId;

        IMountService ms = getMs();
        return ms.destroySecureContainer(fullId, force);
    }

    private boolean isContainerMounted(String localId) throws RemoteException {
        Assert.assertTrue(isMediaMounted());
        String fullId = "com.android.unittests.AsecTests." + localId;

        IMountService ms = getMs();
        return ms.isSecureContainerMounted(fullId);
    }

    private IMountService getMs() {
        IBinder service = ServiceManager.getService("mount");
        if (service != null) {
            return IMountService.Stub.asInterface(service);
        } else {
            Log.e(TAG, "Can't get mount service");
        }
        return null;
    }

    private boolean isMediaMounted() {
        try {
        String mPath = Environment.getExternalStorageDirectory().toString();
        String state = getMs().getVolumeState(mPath);
        return Environment.MEDIA_MOUNTED.equals(state);
        } catch (RemoteException e) {
            failStr(e);
            return false;
        }
    }

    public void testCreateContainer() {
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testCreateContainer", 4, "none"));
            Assert.assertEquals(true, containerExists("testCreateContainer"));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testCreateMinSizeContainer() {
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testCreateContainer", 1, "none"));
            Assert.assertEquals(true, containerExists("testCreateContainer"));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testCreateZeroSizeContainer() {
        try {
            Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
                    createContainer("testCreateZeroContainer", 0, "none"));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testCreateDuplicateContainer() {
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testCreateDupContainer", 4, "none"));

            Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
                    createContainer("testCreateDupContainer", 4, "none"));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testDestroyContainer() {
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testDestroyContainer", 4, "none"));
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    destroyContainer("testDestroyContainer", false));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testMountContainer() {
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testMountContainer", 4, "none"));

            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    unmountContainer("testMountContainer", false));

            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    mountContainer("testMountContainer", "none"));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testMountBadKey() {
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testMountBadKey", 4, "00000000000000000000000000000000"));

            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    unmountContainer("testMountBadKey", false));

            Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
                    mountContainer("testMountContainer", "000000000000000000000000000000001"));

            Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
                    mountContainer("testMountContainer", "none"));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testNonExistPath() {
        IMountService ms = getMs();
        try {
            String path = ms.getSecureContainerPath("jparks.broke.it");
            failStr(path);
        } catch (IllegalArgumentException e) {
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testUnmountBusyContainer() {
        IMountService ms = getMs();
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testUnmountBusyContainer", 4, "none"));

            String path = ms.getSecureContainerPath("com.android.unittests.AsecTests.testUnmountBusyContainer");

            File f = new File(path, "reference");
            FileOutputStream fos = new FileOutputStream(f);

            Assert.assertEquals(StorageResultCode.OperationFailedStorageBusy,
                    unmountContainer("testUnmountBusyContainer", false));

            fos.close();
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    unmountContainer("testUnmountBusyContainer", false));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testDestroyBusyContainer() {
        IMountService ms = getMs();
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testDestroyBusyContainer", 4, "none"));

            String path = ms.getSecureContainerPath("com.android.unittests.AsecTests.testDestroyBusyContainer");

            File f = new File(path, "reference");
            FileOutputStream fos = new FileOutputStream(f);

            Assert.assertEquals(StorageResultCode.OperationFailedStorageBusy,
                    destroyContainer("testDestroyBusyContainer", false));

            fos.close();
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    destroyContainer("testDestroyBusyContainer", false));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testRenameContainer() {
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testRenameContainer.1", 4, "none"));

            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    unmountContainer("testRenameContainer.1", false));

            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    renameContainer("testRenameContainer.1", "testRenameContainer.2"));

            Assert.assertEquals(false, containerExists("testRenameContainer.1"));
            Assert.assertEquals(true, containerExists("testRenameContainer.2"));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testRenameSrcMountedContainer() {
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testRenameContainer.1", 4, "none"));

            Assert.assertEquals(StorageResultCode.OperationFailedStorageMounted,
                    renameContainer("testRenameContainer.1", "testRenameContainer.2"));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testRenameDstMountedContainer() {
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testRenameContainer.1", 4, "none"));

            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    unmountContainer("testRenameContainer.1", false));

            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testRenameContainer.2", 4, "none"));

            Assert.assertEquals(StorageResultCode.OperationFailedStorageMounted,
                    renameContainer("testRenameContainer.1", "testRenameContainer.2"));
        } catch (Exception e) {
            failStr(e);
        }
    }

    public void testContainerSize() {
        IMountService ms = getMs();
        try {
            Assert.assertEquals(StorageResultCode.OperationSucceeded,
                    createContainer("testContainerSize", 1, "none"));
            String path = ms.getSecureContainerPath("com.android.unittests.AsecTests.testUnmountBusyContainer");

            byte[] buf = new byte[4096];
            File f = new File(path, "reference");
            FileOutputStream fos = new FileOutputStream(f);
            for (int i = 0; i < (1024 * 1024); i+= buf.length) {
                fos.write(buf);
            }
            fos.close();
        } catch (Exception e) {
            failStr(e);
        }
    }

    /*------------ Tests for unmounting volume ---*/
    public final long MAX_WAIT_TIME=120*1000;
    public final long WAIT_TIME_INCR=20*1000;
    boolean getMediaState() {
        try {
        String mPath = Environment.getExternalStorageDirectory().toString();
        String state = getMs().getVolumeState(mPath);
        return Environment.MEDIA_MOUNTED.equals(state);
        } catch (RemoteException e) {
            return false;
        }
    }

    boolean mountMedia() {
        if (getMediaState()) {
            return true;
        }
        try {
        String mPath = Environment.getExternalStorageDirectory().toString();
        int ret = getMs().mountVolume(mPath);
        return ret == StorageResultCode.OperationSucceeded;
        } catch (RemoteException e) {
            return false;
        }
    }

    class StorageListener extends StorageEventListener {
        String oldState;
        String newState;
        String path;
        private boolean doneFlag = false;

        public void action() {
            synchronized (this) {
                doneFlag = true;
                notifyAll();
            }
        }

        public boolean isDone() {
            return doneFlag;
        }

        @Override
        public void onStorageStateChanged(String path, String oldState, String newState) {
            if (localLOGV) Log.i(TAG, "Storage state changed from " + oldState + " to " + newState);
            this.oldState = oldState;
            this.newState = newState;
            this.path = path;
            action();
        }
    }

    private boolean unmountMedia() {
        if (!getMediaState()) {
            return true;
        }
        String path = Environment.getExternalStorageDirectory().toString();
        StorageListener observer = new StorageListener();
        StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
        sm.registerListener(observer);
        try {
            // Wait on observer
            synchronized(observer) {
                getMs().unmountVolume(path, false);
                long waitTime = 0;
                while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
                    observer.wait(WAIT_TIME_INCR);
                    waitTime += WAIT_TIME_INCR;
                }
                if(!observer.isDone()) {
                    throw new Exception("Timed out waiting for packageInstalled callback");
                }
                return true;
            }
        } catch (Exception e) {
            return false;
        } finally {
            sm.unregisterListener(observer);
        }
    }
    public void testUnmount() {
        boolean oldStatus = getMediaState();
        Log.i(TAG, "oldStatus="+oldStatus);
        try {
            // Mount media firsts
            if (!getMediaState()) {
                mountMedia();
            }
            assertTrue(unmountMedia());
        } finally {
            // Restore old status
            boolean currStatus = getMediaState();
            if (oldStatus != currStatus) {
                if (oldStatus) {
                    // Mount media
                    mountMedia();
                } else {
                    unmountMedia();
                }
            }
        }
    }

    class MultipleStorageLis extends StorageListener {
        int count = 0;
        public void onStorageStateChanged(String path, String oldState, String newState) {
            count++;
            super.action();
        }
    }
    /*
     * This test invokes unmount multiple time and expects the call back
     * to be invoked just once.
     */
    public void testUnmountMultiple() {
        boolean oldStatus = getMediaState();
        StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
        MultipleStorageLis observer = new MultipleStorageLis();
        try {
            // Mount media firsts
            if (!getMediaState()) {
                mountMedia();
            }
            String path = Environment.getExternalStorageDirectory().toString();
            sm.registerListener(observer);
            // Wait on observer
            synchronized(observer) {
                for (int i = 0; i < 5; i++) {
                    getMs().unmountVolume(path, false);
                }
                long waitTime = 0;
                while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
                    observer.wait(WAIT_TIME_INCR);
                    waitTime += WAIT_TIME_INCR;
                }
                if(!observer.isDone()) {
                    failStr("Timed out waiting for packageInstalled callback");
                }
            }
            assertEquals(observer.count, 1);
        } catch (Exception e) {
            failStr(e);
        } finally {
            sm.unregisterListener(observer);
            // Restore old status
            boolean currStatus = getMediaState();
            if (oldStatus != currStatus) {
                if (oldStatus) {
                    // Mount media
                    mountMedia();
                } else {
                    unmountMedia();
                }
            }
        }
    }
    
    class ShutdownObserver extends  IMountShutdownObserver.Stub{
        private boolean doneFlag = false;
        int statusCode;

        public void action() {
            synchronized (this) {
                doneFlag = true;
                notifyAll();
            }
        }

        public boolean isDone() {
            return doneFlag;
        }
        public void onShutDownComplete(int statusCode) throws RemoteException {
            this.statusCode = statusCode;
            action();
        }
        
    }

    boolean invokeShutdown() {
        IMountService ms = getMs();
        ShutdownObserver observer = new ShutdownObserver();
        synchronized (observer) {
            try {
                ms.shutdown(observer);
                return true;
            } catch (RemoteException e) {
                failStr(e);
            }
        }
        return false;
    }

    public void testShutdown() {
        boolean oldStatus = getMediaState();
        try {
            // Mount media firsts
            if (!getMediaState()) {
                mountMedia();
            }
            assertTrue(invokeShutdown());
        } finally {
            // Restore old status
            boolean currStatus = getMediaState();
            if (oldStatus != currStatus) {
                if (oldStatus) {
                    // Mount media
                    mountMedia();
                } else {
                    unmountMedia();
                }
            }
        }
    }

    /*
     * This test invokes unmount multiple time and expects the call back
     * to be invoked just once.
     */
    public void testShutdownMultiple() {
        boolean oldStatus = getMediaState();
        try {
            // Mount media firsts
            if (!getMediaState()) {
                mountMedia();
            }
            IMountService ms = getMs();
            ShutdownObserver observer = new ShutdownObserver();
            synchronized (observer) {
                try {
                    ms.shutdown(observer);
                    for (int i = 0; i < 4; i++) {
                        ms.shutdown(null);
                    }
                } catch (RemoteException e) {
                    failStr(e);
                }
            }
        } finally {
            // Restore old status
            boolean currStatus = getMediaState();
            if (oldStatus != currStatus) {
                if (oldStatus) {
                    // Mount media
                    mountMedia();
                } else {
                    unmountMedia();
                }
            }
        }
    }

}
