/*
 * 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 com.android.unit_tests.activity;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.test.PerformanceTestCase;
import android.util.Log;

class MyBadParcelable implements Parcelable {
    public MyBadParcelable() {
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeString("I am bad");
    }

    public int describeContents() {
        return 0;
    }

    public static final Parcelable.Creator<MyBadParcelable> CREATOR
            = new Parcelable.Creator<MyBadParcelable>() {
        public MyBadParcelable createFromParcel(Parcel in) {
            return new MyBadParcelable(in);
        }

        public MyBadParcelable[] newArray(int size) {
            return new MyBadParcelable[size];
        }
    };

    public MyBadParcelable(Parcel in) {
        String nm = in.readString();
    }
}

public class LaunchpadActivity extends Activity {
    public interface CallingTest extends PerformanceTestCase.Intermediates {
        public void startTiming(boolean realTime);
        public void addIntermediate(String name);
        public void addIntermediate(String name, long timeInNS);
        public void finishTiming(boolean realTime);
        public void activityFinished(int resultCode, Intent data,
                RuntimeException where);
    }

    // Also used as the Binder interface descriptor string in these tests
    public static final String LAUNCH = "com.android.unit_tests.activity.LAUNCH";

    public static final String FORWARD_RESULT =
            "com.android.unit_tests.activity.FORWARD_RESULT";
    public static final String RETURNED_RESULT =
            "com.android.unit_tests.activity.RETURNED_RESULT";

    public static final String BAD_PARCELABLE =
            "com.android.unit_tests.activity.BAD_PARCELABLE";

    public static final int LAUNCHED_RESULT = 1;
    public static final int FORWARDED_RESULT = 2;

    public static final String LIFECYCLE_BASIC =
            "com.android.unit_tests.activity.LIFECYCLE_BASIC";
    public static final String LIFECYCLE_SCREEN =
            "com.android.unit_tests.activity.LIFECYCLE_SCREEN";
    public static final String LIFECYCLE_DIALOG =
            "com.android.unit_tests.activity.LIFECYCLE_DIALOG";
    public static final String LIFECYCLE_FINISH_CREATE =
            "com.android.unit_tests.activity.LIFECYCLE_FINISH_CREATE";
    public static final String LIFECYCLE_FINISH_START =
            "com.android.unit_tests.activity.LIFECYCLE_FINISH_START";

    public static final String BROADCAST_REGISTERED =
            "com.android.unit_tests.activity.BROADCAST_REGISTERED";
    public static final String BROADCAST_LOCAL =
            "com.android.unit_tests.activity.BROADCAST_LOCAL";
    public static final String BROADCAST_REMOTE =
            "com.android.unit_tests.activity.BROADCAST_REMOTE";
    public static final String BROADCAST_ALL =
            "com.android.unit_tests.activity.BROADCAST_ALL";
    public static final String BROADCAST_REPEAT =
        "com.android.unit_tests.activity.BROADCAST_REPEAT";
    public static final String BROADCAST_MULTI =
            "com.android.unit_tests.activity.BROADCAST_MULTI";
    public static final String BROADCAST_ABORT =
            "com.android.unit_tests.activity.BROADCAST_ABORT";

    public static final String BROADCAST_STICKY1 =
            "com.android.unit_tests.activity.BROADCAST_STICKY1";
    public static final String BROADCAST_STICKY2 =
            "com.android.unit_tests.activity.BROADCAST_STICKY2";

    public static final String RECEIVER_REG = "receiver-reg";
    public static final String RECEIVER_LOCAL = "receiver-local";
    public static final String RECEIVER_REMOTE = "receiver-remote";
    public static final String RECEIVER_ABORT = "receiver-abort";

    public static final String DATA_1 = "one";
    public static final String DATA_2 = "two";

    public static final String ON_START = "onStart";
    public static final String ON_RESTART = "onRestart";
    public static final String ON_RESUME = "onResume";
    public static final String ON_FREEZE = "onSaveInstanceState";
    public static final String ON_PAUSE = "onPause";
    public static final String ON_STOP = "onStop";
    public static final String ON_DESTROY = "onDestroy";

    public static final String DO_FINISH = "finish";
    public static final String DO_LOCAL_SCREEN = "local-screen";
    public static final String DO_LOCAL_DIALOG = "local-dialog";

    private boolean mBadParcelable = false;

    private boolean mStarted = false;
    private long mStartTime;

    private int mResultCode = RESULT_CANCELED;
    private Intent mData = (new Intent()).setAction("No result received");
    private RuntimeException mResultStack = null;

    private String[] mExpectedLifecycle = null;
    private int mNextLifecycle;

    private String[] mExpectedReceivers = null;
    private int mNextReceiver;

    private String[] mExpectedData = null;
    private boolean[] mReceivedData = null;

    boolean mReceiverRegistered = false;

    private static CallingTest sCallingTest = null;

    public static void setCallingTest(CallingTest ct) {
        sCallingTest = ct;
    }

    public LaunchpadActivity() {
        mStartTime = System.currentTimeMillis();
    }

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        String action = getIntent().getAction();
        if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "CREATE lauchpad "
                + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
        if (LIFECYCLE_BASIC.equals(action)) {
            setExpectedLifecycle(new String[]{ON_START, ON_RESUME,
                    DO_FINISH, ON_PAUSE, ON_STOP, ON_DESTROY});
        } else if (LIFECYCLE_SCREEN.equals(action)) {
            setExpectedLifecycle(new String[]{ON_START, ON_RESUME,
                    DO_LOCAL_SCREEN, ON_FREEZE, ON_PAUSE, ON_STOP,
                    ON_RESTART, ON_START, ON_RESUME,
                    DO_FINISH, ON_PAUSE, ON_STOP, ON_DESTROY});
        } else if (LIFECYCLE_DIALOG.equals(action)) {
            setExpectedLifecycle(new String[]{ON_START, ON_RESUME,
                    DO_LOCAL_DIALOG, ON_FREEZE, ON_PAUSE, ON_RESUME,
                    DO_FINISH, ON_PAUSE, ON_STOP, ON_DESTROY});
        } else if (LIFECYCLE_FINISH_CREATE.equals(action)) {
            // This one behaves a little differently when running in a group.
            if (getParent() == null) {
                setExpectedLifecycle(new String[]{ON_DESTROY});
            } else {
                setExpectedLifecycle(new String[]{ON_START, ON_STOP, ON_DESTROY});
            }
            finish();
        } else if (LIFECYCLE_FINISH_START.equals(action)) {
            setExpectedLifecycle(new String[]{ON_START, DO_FINISH,
                    ON_STOP, ON_DESTROY});
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "START lauchpad "
                + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
        checkLifecycle(ON_START);
    }

    @Override
    protected void onRestart() {
        super.onStart();
        checkLifecycle(ON_RESTART);
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "RESUME lauchpad "
                + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
        checkLifecycle(ON_RESUME);

        if (!mStarted) {
            mStarted = true;

            mHandler.postDelayed(mTimeout, 5 * 1000);

            String action = getIntent().getAction();

            sCallingTest.startTiming(true);

            if (LAUNCH.equals(action)) {
                Intent intent = getIntent();
                intent.setFlags(0);
                intent.setComponent((ComponentName)
                        intent.getParcelableExtra("component"));
                //System.out.println("*** Launchpad is starting: comp=" + intent.component);
                startActivityForResult(intent, LAUNCHED_RESULT);
            } else if (FORWARD_RESULT.equals(action)) {
                Intent intent = getIntent();
                intent.setFlags(0);
                intent.setClass(this, LocalScreen.class);
                startActivityForResult(intent, FORWARDED_RESULT);
            } else if (BAD_PARCELABLE.equals(action)) {
                mBadParcelable = true;
                Intent intent = getIntent();
                intent.setFlags(0);
                intent.setClass(this, LocalScreen.class);
                startActivityForResult(intent, LAUNCHED_RESULT);
            } else if (BROADCAST_REGISTERED.equals(action)) {
                setExpectedReceivers(new String[]{RECEIVER_REG});
                registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED));
                sCallingTest.addIntermediate("after-register");
                sendBroadcast(makeBroadcastIntent(BROADCAST_REGISTERED));
            } else if (BROADCAST_LOCAL.equals(action)) {
                setExpectedReceivers(new String[]{RECEIVER_LOCAL});
                sendBroadcast(makeBroadcastIntent(BROADCAST_LOCAL));
            } else if (BROADCAST_REMOTE.equals(action)) {
                setExpectedReceivers(new String[]{RECEIVER_REMOTE});
                sendBroadcast(makeBroadcastIntent(BROADCAST_REMOTE));
            } else if (BROADCAST_ALL.equals(action)) {
                setExpectedReceivers(new String[]{
                        RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL});
                registerMyReceiver(new IntentFilter(BROADCAST_ALL));
                sCallingTest.addIntermediate("after-register");
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
            } else if (BROADCAST_MULTI.equals(action)) {
                setExpectedReceivers(new String[]{
                        RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
                        RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
                        RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
                        RECEIVER_LOCAL, RECEIVER_REMOTE,
                        RECEIVER_LOCAL, RECEIVER_REMOTE,
                        RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
                        RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
                        RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
                        RECEIVER_REMOTE, RECEIVER_LOCAL,
                        RECEIVER_REMOTE, RECEIVER_LOCAL});
                registerMyReceiver(new IntentFilter(BROADCAST_ALL));
                sCallingTest.addIntermediate("after-register");
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_LOCAL), null);
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_REMOTE), null);
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_LOCAL), null);
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_REMOTE), null);
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_REPEAT), null);
            } else if (BROADCAST_ABORT.equals(action)) {
                setExpectedReceivers(new String[]{
                        RECEIVER_REMOTE, RECEIVER_ABORT});
                registerMyReceiver(new IntentFilter(BROADCAST_ABORT));
                sCallingTest.addIntermediate("after-register");
                sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ABORT), null);
            } else if (BROADCAST_STICKY1.equals(action)) {
                setExpectedReceivers(new String[]{RECEIVER_REG});
                setExpectedData(new String[]{DATA_1});
                registerMyReceiver(new IntentFilter(BROADCAST_STICKY1));
                sCallingTest.addIntermediate("after-register");
            } else if (BROADCAST_STICKY2.equals(action)) {
                setExpectedReceivers(new String[]{RECEIVER_REG, RECEIVER_REG});
                setExpectedData(new String[]{DATA_1, DATA_2});
                IntentFilter filter = new IntentFilter(BROADCAST_STICKY1);
                filter.addAction(BROADCAST_STICKY2);
                registerMyReceiver(filter);
                sCallingTest.addIntermediate("after-register");
            }
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle icicle) {
        super.onSaveInstanceState(icicle);
        checkLifecycle(ON_FREEZE);
        if (mBadParcelable) {
            icicle.putParcelable("baddy", new MyBadParcelable());
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "PAUSE lauchpad "
                + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
        checkLifecycle(ON_PAUSE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "STOP lauchpad "
                + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
        checkLifecycle(ON_STOP);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode,
            Intent data) {
        switch (requestCode) {
            case LAUNCHED_RESULT:
                sCallingTest.finishTiming(true);
                finishWithResult(resultCode, data);
                break;
            case FORWARDED_RESULT:
                sCallingTest.finishTiming(true);
                if (RETURNED_RESULT.equals(data.getAction())) {
                    finishWithResult(resultCode, data);
                } else {
                    finishWithResult(RESULT_CANCELED, (new Intent()).setAction(
                            "Bad data returned: " + data));
                }
                break;
            default:
                sCallingTest.finishTiming(true);
                finishWithResult(RESULT_CANCELED, (new Intent()).setAction(
                        "Unexpected request code: " + requestCode));
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "DESTROY lauchpad "
                + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
        checkLifecycle(ON_DESTROY);
        sCallingTest.activityFinished(mResultCode, mData, mResultStack);
    }

    private void setExpectedLifecycle(String[] lifecycle) {
        mExpectedLifecycle = lifecycle;
        mNextLifecycle = 0;
    }

    private void checkLifecycle(String where) {
        if (mExpectedLifecycle == null) return;

        if (mNextLifecycle >= mExpectedLifecycle.length) {
            finishBad("Activity lifecycle incorrect: received " + where
                    + " but don't expect any more calls");
            mExpectedLifecycle = null;
            return;
        }
        if (!mExpectedLifecycle[mNextLifecycle].equals(where)) {
            finishBad("Activity lifecycle incorrect: received " + where
                    + " but expected " + mExpectedLifecycle[mNextLifecycle]
                    + " at " + mNextLifecycle);
            mExpectedLifecycle = null;
            return;
        }

        mNextLifecycle++;

        if (mNextLifecycle >= mExpectedLifecycle.length) {
            setTestResult(RESULT_OK, null);
            return;
        }

        String next = mExpectedLifecycle[mNextLifecycle];
        if (where.equals(ON_DESTROY)) {
            finishBad("Activity lifecycle incorrect: received " + where
                    + " but expected more actions (next is " + next + ")");
            mExpectedLifecycle = null;
            return;
        } else if (next.equals(DO_FINISH)) {
            mNextLifecycle++;
            if (mNextLifecycle >= mExpectedLifecycle.length) {
                setTestResult(RESULT_OK, null);
            }
            if (!isFinishing()) {
                finish();
            }
        } else if (next.equals(DO_LOCAL_SCREEN)) {
            mNextLifecycle++;
            Intent intent = new Intent(TestedScreen.WAIT_BEFORE_FINISH);
            intent.setClass(this, LocalScreen.class);
            startActivity(intent);
        } else if (next.equals(DO_LOCAL_DIALOG)) {
            mNextLifecycle++;
            Intent intent = new Intent(TestedScreen.WAIT_BEFORE_FINISH);
            intent.setClass(this, LocalDialog.class);
            startActivity(intent);
        }
    }

    private void setExpectedReceivers(String[] receivers) {
        mExpectedReceivers = receivers;
        mNextReceiver = 0;
    }

    private void setExpectedData(String[] data) {
        mExpectedData = data;
        mReceivedData = new boolean[data.length];
    }

    private Intent makeBroadcastIntent(String action) {
        Intent intent = new Intent(action, null);
        intent.putExtra("caller", mCallTarget);
        return intent;
    }

    private void finishGood() {
        finishWithResult(RESULT_OK, null);
    }

    private void finishBad(String error) {
        finishWithResult(RESULT_CANCELED, (new Intent()).setAction(error));
    }

    private void finishWithResult(int resultCode, Intent data) {
        setTestResult(resultCode, data);
        finish();
    }

    private void setTestResult(int resultCode, Intent data) {
        mHandler.removeCallbacks(mTimeout);
        unregisterMyReceiver();
        mResultCode = resultCode;
        mData = data;
        mResultStack = new RuntimeException("Original error was here");
        mResultStack.fillInStackTrace();
    }

    private void registerMyReceiver(IntentFilter filter) {
        mReceiverRegistered = true;
        //System.out.println("Registering: " + mReceiver);
        registerReceiver(mReceiver, filter);
    }

    private void unregisterMyReceiver() {
        if (mReceiverRegistered) {
            mReceiverRegistered = false;
            //System.out.println("Unregistering: " + mReceiver);
            unregisterReceiver(mReceiver);
        }
    }

    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
        }
    };

    static final int GOT_RECEIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
    static final int ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;

    private Binder mCallTarget = new Binder() {
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
            data.setDataPosition(0);
            data.enforceInterface(LaunchpadActivity.LAUNCH);
            if (code == GOT_RECEIVE_TRANSACTION) {
                String name = data.readString();
                gotReceive(name, null);
                return true;
            } else if (code == ERROR_TRANSACTION) {
                finishBad(data.readString());
                return true;
            }
            return false;
        }
    };

    private final void gotReceive(String name, Intent intent) {
        synchronized (this) {

            //System.out.println("Got receive: " + name);
            //System.out.println(mNextReceiver + " in " + mExpectedReceivers);
            //new RuntimeException("stack").printStackTrace();

            sCallingTest.addIntermediate(mNextReceiver + "-" + name);

            if (mExpectedData != null) {
                int n = mExpectedData.length;
                int i;
                boolean prev = false;
                for (i = 0; i < n; i++) {
                    if (mExpectedData[i].equals(intent.getStringExtra("test"))) {
                        if (mReceivedData[i]) {
                            prev = true;
                            continue;
                        }
                        mReceivedData[i] = true;
                        break;
                    }
                }
                if (i >= n) {
                    if (prev) {
                        finishBad("Receive got data too many times: "
                                + intent.getStringExtra("test"));
                    } else {
                        finishBad("Receive got unexpected data: "
                                + intent.getStringExtra("test"));
                    }
                    return;
                }
            }

            if (mNextReceiver >= mExpectedReceivers.length) {
                finishBad("Got too many onReceiveIntent() calls!");
//                System.out.println("Too many intents received: now at "
//                                   + mNextReceiver + ", expect list: "
//                                   + Arrays.toString(mExpectedReceivers));
            } else if (!mExpectedReceivers[mNextReceiver].equals(name)) {
                finishBad("Receive out of order: got " + name + " but expected "
                        + mExpectedReceivers[mNextReceiver] + " at "
                        + mNextReceiver);
            } else {
                mNextReceiver++;
                if (mNextReceiver == mExpectedReceivers.length) {
                    mHandler.post(mUnregister);
                }
            }

        }
    }

    private Runnable mUnregister = new Runnable() {
        public void run() {
            if (mReceiverRegistered) {
                sCallingTest.addIntermediate("before-unregister");
                unregisterMyReceiver();
            }
            sCallingTest.finishTiming(true);
            finishGood();
        }
    };

    private Runnable mTimeout = new Runnable() {
        public void run() {
            Log.i("foo", "**** TIMEOUT");
            String msg = "Timeout";
            if (mExpectedReceivers != null
                    && mNextReceiver < mExpectedReceivers.length) {
                msg = msg + " waiting for " + mExpectedReceivers[mNextReceiver];
            }
            finishBad(msg);
        }
    };

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            //System.out.println("Receive in: " + this + ": " + intent);
            gotReceive(RECEIVER_REG, intent);
        }
    };
}

