diff --git a/pdk/docs/compatibility/cts-development.jd b/pdk/docs/compatibility/cts-development.jd
new file mode 100644
index 000000000..a0000bde0
--- /dev/null
+++ b/pdk/docs/compatibility/cts-development.jd
@@ -0,0 +1,130 @@
+page.title=CTS Development
+doc.type=compatibility
+@jd:body
+
+
Initializing Your Repo Client
+
+
Follow the
+instructions
+to get and build the Android source code but specify "-b froyo"
+when issuing the "repo init" command. This assures that your CTS
+changes will be included in the next CTS release and beyond.
+
+
Setting Up Eclipse
+
+
Follow the
+instructions
+to setup Eclipse but execute the following command to generate the
+.classpath file rather than copying the one from the development
+project:
CTS tests use JUnit and the Android testing APIs. Review the
+Testing
+and Instrumentation tutorial while perusing the existing tests under the
+"cts/tests/tests" directory. You will see that CTS tests mostly follow the same
+conventions used in other Android tests.
+
+
Since CTS runs across many production devices, the tests must follow
+these rules:
+
+
+
Must take into account varying screen sizes, orientations, and
+ keyboard layouts.
+
Only use public API methods. In other words, avoid all classes,
+ methods, and fields that are annotated with the "hide" annotation.
+
Avoid relying upon particular view layouts or depend on the
+ dimensions of assets that may not be on some device.
+
Don't rely upon root privileges.
+
+
+
Test Naming and Location
+
+
Most CTS test cases target a specific class in the Android API. These tests
+have Java package names with a "cts" suffix like "android.view.cts" and class
+names with the "Test" suffix like "ViewTest." Each test case consists of
+multiple tests, where each test usually exercises a particular API method of
+the API class being tested. Each test is annotated with a @TestTargetNew
+annotation to indicate what API method is being exercised. These tests are
+arranged in a directory structure where tests are grouped into different
+categories like "widgets" and "views."
+
+
For example, the CTS test for "android.widget.TextView" is
+"android.widget.cts.TextVietTest" found under the
+"cts/tests/tests/widget/src/android/widget/cts" directory with its
+Java package name as "android.widget.cts" and its class name as
+"TextViewTest." The "TextViewTest" class has a test called "testSetText"
+that exercises the "setText" method and a test named "testSetSingleLine" that
+calls the "setSingleLine" method. Each of those tests have @TestTargetNew
+annotations indicating what they cover.
+
+
Some CTS tests do not directly correspond to an API class but are placed in
+the most related package possible. For instance, the CTS test,
+"android.net.cts.ListeningPortsTest," is in the "android.net.cts," because it
+is network related even though there is no "android.net.ListeningPorts" class.
+Thus, use your best judgement when adding new tests and refer to other tests
+as examples.
+
+
New Test Packages
+
+
When adding new tests, there may not be an existing directory to place your
+test. In that case, refer to the example under "cts/tests/tests/example" and
+create a new directory. Furthermore, make sure to add your new package's
+module name from its Android.mk to "CTS_COVERAGE_TEST_CASE_LIST" in
+"cts/CtsTestCaseList.mk." This Makefile is used by "build/core/tasks/cts.mk"
+to glue all the tests together to create the final CTS package.
+
+
Test Stubs and Utilities
+
+
Some tests use additional infrastructure like separate activities
+and various utilities to perform tests. These are located under the
+"cts/tests/src" directory. These stubs aren't separated into separate test
+APKs like the tests, so the "cts/tests/src" directory does not have additional
+top level directories like "widget" or "view." Follow the same principle of
+putting new classes into a package with a name that correlates to the purpose
+of your new class. For instance, a stub activity used for testing OpenGL like
+"GLSurfaceViewStubActivity" belongs in the "android.opengl.cts" package under
+the "cts/tests/src/android/opengl" directory.
+
+
Other Tasks
+
+
Besides adding new tests there are other ways to contribute to CTS:
+
+
+
Fix or remove tests annotated with BrokenTest and KnownFailure.
+
+
+
Submitting Your Changes
+
+
Follow the
+Android
+contributors' workflow to contribute changes to CTS. A reviewer
+will be assigned to your change, and your change should be reviewed shortly!
+
diff --git a/samples/ApiDemos/res/layout/alert_dialog.xml b/samples/ApiDemos/res/layout/alert_dialog.xml
index 0097e7a16..9c6e4a3ed 100644
--- a/samples/ApiDemos/res/layout/alert_dialog.xml
+++ b/samples/ApiDemos/res/layout/alert_dialog.xml
@@ -29,6 +29,9 @@
+
diff --git a/samples/ApiDemos/res/layout/device_admin_sample.xml b/samples/ApiDemos/res/layout/device_admin_sample.xml
index 23f065ad5..a7cb0d9c0 100644
--- a/samples/ApiDemos/res/layout/device_admin_sample.xml
+++ b/samples/ApiDemos/res/layout/device_admin_sample.xml
@@ -158,6 +158,20 @@
+
+
+
+
+
+
+
+
diff --git a/samples/ApiDemos/res/menu/actions.xml b/samples/ApiDemos/res/menu/actions.xml
index d148ac915..9f0440c72 100644
--- a/samples/ApiDemos/res/menu/actions.xml
+++ b/samples/ApiDemos/res/menu/actions.xml
@@ -18,7 +18,7 @@
android:icon="@android:drawable/ic_menu_search"
android:title="@string/action_bar_search"
android:showAsAction="ifRoom"
- android:onClick="onSearch" />
+ android:actionViewClass="android.widget.SearchView" />
@@ -30,15 +30,19 @@
android:icon="@android:drawable/ic_menu_share"
android:title="@string/action_bar_share"
android:showAsAction="ifRoom" />
-
diff --git a/samples/ApiDemos/res/menu/camera_menu.xml b/samples/ApiDemos/res/menu/camera_menu.xml
new file mode 100644
index 000000000..16504b731
--- /dev/null
+++ b/samples/ApiDemos/res/menu/camera_menu.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index b76189a9c..02b6beda0 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -370,6 +370,7 @@
App/DialogOK Cancel dialog with a messageOK Cancel dialog with a long message
+ OK Cancel dialog with ultra long messageList dialogSingle choice listRepeat alarm
@@ -391,6 +392,44 @@
kipg naar mixent phona. Cak pwico siructiun
ruous nust apoply tyu cak Uhex sisulutiun munityuw uw dseg
+
+ Plloaso mako nuto siwuf cakso dodtos anr koop a
+ cupy uf cak vux noaw yerw phuno. Whag schengos, uf efed, quiel
+ ba mada su otrenzr.\n\nSwipontgwook proudgs hus yag su ba dagarmidad.
+ Plasa maku noga wipont trenzsa schengos ent kaap zux comy.\n\nWipont trenz
+ kipg naar mixent phona. Cak pwico siructiun
+ ruous nust apoply tyu cak Uhex sisulutiun munityuw uw dseg\n\n
+ Plloaso mako nuto siwuf cakso dodtos anr koop a
+ cupy uf cak vux noaw yerw phuno. Whag schengos, uf efed, quiel
+ ba mada su otrenzr.\n\nSwipontgwook proudgs hus yag su ba dagarmidad.
+ Plasa maku noga wipont trenzsa schengos ent kaap zux comy.\n\nWipont trenz
+ kipg naar mixent phona. Cak pwico siructiun
+ ruous nust apoply tyu cak Uhex sisulutiun munityuw uw dseg\n\n
+ Plloaso mako nuto siwuf cakso dodtos anr koop a
+ cupy uf cak vux noaw yerw phuno. Whag schengos, uf efed, quiel
+ ba mada su otrenzr.\n\nSwipontgwook proudgs hus yag su ba dagarmidad.
+ Plasa maku noga wipont trenzsa schengos ent kaap zux comy.\n\nWipont trenz
+ kipg naar mixent phona. Cak pwico siructiun
+ ruous nust apoply tyu cak Uhex sisulutiun munityuw uw dseg\n\n
+ Plloaso mako nuto siwuf cakso dodtos anr koop a
+ cupy uf cak vux noaw yerw phuno. Whag schengos, uf efed, quiel
+ ba mada su otrenzr.\n\nSwipontgwook proudgs hus yag su ba dagarmidad.
+ Plasa maku noga wipont trenzsa schengos ent kaap zux comy.\n\nWipont trenz
+ kipg naar mixent phona. Cak pwico siructiun
+ ruous nust apoply tyu cak Uhex sisulutiun munityuw uw dseg\n\n
+ Plloaso mako nuto siwuf cakso dodtos anr koop a
+ cupy uf cak vux noaw yerw phuno. Whag schengos, uf efed, quiel
+ ba mada su otrenzr.\n\nSwipontgwook proudgs hus yag su ba dagarmidad.
+ Plasa maku noga wipont trenzsa schengos ent kaap zux comy.\n\nWipont trenz
+ kipg naar mixent phona. Cak pwico siructiun
+ ruous nust apoply tyu cak Uhex sisulutiun munityuw uw dseg\n\n
+ Plloaso mako nuto siwuf cakso dodtos anr koop a
+ cupy uf cak vux noaw yerw phuno. Whag schengos, uf efed, quiel
+ ba mada su otrenzr.\n\nSwipontgwook proudgs hus yag su ba dagarmidad.
+ Plasa maku noga wipont trenzsa schengos ent kaap zux comy.\n\nWipont trenz
+ kipg naar mixent phona. Cak pwico siructiun
+ ruous nust apoply tyu cak Uhex sisulutiun munityuw uw dseg\n\n
+ OKHideSomething
@@ -551,6 +590,7 @@
Password History LengthPassword Expiration Timeout (minutes) Update
+ Update StatusSet PasswordPasswordReset Password
@@ -584,8 +624,9 @@
AddEditShare
- Zoom
- Save
+ Sort
+ Alphabetically
+ By sizeApp/Action Bar/Display OptionsDISPLAY_HOME_AS_UP
@@ -611,6 +652,8 @@
Hide Me!Density: Unknown Screen
+ Device has only one camera!
+ Switch Camera
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ActionBarUsage.java b/samples/ApiDemos/src/com/example/android/apis/app/ActionBarUsage.java
index 698005080..a61a5827f 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/ActionBarUsage.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/ActionBarUsage.java
@@ -16,9 +16,14 @@
package com.example.android.apis.app;
import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.widget.SearchView;
+import android.widget.SearchView.OnQueryChangeListener;
+import android.widget.TextView;
import android.widget.Toast;
import com.example.android.apis.R;
@@ -29,14 +34,35 @@ import com.example.android.apis.R;
* menu data itself. If you'd like to see how these things work under the hood, see
* ActionBarMechanics.
*/
-public class ActionBarUsage extends Activity {
+public class ActionBarUsage extends Activity implements OnQueryChangeListener {
+ TextView mSearchText;
+ int mSortMode = -1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSearchText = new TextView(this);
+ setContentView(mSearchText);
+ }
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.actions, menu);
+ SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
+ searchView.setOnQueryChangeListener(this);
return true;
}
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ if (mSortMode != -1) {
+ Drawable icon = menu.findItem(mSortMode).getIcon();
+ menu.findItem(R.id.action_sort).setIcon(icon);
+ }
+ return super.onPrepareOptionsMenu(menu);
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Toast.makeText(this, "Selected Item: " + item.getTitle(), Toast.LENGTH_SHORT).show();
@@ -46,7 +72,24 @@ public class ActionBarUsage extends Activity {
// This method is specified as an onClick handler in the menu xml and will
// take precedence over the Activity's onOptionsItemSelected method.
// See res/menu/actions.xml for more info.
- public void onSearch(MenuItem item) {
- Toast.makeText(this, "Searching...", Toast.LENGTH_SHORT).show();
+ public void onSort(MenuItem item) {
+ mSortMode = item.getItemId();
+ // Request a call to onPrepareOptionsMenu so we can change the sort icon
+ invalidateOptionsMenu();
+ }
+
+ // The following callbacks are called for the SearchView.OnQueryChangeListener
+ // For more about using SearchView, see src/.../view/SearchView1.java and SearchView2.java
+ @Override
+ public boolean onQueryTextChanged(String newText) {
+ newText = newText.isEmpty() ? "" : "Query so far: " + newText;
+ mSearchText.setText(newText);
+ return true;
+ }
+
+ @Override
+ public boolean onSubmitQuery(String query) {
+ Toast.makeText(this, "Searching for: " + query + "...", Toast.LENGTH_SHORT).show();
+ return true;
}
}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/AlertDialogSamples.java b/samples/ApiDemos/src/com/example/android/apis/app/AlertDialogSamples.java
index 67141751b..057e20ce2 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/AlertDialogSamples.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/AlertDialogSamples.java
@@ -64,6 +64,7 @@ public class AlertDialogSamples extends Activity {
private static final int DIALOG_MULTIPLE_CHOICE = 6;
private static final int DIALOG_TEXT_ENTRY = 7;
private static final int DIALOG_MULTIPLE_CHOICE_CURSOR = 8;
+ private static final int DIALOG_YES_NO_ULTRA_LONG_MESSAGE = 9;
private static final int MAX_PROGRESS = 100;
@@ -115,6 +116,30 @@ public class AlertDialogSamples extends Activity {
}
})
.create();
+ case DIALOG_YES_NO_ULTRA_LONG_MESSAGE:
+ return new AlertDialog.Builder(AlertDialogSamples.this)
+ .setIcon(R.drawable.alert_dialog_icon)
+ .setTitle(R.string.alert_dialog_two_buttons_msg)
+ .setMessage(R.string.alert_dialog_two_buttons2ultra_msg)
+ .setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+
+ /* User clicked OK so do some stuff */
+ }
+ })
+ .setNeutralButton(R.string.alert_dialog_something, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+
+ /* User clicked Something so do some stuff */
+ }
+ })
+ .setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+
+ /* User clicked Cancel so do some stuff */
+ }
+ })
+ .create();
case DIALOG_LIST:
return new AlertDialog.Builder(AlertDialogSamples.this)
.setTitle(R.string.select_dialog)
@@ -277,6 +302,15 @@ public class AlertDialogSamples extends Activity {
});
+ /* Display an ultra long text message with yes/no buttons and handle each message as well as the cancel action */
+ Button twoButtons2UltraTitle = (Button) findViewById(R.id.two_buttons2ultra);
+ twoButtons2UltraTitle.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ showDialog(DIALOG_YES_NO_ULTRA_LONG_MESSAGE);
+ }
+ });
+
+
/* Display a list of items */
Button selectButton = (Button) findViewById(R.id.select_button);
selectButton.setOnClickListener(new OnClickListener() {
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
index cb24b7dbb..06a2c19ef 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.java
@@ -40,6 +40,7 @@ import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
+import android.widget.TextView;
import android.widget.Toast;
import java.net.InetSocketAddress;
@@ -170,7 +171,6 @@ public class DeviceAdminSample extends DeviceAdminReceiver {
EditText mPasswordMinimumSymbols;
EditText mPasswordMinimumNonLetter;
EditText mPasswordHistoryLength;
- EditText mPasswordExpirationTimeout;
Button mSetPasswordButton;
EditText mPassword;
@@ -190,7 +190,10 @@ public class DeviceAdminSample extends DeviceAdminReceiver {
EditText mProxyList;
Button mProxyButton;
+ private EditText mPasswordExpirationTimeout;
private Button mPasswordExpirationButton;
+ private TextView mPasswordExpirationStatus;
+ private Button mPasswordExpirationStatusButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -333,12 +336,21 @@ public class DeviceAdminSample extends DeviceAdminReceiver {
mPasswordExpirationButton = (Button) findViewById(R.id.update_expiration_button);
mPasswordExpirationButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
- long expiration;
try {
setPasswordExpiration(
Long.parseLong(mPasswordExpirationTimeout.getText().toString()));
} catch (NumberFormatException nfe) {
}
+ updatePasswordExpirationStatus();
+ }
+ });
+
+ mPasswordExpirationStatus = (TextView) findViewById(R.id.password_expiration_status);
+ mPasswordExpirationStatusButton =
+ (Button) findViewById(R.id.update_expiration_status_button);
+ mPasswordExpirationStatusButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ updatePasswordExpirationStatus();
}
});
@@ -458,6 +470,41 @@ public class DeviceAdminSample extends DeviceAdminReceiver {
mMaxFailedPw.setText(Integer.toString(maxFailedPw));
}
+ void updatePasswordExpirationStatus() {
+ boolean active = mDPM.isAdminActive(mDeviceAdminSample);
+ String statusText;
+ if (active) {
+ long now = System.currentTimeMillis();
+ // We'll query the DevicePolicyManager twice - first for the expiration values
+ // set by the sample app, and later, for the system values (which may be different
+ // if there is another administrator active.)
+ long expirationDate = mDPM.getPasswordExpiration(mDeviceAdminSample);
+ long mSecUntilExpiration = expirationDate - now;
+ if (mSecUntilExpiration >= 0) {
+ statusText = "Expiration in " + countdownString(mSecUntilExpiration);
+ } else {
+ statusText = "Expired " + countdownString(-mSecUntilExpiration) + " ago";
+ }
+
+ // expirationTimeout is the cycle time between required password refresh
+ long expirationTimeout = mDPM.getPasswordExpirationTimeout(mDeviceAdminSample);
+ statusText += " / timeout period " + countdownString(expirationTimeout);
+
+ // Now report the aggregate (global) expiration time
+ statusText += " / Aggregate ";
+ expirationDate = mDPM.getPasswordExpiration(null);
+ mSecUntilExpiration = expirationDate - now;
+ if (mSecUntilExpiration >= 0) {
+ statusText += "expiration in " + countdownString(mSecUntilExpiration);
+ } else {
+ statusText += "expired " + countdownString(-mSecUntilExpiration) + " ago";
+ }
+ } else {
+ statusText = "";
+ }
+ mPasswordExpirationStatus.setText(statusText);
+ }
+
void updatePolicies() {
SharedPreferences prefs = getSamplePreferences(this);
final int pwQuality = prefs.getInt(PREF_PASSWORD_QUALITY,
@@ -569,6 +616,7 @@ public class DeviceAdminSample extends DeviceAdminReceiver {
protected void onResume() {
super.onResume();
updateButtonStates();
+ updatePasswordExpirationStatus();
}
@Override
@@ -615,7 +663,7 @@ public class DeviceAdminSample extends DeviceAdminReceiver {
private OnClickListener mResetPasswordListener = new OnClickListener() {
public void onClick(View v) {
- if (mAM.isUserAMonkey()) {
+ if (ActivityManager.isUserAMonkey()) {
// Don't trust monkeys to do the right thing!
AlertDialog.Builder builder = new AlertDialog.Builder(Controller.this);
builder.setMessage("You can't reset my password because you are a monkey!");
@@ -633,7 +681,7 @@ public class DeviceAdminSample extends DeviceAdminReceiver {
private OnClickListener mForceLockListener = new OnClickListener() {
public void onClick(View v) {
- if (mAM.isUserAMonkey()) {
+ if (ActivityManager.isUserAMonkey()) {
// Don't trust monkeys to do the right thing!
AlertDialog.Builder builder = new AlertDialog.Builder(Controller.this);
builder.setMessage("You can't lock my screen because you are a monkey!");
@@ -650,7 +698,7 @@ public class DeviceAdminSample extends DeviceAdminReceiver {
private OnClickListener mWipeDataListener = new OnClickListener() {
public void onClick(final View v) {
- if (mAM.isUserAMonkey()) {
+ if (ActivityManager.isUserAMonkey()) {
// Don't trust monkeys to do the right thing!
AlertDialog.Builder builder = new AlertDialog.Builder(Controller.this);
builder.setMessage("You can't wipe my data because you are a monkey!");
@@ -694,7 +742,7 @@ public class DeviceAdminSample extends DeviceAdminReceiver {
private OnClickListener mSetTimeoutListener = new OnClickListener() {
public void onClick(View v) {
- if (mAM.isUserAMonkey()) {
+ if (ActivityManager.isUserAMonkey()) {
// Don't trust monkeys to do the right thing!
AlertDialog.Builder builder = new AlertDialog.Builder(Controller.this);
builder.setMessage("You can't lock my screen because you are a monkey!");
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.java b/samples/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.java
index 22dc29729..73d50cb3c 100644
--- a/samples/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.java
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.java
@@ -17,11 +17,16 @@
package com.example.android.apis.graphics;
import android.app.Activity;
+import android.app.AlertDialog;
import android.content.Context;
import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
@@ -32,11 +37,20 @@ import android.view.WindowManager;
import java.io.IOException;
import java.util.List;
+// Need the following import to get access to the app resources, since this
+// class is in a sub-package.
+import com.example.android.apis.R;
+
// ----------------------------------------------------------------------
public class CameraPreview extends Activity {
private Preview mPreview;
Camera mCamera;
+ int numberOfCameras;
+ int cameraCurrentlyLocked;
+
+ // The first rear facing camera
+ int defaultCameraId;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -50,13 +64,27 @@ public class CameraPreview extends Activity {
// and set it as the content of our activity.
mPreview = new Preview(this);
setContentView(mPreview);
+
+ // Find the total number of cameras available
+ numberOfCameras = Camera.getNumberOfCameras();
+
+ // Find the ID of the default camera
+ CameraInfo cameraInfo = new CameraInfo();
+ for (int i = 0; i < numberOfCameras; i++) {
+ Camera.getCameraInfo(i, cameraInfo);
+ if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
+ defaultCameraId = i;
+ }
+ }
}
@Override
protected void onResume() {
super.onResume();
+ // Open the default i.e. the first rear facing camera.
mCamera = Camera.open();
+ cameraCurrentlyLocked = defaultCameraId;
mPreview.setCamera(mCamera);
}
@@ -72,6 +100,55 @@ public class CameraPreview extends Activity {
mCamera = null;
}
}
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+
+ // Inflate our menu which can gather user input for switching camera
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.camera_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle item selection
+ switch (item.getItemId()) {
+ case R.id.switch_cam:
+ // check for availability of multiple cameras
+ if (numberOfCameras == 1) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(this.getString(R.string.camera_alert))
+ .setNeutralButton("Close", null);
+ AlertDialog alert = builder.create();
+ alert.show();
+ return true;
+ }
+
+ // OK, we have multiple cameras.
+ // Release this camera -> cameraCurrentlyLocked
+ if (mCamera != null) {
+ mCamera.stopPreview();
+ mPreview.setCamera(null);
+ mCamera.release();
+ mCamera = null;
+ }
+
+ // Acquire the next camera and request Preview to reconfigure
+ // parameters.
+ mCamera = Camera
+ .open((cameraCurrentlyLocked + 1) % numberOfCameras);
+ cameraCurrentlyLocked = (cameraCurrentlyLocked + 1)
+ % numberOfCameras;
+ mPreview.switchCamera(mCamera);
+
+ // Start the preview
+ mCamera.startPreview();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
}
// ----------------------------------------------------------------------
@@ -111,6 +188,20 @@ class Preview extends ViewGroup implements SurfaceHolder.Callback {
}
}
+ public void switchCamera(Camera camera) {
+ setCamera(camera);
+ try {
+ camera.setPreviewDisplay(mHolder);
+ } catch (IOException exception) {
+ Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
+ }
+ Camera.Parameters parameters = camera.getParameters();
+ parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
+ requestLayout();
+
+ camera.setParameters(parameters);
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We purposely disregard child measurements because act as a