/* * Copyright (C) 2008 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.app.cts; 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; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; 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 CREATOR = new Parcelable.Creator() { public MyBadParcelable createFromParcel(Parcel in) { return new MyBadParcelable(in); } public MyBadParcelable[] newArray(int size) { return new MyBadParcelable[size]; } }; public MyBadParcelable(Parcel in) { 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 = "android.app.cts.activity.LAUNCH"; public static final String FORWARD_RESULT = "android.app.cts.activity.FORWARD_RESULT"; public static final String RETURNED_RESULT = "android.app.cts.activity.RETURNED_RESULT"; public static final String BAD_PARCELABLE = "android.app.cts.activity.BAD_PARCELABLE"; public static final int LAUNCHED_RESULT = 1; public static final int FORWARDED_RESULT = 2; public static final String LIFECYCLE_BASIC = "android.app.cts.activity.LIFECYCLE_BASIC"; public static final String LIFECYCLE_SCREEN = "android.app.cts.activity.LIFECYCLE_SCREEN"; public static final String LIFECYCLE_DIALOG = "android.app.cts.activity.LIFECYCLE_DIALOG"; public static final String BROADCAST_REGISTERED = "android.app.cts.activity.BROADCAST_REGISTERED"; public static final String BROADCAST_LOCAL = "android.app.cts.activity.BROADCAST_LOCAL"; public static final String BROADCAST_REMOTE = "android.app.cts.activity.BROADCAST_REMOTE"; public static final String BROADCAST_ALL = "android.app.cts.activity.BROADCAST_ALL"; public static final String BROADCAST_REPEAT = "android.app.cts.activity.BROADCAST_REPEAT"; public static final String BROADCAST_MULTI = "android.app.cts.activity.BROADCAST_MULTI"; public static final String BROADCAST_ABORT = "android.app.cts.activity.BROADCAST_ABORT"; public static final String EXPANDLIST_SELECT = "EXPANDLIST_SELECT"; public static final String EXPANDLIST_VIEW = "EXPANDLIST_VIEW"; public static final String EXPANDLIST_CALLBACK = "EXPANDLIST_CALLBACK"; public static final String BROADCAST_STICKY1 = "android.app.cts.activity.BROADCAST_STICKY1"; public static final String BROADCAST_STICKY2 = "android.app.cts.activity.BROADCAST_STICKY2"; public static final String ALIAS_ACTIVITY = "android.app.cts.activity.ALIAS_ACTIVITY"; 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"; // ON_STOP and ON_DESTROY are not tested because they may not be called. 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 static final String TAG = "LaunchpadActivity"; private boolean mBadParcelable = false; private boolean mStarted = false; private int mResultCode = RESULT_CANCELED; private Intent mData = new Intent().setAction("No result received"); private RuntimeException mResultStack = null; /** Index into the {@link #mNextLifecycle} array. */ private int mNextLifecycle; /** Current lifecycle expected to be followed. */ private String[] mExpectedLifecycle; /** Other possible lifecycles. Never includes the current {@link #mExpectedLifecycle}. */ private List mOtherPossibleLifecycles = new ArrayList(2); /** Map from lifecycle arrays to debugging log names. */ private Map mLifecycleNames = new HashMap(2); 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() { } @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); resetLifecycles(); // ON_STOP and ON_DESTROY are not tested because they may not be called. final String action = getIntent().getAction(); if (LIFECYCLE_BASIC.equals(action)) { addPossibleLifecycle(LIFECYCLE_BASIC, new String[] { ON_START, ON_RESUME, DO_FINISH, ON_PAUSE }); } else if (LIFECYCLE_SCREEN.equals(action)) { addPossibleLifecycle(LIFECYCLE_SCREEN + "_RESTART", new String[] { ON_START, ON_RESUME, DO_LOCAL_SCREEN, ON_PAUSE, ON_RESTART, ON_START, ON_RESUME, DO_FINISH, ON_PAUSE }); addPossibleLifecycle(LIFECYCLE_SCREEN + "_RESUME", new String[] { ON_START, ON_RESUME, DO_LOCAL_SCREEN, ON_PAUSE, ON_RESUME, DO_FINISH, ON_PAUSE }); } else if (LIFECYCLE_DIALOG.equals(action)) { addPossibleLifecycle(LIFECYCLE_DIALOG + "_RESTART", new String[] { ON_START, ON_RESUME, DO_LOCAL_DIALOG, ON_PAUSE, ON_RESTART, ON_START, ON_RESUME, DO_FINISH, ON_PAUSE }); addPossibleLifecycle(LIFECYCLE_DIALOG + "_RESUME", new String[] { ON_START, ON_RESUME, DO_LOCAL_DIALOG, ON_PAUSE, ON_RESUME, DO_FINISH, ON_PAUSE }); } } private void resetLifecycles() { mNextLifecycle = 0; mExpectedLifecycle = null; mOtherPossibleLifecycles.clear(); mLifecycleNames.clear(); } /** * Add a potential lifecycle that this activity may follow, since there * are usually multiple valid lifecycles. For instance, sometimes onPause * will lead to onResume rather than onStop when another activity is * raised over the current one. * * @param debugName for the lifecycle shown in the logs * @param lifecycle array containing tokens indicating the expected lifecycle */ private void addPossibleLifecycle(String debugName, String[] lifecycle) { mLifecycleNames.put(lifecycle, debugName); if (mExpectedLifecycle == null) { mExpectedLifecycle = lifecycle; } else { mOtherPossibleLifecycles.add(lifecycle); } } /** * Switch to the next possible lifecycle and return if switching was * successful. Call this method when mExpectedLifecycle doesn't match * the current lifecycle and you need to check another possible lifecycle. * * @return whether on not there was a lifecycle to switch to */ private boolean switchToNextPossibleLifecycle() { if (!mOtherPossibleLifecycles.isEmpty()) { String[] newLifecycle = mOtherPossibleLifecycles.remove(0); Log.w(TAG, "Switching expected lifecycles from " + mLifecycleNames.get(mExpectedLifecycle) + " to " + mLifecycleNames.get(newLifecycle)); mExpectedLifecycle = newLifecycle; return true; } else { Log.w(TAG, "No more lifecycles after " + mLifecycleNames.get(mExpectedLifecycle)); mExpectedLifecycle = null; return false; } } @Override protected void onStart() { super.onStart(); checkLifecycle(ON_START); } @Override protected void onRestart() { super.onStart(); checkLifecycle(ON_RESTART); } @Override protected void onResume() { super.onResume(); checkLifecycle(ON_RESUME); if (!mStarted) { mStarted = true; mHandler.postDelayed(mTimeout, 10 * 1000); final String action = getIntent().getAction(); sCallingTest.startTiming(true); if (LAUNCH.equals(action)) { final Intent intent = getIntent(); intent.setFlags(0); intent.setComponent((ComponentName) intent.getParcelableExtra("component")); startActivityForResult(intent, LAUNCHED_RESULT); } else if (FORWARD_RESULT.equals(action)) { final Intent intent = getIntent(); intent.setFlags(0); intent.setClass(this, LocalScreen.class); startActivityForResult(intent, FORWARDED_RESULT); } else if (BAD_PARCELABLE.equals(action)) { mBadParcelable = true; final 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 }); final IntentFilter filter = new IntentFilter(BROADCAST_STICKY1); filter.addAction(BROADCAST_STICKY2); registerMyReceiver(filter); sCallingTest.addIntermediate("after-register"); } else if (ALIAS_ACTIVITY.equals(action)) { final Intent intent = getIntent(); intent.setFlags(0); intent.setClass(this, AliasActivityStub.class); startActivityForResult(intent, LAUNCHED_RESULT); } else if (EXPANDLIST_SELECT.equals(action)) { final Intent intent = getIntent(); intent.setFlags(0); intent.setAction(action); intent.setComponent((ComponentName) intent.getParcelableExtra("component")); startActivityForResult(intent, LAUNCHED_RESULT); } else if (EXPANDLIST_VIEW.equals(action)) { final Intent intent = getIntent(); intent.setFlags(0); intent.setAction(action); intent.setComponent((ComponentName) intent.getParcelableExtra("component")); startActivityForResult(intent, LAUNCHED_RESULT); } else if (EXPANDLIST_CALLBACK.equals(action)) { final Intent intent = getIntent(); intent.setFlags(0); intent.setAction(action); intent.setComponent((ComponentName) intent.getParcelableExtra("component")); startActivityForResult(intent, LAUNCHED_RESULT); } } } @Override protected void onSaveInstanceState(Bundle icicle) { super.onSaveInstanceState(icicle); if (mBadParcelable) { icicle.putParcelable("baddy", new MyBadParcelable()); } } @Override protected void onPause() { super.onPause(); checkLifecycle(ON_PAUSE); } @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; } } private void checkLifecycle(String where) { String action = getIntent().getAction(); if (mExpectedLifecycle == null) { return; } if (mNextLifecycle >= mExpectedLifecycle.length) { finishBad("Activity lifecycle for " + action + " incorrect: received " + where + " but don't expect any more calls"); mExpectedLifecycle = null; return; } do { if (mExpectedLifecycle[mNextLifecycle].equals(where)) { Log.w(TAG, "Matched: " + where); break; } else { Log.w(TAG, "Expected " + mExpectedLifecycle[mNextLifecycle] + " but got " + where); } } while (switchToNextPossibleLifecycle()); if (mExpectedLifecycle == null) { finishBad("Activity lifecycle for " + action + " incorrect: received " + where + " at " + mNextLifecycle); return; } mNextLifecycle++; if (mNextLifecycle >= mExpectedLifecycle.length) { finishGood(); return; } final String next = mExpectedLifecycle[mNextLifecycle]; 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++; final Intent intent = new Intent(TestedScreen.WAIT_BEFORE_FINISH); intent.setClass(this, LocalScreen.class); startActivity(intent); } else if (next.equals(DO_LOCAL_DIALOG)) { mNextLifecycle++; final 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]; } @SuppressWarnings("deprecation") private Intent makeBroadcastIntent(String action) { final 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(); // Member fields set by calling setTestResult above... sCallingTest.activityFinished(mResultCode, mData, mResultStack); } 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; registerReceiver(mReceiver, filter); } private void unregisterMyReceiver() { if (mReceiverRegistered) { mReceiverRegistered = false; unregisterReceiver(mReceiver); } } private final Handler mHandler = new Handler() { @Override 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 final Binder mCallTarget = new Binder() { @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) { data.setDataPosition(0); data.enforceInterface(LaunchpadActivity.LAUNCH); if (code == GOT_RECEIVE_TRANSACTION) { final 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) { sCallingTest.addIntermediate(mNextReceiver + "-" + name); if (mExpectedData != null) { final 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!"); } 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 final Runnable mUnregister = new Runnable() { public void run() { if (mReceiverRegistered) { sCallingTest.addIntermediate("before-unregister"); unregisterMyReceiver(); } sCallingTest.finishTiming(true); finishGood(); } }; private final Runnable mTimeout = new Runnable() { public void run() { Log.i(TAG, "timeout"); String msg = "Timeout"; if (mExpectedReceivers != null && mNextReceiver < mExpectedReceivers.length) { msg = msg + " waiting for " + mExpectedReceivers[mNextReceiver]; } finishBad(msg); } }; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { gotReceive(RECEIVER_REG, intent); } }; }