diff options
Diffstat (limited to 'tests')
28 files changed, 1448 insertions, 2 deletions
diff --git a/tests/Camera2Tests/CameraToo/Android.mk b/tests/Camera2Tests/CameraToo/Android.mk new file mode 100644 index 0000000..7e5911d --- /dev/null +++ b/tests/Camera2Tests/CameraToo/Android.mk @@ -0,0 +1,23 @@ +# Copyright (C) 2014 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. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := CameraToo +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := $(call all-java-files-under,src) + +include $(BUILD_PACKAGE) diff --git a/tests/Camera2Tests/CameraToo/AndroidManifest.xml b/tests/Camera2Tests/CameraToo/AndroidManifest.xml new file mode 100644 index 0000000..a92b5d8 --- /dev/null +++ b/tests/Camera2Tests/CameraToo/AndroidManifest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + Copyright (C) 2014 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.camera2.cameratoo"> + <uses-permission android:name="android.permission.CAMERA" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <application android:label="CameraToo"> + <activity + android:name=".CameraTooActivity" + android:screenOrientation="portrait"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/Camera2Tests/CameraToo/res/layout/mainactivity.xml b/tests/Camera2Tests/CameraToo/res/layout/mainactivity.xml new file mode 100644 index 0000000..f93f177 --- /dev/null +++ b/tests/Camera2Tests/CameraToo/res/layout/mainactivity.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + Copyright (C) 2014 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. +--> + +<SurfaceView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/mainSurfaceView" + android:layout_height="fill_parent" + android:layout_width="fill_parent" + android:onClick="onClickOnSurfaceView" /> diff --git a/tests/Camera2Tests/CameraToo/src/com/example/android/camera2/cameratoo/CameraTooActivity.java b/tests/Camera2Tests/CameraToo/src/com/example/android/camera2/cameratoo/CameraTooActivity.java new file mode 100644 index 0000000..c630bad --- /dev/null +++ b/tests/Camera2Tests/CameraToo/src/com/example/android/camera2/cameratoo/CameraTooActivity.java @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2014 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.example.android.camera2.cameratoo; + +import android.app.Activity; +import android.graphics.ImageFormat; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CaptureFailure; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.Image; +import android.media.ImageReader; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.util.Size; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * A basic demonstration of how to write a point-and-shoot camera app against the new + * android.hardware.camera2 API. + */ +public class CameraTooActivity extends Activity { + /** Output files will be saved as /sdcard/Pictures/cameratoo*.jpg */ + static final String CAPTURE_FILENAME_PREFIX = "cameratoo"; + /** Tag to distinguish log prints. */ + static final String TAG = "CameraToo"; + + /** An additional thread for running tasks that shouldn't block the UI. */ + HandlerThread mBackgroundThread; + /** Handler for running tasks in the background. */ + Handler mBackgroundHandler; + /** Handler for running tasks on the UI thread. */ + Handler mForegroundHandler; + /** View for displaying the camera preview. */ + SurfaceView mSurfaceView; + /** Used to retrieve the captured image when the user takes a snapshot. */ + ImageReader mCaptureBuffer; + /** Handle to the Android camera services. */ + CameraManager mCameraManager; + /** The specific camera device that we're using. */ + CameraDevice mCamera; + /** Our image capture session. */ + CameraCaptureSession mCaptureSession; + + /** + * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose + * width and height are at least as large as the respective requested values. + * @param choices The list of sizes that the camera supports for the intended output class + * @param width The minimum desired width + * @param height The minimum desired height + * @return The optimal {@code Size}, or an arbitrary one if none were big enough + */ + static Size chooseBigEnoughSize(Size[] choices, int width, int height) { + // Collect the supported resolutions that are at least as big as the preview Surface + List<Size> bigEnough = new ArrayList<Size>(); + for (Size option : choices) { + if (option.getWidth() >= width && option.getHeight() >= height) { + bigEnough.add(option); + } + } + + // Pick the smallest of those, assuming we found any + if (bigEnough.size() > 0) { + return Collections.min(bigEnough, new CompareSizesByArea()); + } else { + Log.e(TAG, "Couldn't find any suitable preview size"); + return choices[0]; + } + } + + /** + * Compares two {@code Size}s based on their areas. + */ + static class CompareSizesByArea implements Comparator<Size> { + @Override + public int compare(Size lhs, Size rhs) { + // We cast here to ensure the multiplications won't overflow + return Long.signum((long) lhs.getWidth() * lhs.getHeight() - + (long) rhs.getWidth() * rhs.getHeight()); + } + } + + /** + * Called when our {@code Activity} gains focus. <p>Starts initializing the camera.</p> + */ + @Override + protected void onResume() { + super.onResume(); + + // Start a background thread to manage camera requests + mBackgroundThread = new HandlerThread("background"); + mBackgroundThread.start(); + mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); + mForegroundHandler = new Handler(getMainLooper()); + + mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE); + + // Inflate the SurfaceView, set it as the main layout, and attach a listener + View layout = getLayoutInflater().inflate(R.layout.mainactivity, null); + mSurfaceView = (SurfaceView) layout.findViewById(R.id.mainSurfaceView); + mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback); + setContentView(mSurfaceView); + + // Control flow continues in mSurfaceHolderCallback.surfaceChanged() + } + + /** + * Called when our {@code Activity} loses focus. <p>Tears everything back down.</p> + */ + @Override + protected void onPause() { + super.onPause(); + + try { + // Ensure SurfaceHolderCallback#surfaceChanged() will run again if the user returns + mSurfaceView.getHolder().setFixedSize(/*width*/0, /*height*/0); + + // Cancel any stale preview jobs + if (mCaptureSession != null) { + mCaptureSession.close(); + mCaptureSession = null; + } + } finally { + if (mCamera != null) { + mCamera.close(); + mCamera = null; + } + } + + // Finish processing posted messages, then join on the handling thread + mBackgroundThread.quitSafely(); + try { + mBackgroundThread.join(); + } catch (InterruptedException ex) { + Log.e(TAG, "Background worker thread was interrupted while joined", ex); + } + + // Close the ImageReader now that the background thread has stopped + if (mCaptureBuffer != null) mCaptureBuffer.close(); + } + + /** + * Called when the user clicks on our {@code SurfaceView}, which has ID {@code mainSurfaceView} + * as defined in the {@code mainactivity.xml} layout file. <p>Captures a full-resolution image + * and saves it to permanent storage.</p> + */ + public void onClickOnSurfaceView(View v) { + if (mCaptureSession != null) { + try { + CaptureRequest.Builder requester = + mCamera.createCaptureRequest(mCamera.TEMPLATE_STILL_CAPTURE); + requester.addTarget(mCaptureBuffer.getSurface()); + try { + // This handler can be null because we aren't actually attaching any callback + mCaptureSession.capture(requester.build(), /*listener*/null, /*handler*/null); + } catch (CameraAccessException ex) { + Log.e(TAG, "Failed to file actual capture request", ex); + } + } catch (CameraAccessException ex) { + Log.e(TAG, "Failed to build actual capture request", ex); + } + } else { + Log.e(TAG, "User attempted to perform a capture outside our session"); + } + + // Control flow continues in mImageCaptureListener.onImageAvailable() + } + + /** + * Callbacks invoked upon state changes in our {@code SurfaceView}. + */ + final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { + /** The camera device to use, or null if we haven't yet set a fixed surface size. */ + private String mCameraId; + + /** Whether we received a change callback after setting our fixed surface size. */ + private boolean mGotSecondCallback; + + @Override + public void surfaceCreated(SurfaceHolder holder) { + // This is called every time the surface returns to the foreground + Log.i(TAG, "Surface created"); + mCameraId = null; + mGotSecondCallback = false; + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.i(TAG, "Surface destroyed"); + holder.removeCallback(this); + // We don't stop receiving callbacks forever because onResume() will reattach us + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + // On the first invocation, width and height were automatically set to the view's size + if (mCameraId == null) { + // Find the device's back-facing camera and set the destination buffer sizes + try { + for (String cameraId : mCameraManager.getCameraIdList()) { + CameraCharacteristics cameraCharacteristics = + mCameraManager.getCameraCharacteristics(cameraId); + if (cameraCharacteristics.get(cameraCharacteristics.LENS_FACING) == + CameraCharacteristics.LENS_FACING_BACK) { + Log.i(TAG, "Found a back-facing camera"); + StreamConfigurationMap info = cameraCharacteristics + .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + + // Bigger is better when it comes to saving our image + Size largestSize = Collections.max( + Arrays.asList(info.getOutputSizes(ImageFormat.JPEG)), + new CompareSizesByArea()); + + // Prepare an ImageReader in case the user wants to capture images + Log.i(TAG, "Capture size: " + largestSize); + mCaptureBuffer = ImageReader.newInstance(largestSize.getWidth(), + largestSize.getHeight(), ImageFormat.JPEG, /*maxImages*/2); + mCaptureBuffer.setOnImageAvailableListener( + mImageCaptureListener, mBackgroundHandler); + + // Danger, W.R.! Attempting to use too large a preview size could + // exceed the camera bus' bandwidth limitation, resulting in + // gorgeous previews but the storage of garbage capture data. + Log.i(TAG, "SurfaceView size: " + + mSurfaceView.getWidth() + 'x' + mSurfaceView.getHeight()); + Size optimalSize = chooseBigEnoughSize( + info.getOutputSizes(SurfaceHolder.class), width, height); + + // Set the SurfaceHolder to use the camera's largest supported size + Log.i(TAG, "Preview size: " + optimalSize); + SurfaceHolder surfaceHolder = mSurfaceView.getHolder(); + surfaceHolder.setFixedSize(optimalSize.getWidth(), + optimalSize.getHeight()); + + mCameraId = cameraId; + return; + + // Control flow continues with this method one more time + // (since we just changed our own size) + } + } + } catch (CameraAccessException ex) { + Log.e(TAG, "Unable to list cameras", ex); + } + + Log.e(TAG, "Didn't find any back-facing cameras"); + // This is the second time the method is being invoked: our size change is complete + } else if (!mGotSecondCallback) { + if (mCamera != null) { + Log.e(TAG, "Aborting camera open because it hadn't been closed"); + return; + } + + // Open the camera device + try { + mCameraManager.openCamera(mCameraId, mCameraStateListener, + mBackgroundHandler); + } catch (CameraAccessException ex) { + Log.e(TAG, "Failed to configure output surface", ex); + } + mGotSecondCallback = true; + + // Control flow continues in mCameraStateListener.onOpened() + } + }}; + + /** + * Calledbacks invoked upon state changes in our {@code CameraDevice}. <p>These are run on + * {@code mBackgroundThread}.</p> + */ + final CameraDevice.StateListener mCameraStateListener = + new CameraDevice.StateListener() { + @Override + public void onOpened(CameraDevice camera) { + Log.i(TAG, "Successfully opened camera"); + mCamera = camera; + try { + List<Surface> outputs = Arrays.asList( + mSurfaceView.getHolder().getSurface(), mCaptureBuffer.getSurface()); + camera.createCaptureSession(outputs, mCaptureSessionListener, + mBackgroundHandler); + } catch (CameraAccessException ex) { + Log.e(TAG, "Failed to create a capture session", ex); + } + + // Control flow continues in mCaptureSessionListener.onConfigured() + } + + @Override + public void onDisconnected(CameraDevice camera) { + Log.e(TAG, "Camera was disconnected"); + } + + @Override + public void onError(CameraDevice camera, int error) { + Log.e(TAG, "State error on device '" + camera.getId() + "': code " + error); + }}; + + /** + * Callbacks invoked upon state changes in our {@code CameraCaptureSession}. <p>These are run on + * {@code mBackgroundThread}.</p> + */ + final CameraCaptureSession.StateListener mCaptureSessionListener = + new CameraCaptureSession.StateListener() { + @Override + public void onConfigured(CameraCaptureSession session) { + Log.i(TAG, "Finished configuring camera outputs"); + mCaptureSession = session; + + SurfaceHolder holder = mSurfaceView.getHolder(); + if (holder != null) { + try { + // Build a request for preview footage + CaptureRequest.Builder requestBuilder = + mCamera.createCaptureRequest(mCamera.TEMPLATE_PREVIEW); + requestBuilder.addTarget(holder.getSurface()); + CaptureRequest previewRequest = requestBuilder.build(); + + // Start displaying preview images + try { + session.setRepeatingRequest(previewRequest, /*listener*/null, + /*handler*/null); + } catch (CameraAccessException ex) { + Log.e(TAG, "Failed to make repeating preview request", ex); + } + } catch (CameraAccessException ex) { + Log.e(TAG, "Failed to build preview request", ex); + } + } + else { + Log.e(TAG, "Holder didn't exist when trying to formulate preview request"); + } + } + + @Override + public void onClosed(CameraCaptureSession session) { + mCaptureSession = null; + } + + @Override + public void onConfigureFailed(CameraCaptureSession session) { + Log.e(TAG, "Configuration error on device '" + mCamera.getId()); + }}; + + /** + * Callback invoked when we've received a JPEG image from the camera. + */ + final ImageReader.OnImageAvailableListener mImageCaptureListener = + new ImageReader.OnImageAvailableListener() { + @Override + public void onImageAvailable(ImageReader reader) { + // Save the image once we get a chance + mBackgroundHandler.post(new CapturedImageSaver(reader.acquireNextImage())); + + // Control flow continues in CapturedImageSaver#run() + }}; + + /** + * Deferred processor responsible for saving snapshots to disk. <p>This is run on + * {@code mBackgroundThread}.</p> + */ + static class CapturedImageSaver implements Runnable { + /** The image to save. */ + private Image mCapture; + + public CapturedImageSaver(Image capture) { + mCapture = capture; + } + + @Override + public void run() { + try { + // Choose an unused filename under the Pictures/ directory + File file = File.createTempFile(CAPTURE_FILENAME_PREFIX, ".jpg", + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES)); + try (FileOutputStream ostream = new FileOutputStream(file)) { + Log.i(TAG, "Retrieved image is" + + (mCapture.getFormat() == ImageFormat.JPEG ? "" : "n't") + " a JPEG"); + ByteBuffer buffer = mCapture.getPlanes()[0].getBuffer(); + Log.i(TAG, "Captured image size: " + + mCapture.getWidth() + 'x' + mCapture.getHeight()); + + // Write the image out to the chosen file + byte[] jpeg = new byte[buffer.remaining()]; + buffer.get(jpeg); + ostream.write(jpeg); + } catch (FileNotFoundException ex) { + Log.e(TAG, "Unable to open output file for writing", ex); + } catch (IOException ex) { + Log.e(TAG, "Failed to write the image to the output file", ex); + } + } catch (IOException ex) { + Log.e(TAG, "Unable to create a new output file", ex); + } finally { + mCapture.close(); + } + } + } +} diff --git a/tests/Camera2Tests/CameraToo/tests/Android.mk b/tests/Camera2Tests/CameraToo/tests/Android.mk new file mode 100644 index 0000000..0b58243 --- /dev/null +++ b/tests/Camera2Tests/CameraToo/tests/Android.mk @@ -0,0 +1,25 @@ +# Copyright (C) 2014 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. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := CameraTooTests +LOCAL_INSTRUMENTATION_FOR := CameraToo +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target + +include $(BUILD_PACKAGE) diff --git a/tests/Camera2Tests/CameraToo/tests/AndroidManifest.xml b/tests/Camera2Tests/CameraToo/tests/AndroidManifest.xml new file mode 100644 index 0000000..30210ba --- /dev/null +++ b/tests/Camera2Tests/CameraToo/tests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + Copyright (C) 2014 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.camera2.cameratoo.tests"> + <uses-permission android:name="android.permission.CAMERA" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <application android:label="CameraToo"> + <uses-library android:name="android.test.runner" /> + </application> + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.example.android.camera2.cameratoo" + android:label="CameraToo tests" /> +</manifest> diff --git a/tests/Camera2Tests/CameraToo/tests/src/com/example/android/camera2/cameratoo/CameraTooTest.java b/tests/Camera2Tests/CameraToo/tests/src/com/example/android/camera2/cameratoo/CameraTooTest.java new file mode 100644 index 0000000..3acca5a --- /dev/null +++ b/tests/Camera2Tests/CameraToo/tests/src/com/example/android/camera2/cameratoo/CameraTooTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2014 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.example.android.camera2.cameratoo; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.media.Image; +import android.os.Environment; +import android.util.Size; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + +import com.example.android.camera2.cameratoo.CameraTooActivity; +import org.junit.Test; + +public class CameraTooTest { + private <T> void assertComparatorEq(T lhs, T rhs, Comparator<T> rel) { + assertEquals(String.format("%s should be equal to %s", lhs, rhs), rel.compare(lhs, rhs), 0); + assertEquals(String.format("%s should be equal to %s (reverse check)", lhs, rhs), + rel.compare(rhs, lhs), 0); + } + + private <T> void assertComparatorLt(T lhs, T rhs, Comparator<T> rel) { + assertTrue(String.format("%s should be less than %s", lhs, rhs), rel.compare(lhs, rhs) < 0); + assertTrue(String.format("%s should be less than %s (reverse check)", lhs, rhs), + rel.compare(rhs, lhs) > 0); + } + + @Test + public void compareSizesByArea() { + Size empty = new Size(0, 0), fatAndFlat = new Size(100, 0), tallAndThin = new Size(0, 100); + Size smallSquare = new Size(4, 4), horizRect = new Size(8, 2), vertRect = new Size(2, 8); + Size largeSquare = new Size(5, 5); + Comparator<Size> rel = new CameraTooActivity.CompareSizesByArea(); + + assertComparatorEq(empty, fatAndFlat, rel); + assertComparatorEq(empty, tallAndThin, rel); + assertComparatorEq(fatAndFlat, empty, rel); + assertComparatorEq(fatAndFlat, tallAndThin, rel); + assertComparatorEq(tallAndThin, empty, rel); + assertComparatorEq(tallAndThin, fatAndFlat, rel); + + assertComparatorEq(smallSquare, horizRect, rel); + assertComparatorEq(smallSquare, vertRect, rel); + assertComparatorEq(horizRect, smallSquare, rel); + assertComparatorEq(horizRect, vertRect, rel); + assertComparatorEq(vertRect, smallSquare, rel); + assertComparatorEq(vertRect, horizRect, rel); + + assertComparatorLt(empty, smallSquare, rel); + assertComparatorLt(empty, horizRect, rel); + assertComparatorLt(empty, vertRect, rel); + + assertComparatorLt(fatAndFlat, smallSquare, rel); + assertComparatorLt(fatAndFlat, horizRect, rel); + assertComparatorLt(fatAndFlat, vertRect, rel); + + assertComparatorLt(tallAndThin, smallSquare, rel); + assertComparatorLt(tallAndThin, horizRect, rel); + assertComparatorLt(tallAndThin, vertRect, rel); + + assertComparatorLt(empty, largeSquare, rel); + assertComparatorLt(fatAndFlat, largeSquare, rel); + assertComparatorLt(tallAndThin, largeSquare, rel); + assertComparatorLt(smallSquare, largeSquare, rel); + assertComparatorLt(horizRect, largeSquare, rel); + assertComparatorLt(vertRect, largeSquare, rel); + } + + private void assertOptimalSize(Size[] options, int minWidth, int minHeight, Size expected) { + Size verdict = CameraTooActivity.chooseBigEnoughSize(options, minWidth, minHeight); + assertEquals(String.format("Expected optimal size %s but got %s", expected, verdict), + verdict, expected); + } + + @Test + public void chooseBigEnoughSize() { + Size empty = new Size(0, 0), fatAndFlat = new Size(100, 0), tallAndThin = new Size(0, 100); + Size smallSquare = new Size(4, 4), horizRect = new Size(8, 2), vertRect = new Size(2, 8); + Size largeSquare = new Size(5, 5); + Size[] siz = + { empty, fatAndFlat, tallAndThin, smallSquare, horizRect, vertRect, largeSquare }; + + assertOptimalSize(siz, 0, 0, empty); + + assertOptimalSize(siz, 1, 0, fatAndFlat); + assertOptimalSize(siz, 0, 1, tallAndThin); + + assertOptimalSize(siz, 4, 4, smallSquare); + assertOptimalSize(siz, 1, 1, smallSquare); + assertOptimalSize(siz, 2, 1, smallSquare); + assertOptimalSize(siz, 1, 2, smallSquare); + assertOptimalSize(siz, 3, 4, smallSquare); + assertOptimalSize(siz, 4, 3, smallSquare); + + assertOptimalSize(siz, 8, 2, horizRect); + assertOptimalSize(siz, 5, 1, horizRect); + assertOptimalSize(siz, 5, 2, horizRect); + + assertOptimalSize(siz, 2, 8, vertRect); + assertOptimalSize(siz, 1, 5, vertRect); + assertOptimalSize(siz, 2, 5, vertRect); + + assertOptimalSize(siz, 5, 5, largeSquare); + assertOptimalSize(siz, 3, 5, largeSquare); + assertOptimalSize(siz, 5, 3, largeSquare); + } + + private static final FilenameFilter OUTPUT_FILE_DECIDER = new FilenameFilter() { + @Override + public boolean accept(File dir, String filename) { + return filename.indexOf("cameratoo") == 0 && + filename.indexOf(".jpg") == filename.length() - ".jpg".length(); + }}; + + private static <T> Set<T> newlyAddedElements(Set<T> before, Set<T> after) { + Set<T> result = new HashSet<T>(after); + result.removeAll(before); + return result; + } + + @Test + public void capturedImageSaver() throws FileNotFoundException, IOException { + ByteBuffer buf = ByteBuffer.allocate(25); + for(int index = 0; index < buf.capacity(); ++index) + buf.put(index, (byte) index); + + Image.Plane plane = mock(Image.Plane.class); + when(plane.getBuffer()).thenReturn(buf); + when(plane.getPixelStride()).thenReturn(1); + when(plane.getRowStride()).thenReturn(5); + + Image.Plane[] onlyPlaneThatMatters = { plane }; + Image image = mock(Image.class); + when(image.getPlanes()).thenReturn(onlyPlaneThatMatters); + when(image.getWidth()).thenReturn(5); + when(image.getHeight()).thenReturn(5); + + File picturesFolder = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); + Set<File> preListing = + new HashSet<File>(Arrays.asList(picturesFolder.listFiles(OUTPUT_FILE_DECIDER))); + + CameraTooActivity.CapturedImageSaver saver = + new CameraTooActivity.CapturedImageSaver(image); + saver.run(); + + Set<File> postListing = + new HashSet<File>(Arrays.asList(picturesFolder.listFiles(OUTPUT_FILE_DECIDER))); + Set<File> newFiles = newlyAddedElements(preListing, postListing); + + assertEquals(newFiles.size(), 1); + + File picture = newFiles.iterator().next(); + FileInputStream istream = new FileInputStream(picture); + + for(int count = 0; count < buf.capacity(); ++count) { + assertEquals(istream.read(), buf.get(count)); + } + assertEquals(istream.read(), -1); + assertTrue(picture.delete()); + } +} diff --git a/tests/JobSchedulerTestApp/Android.mk b/tests/JobSchedulerTestApp/Android.mk new file mode 100644 index 0000000..7336d8c --- /dev/null +++ b/tests/JobSchedulerTestApp/Android.mk @@ -0,0 +1,15 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := JobSchedulerTestApp + +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE) + diff --git a/tests/JobSchedulerTestApp/AndroidManifest.xml b/tests/JobSchedulerTestApp/AndroidManifest.xml new file mode 100644 index 0000000..9654197 --- /dev/null +++ b/tests/JobSchedulerTestApp/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.demo.jobSchedulerApp" > + + <uses-sdk + android:minSdkVersion="18" + android:targetSdkVersion="18" /> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> + + <application + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" + android:theme="@style/AppTheme" > + <activity + android:name="com.android.demo.jobSchedulerApp.MainActivity" + android:label="@string/app_name" + android:windowSoftInputMode="stateHidden" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <service + android:name=".service.TestJobService" + android:permission="android.permission.BIND_JOB_SERVICE" + android:exported="true"/> + </application> + +</manifest> diff --git a/tests/JobSchedulerTestApp/res/drawable-hdpi/ic_launcher.png b/tests/JobSchedulerTestApp/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..a0f7005 --- /dev/null +++ b/tests/JobSchedulerTestApp/res/drawable-hdpi/ic_launcher.png diff --git a/tests/JobSchedulerTestApp/res/drawable-mdpi/ic_launcher.png b/tests/JobSchedulerTestApp/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..a085462 --- /dev/null +++ b/tests/JobSchedulerTestApp/res/drawable-mdpi/ic_launcher.png diff --git a/tests/JobSchedulerTestApp/res/drawable-xhdpi/ic_action_refresh.png b/tests/JobSchedulerTestApp/res/drawable-xhdpi/ic_action_refresh.png Binary files differnew file mode 100644 index 0000000..4f5d255 --- /dev/null +++ b/tests/JobSchedulerTestApp/res/drawable-xhdpi/ic_action_refresh.png diff --git a/tests/JobSchedulerTestApp/res/drawable-xhdpi/ic_launcher.png b/tests/JobSchedulerTestApp/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..4f78eb8 --- /dev/null +++ b/tests/JobSchedulerTestApp/res/drawable-xhdpi/ic_launcher.png diff --git a/tests/JobSchedulerTestApp/res/drawable-xxhdpi/ic_launcher.png b/tests/JobSchedulerTestApp/res/drawable-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..b198ee3 --- /dev/null +++ b/tests/JobSchedulerTestApp/res/drawable-xxhdpi/ic_launcher.png diff --git a/tests/JobSchedulerTestApp/res/layout/activity_main.xml b/tests/JobSchedulerTestApp/res/layout/activity_main.xml new file mode 100644 index 0000000..7f4961b --- /dev/null +++ b/tests/JobSchedulerTestApp/res/layout/activity_main.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="100dp"> + <TextView + android:id="@+id/onstart_textview" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="@color/none_received" + android:gravity="center" + android:text="@string/onstarttask"/> + <TextView + android:id="@+id/onstop_textview" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="@color/none_received" + android:gravity="center" + android:text="@string/onstoptask"/> + </LinearLayout> + <Button + android:id="@+id/finished_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="20dp" + android:layout_marginBottom="5dp" + android:onClick="finishJob" + android:text="@string/finish_job_button_text"/> + + <TextView + android:id="@+id/task_params" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/defaultparamtext" + android:gravity="center" + android:textSize="20dp" + + android:padding="15dp" + android:layout_marginBottom="10dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/constraints" + android:textSize="18dp"/> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginLeft="10dp"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/connectivity" + android:layout_marginRight="10dp"/> + <RadioGroup + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <RadioButton android:id="@+id/checkbox_any" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/any"/> + <RadioButton android:id="@+id/checkbox_unmetered" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/unmetered"/> + </RadioGroup> + + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/timing"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="15dp" + android:textSize="17dp" + android:text="@string/delay"/> + <EditText + android:id="@+id/delay_time" + android:layout_width="60dp" + android:layout_height="wrap_content" + android:inputType="number"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/deadline" + android:textSize="17dp"/> + <EditText + android:id="@+id/deadline_time" + android:layout_width="60dp" + android:layout_height="wrap_content" + android:inputType="number"/> + </LinearLayout> + + </LinearLayout> + <Button + android:id="@+id/schedule_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="40dp" + android:onClick="scheduleJob" + android:text="@string/schedule_job_button_text"/> + </LinearLayout> +</LinearLayout> diff --git a/tests/JobSchedulerTestApp/res/values-v11/styles.xml b/tests/JobSchedulerTestApp/res/values-v11/styles.xml new file mode 100644 index 0000000..ff65301 --- /dev/null +++ b/tests/JobSchedulerTestApp/res/values-v11/styles.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2013 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. +--> + +<resources> + + <!-- + Base application theme for API 11+. This theme completely replaces + AppBaseTheme from res/values/styles.xml on API 11+ devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Holo.Light"> + <!-- API 11 theme customizations can go here. --> + </style> + +</resources>
\ No newline at end of file diff --git a/tests/JobSchedulerTestApp/res/values-v14/styles.xml b/tests/JobSchedulerTestApp/res/values-v14/styles.xml new file mode 100644 index 0000000..a4a443a --- /dev/null +++ b/tests/JobSchedulerTestApp/res/values-v14/styles.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2013 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. +--> + +<resources> + + <!-- + Base application theme for API 14+. This theme completely replaces + AppBaseTheme from BOTH res/values/styles.xml and + res/values-v11/styles.xml on API 14+ devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar"> + <!-- API 14 theme customizations can go here. --> + </style> + +</resources>
\ No newline at end of file diff --git a/tests/JobSchedulerTestApp/res/values/color.xml b/tests/JobSchedulerTestApp/res/values/color.xml new file mode 100644 index 0000000..7bd3a91 --- /dev/null +++ b/tests/JobSchedulerTestApp/res/values/color.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2014 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 + --> +<resources> + <color name="none_received">#999999</color> + <color name="start_received">#00FF00</color> + <color name="stop_received">#FF0000</color> +</resources>
\ No newline at end of file diff --git a/tests/JobSchedulerTestApp/res/values/strings.xml b/tests/JobSchedulerTestApp/res/values/strings.xml new file mode 100644 index 0000000..824d4b1 --- /dev/null +++ b/tests/JobSchedulerTestApp/res/values/strings.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2013 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. +--> + +<resources> + <string name="onstoptask">onStopTask</string> + <string name="onstarttask">onStartTask</string> + <string name="defaultparamtext">task params will show up here.</string> + <string name="schedule_job_button_text">Schedule Job</string> + <string name="app_name">Job Scheduler Test</string> + <string name="finish_job_button_text">taskFinished</string> + <string name="manual_sync_text">Manual Sync</string> + <string name="constraints">Constraints</string> + <string name="connectivity">Connectivity:</string> + <string name="any">Any</string> + <string name="unmetered">WiFi</string> + <string name="timing">Timing:</string> + <string name="delay">Delay:</string> + <string name="deadline">Deadline:</string> +</resources> diff --git a/tests/JobSchedulerTestApp/res/values/styles.xml b/tests/JobSchedulerTestApp/res/values/styles.xml new file mode 100644 index 0000000..43a8f2b --- /dev/null +++ b/tests/JobSchedulerTestApp/res/values/styles.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2013 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. +--> + +<resources> + + <!-- + Base application theme, dependent on API level. This theme is replaced + by AppBaseTheme from res/values-vXX/styles.xml on newer devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Light"> + <!-- + Theme customizations available in newer API levels can go in + res/values-vXX/styles.xml, while customizations related to + backward-compatibility can go here. + --> + </style> + + <!-- Application theme. --> + <style name="AppTheme" parent="AppBaseTheme"> + <!-- All customizations that are NOT specific to a particular API-level can go here. --> + </style> + +</resources>
\ No newline at end of file diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java new file mode 100644 index 0000000..15050ef --- /dev/null +++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java @@ -0,0 +1,169 @@ +/* + * Copyright 2013 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.demo.jobSchedulerApp; + +import android.app.Activity; +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.content.ComponentName; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.text.TextUtils; +import android.view.View; +import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.demo.jobSchedulerApp.service.TestJobService; + +public class MainActivity extends Activity { + + private static final String TAG = "MainActivity"; + + public static final int MSG_UNCOLOUR_START = 0; + public static final int MSG_UNCOLOUR_STOP = 1; + public static final int MSG_SERVICE_OBJ = 2; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + Resources res = getResources(); + defaultColor = res.getColor(R.color.none_received); + startJobColor = res.getColor(R.color.start_received); + stopJobColor = res.getColor(R.color.stop_received); + + // Set up UI. + mShowStartView = (TextView) findViewById(R.id.onstart_textview); + mShowStopView = (TextView) findViewById(R.id.onstop_textview); + mParamsTextView = (TextView) findViewById(R.id.task_params); + mDelayEditText = (EditText) findViewById(R.id.delay_time); + mDeadlineEditText = (EditText) findViewById(R.id.deadline_time); + mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered); + mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any); + + mServiceComponent = new ComponentName(this, TestJobService.class); + // Start service and provide it a way to communicate with us. + Intent startServiceIntent = new Intent(this, TestJobService.class); + startServiceIntent.putExtra("messenger", new Messenger(mHandler)); + startService(startServiceIntent); + } + // UI fields. + int defaultColor; + int startJobColor; + int stopJobColor; + + TextView mShowStartView; + TextView mShowStopView; + TextView mParamsTextView; + EditText mDelayEditText; + EditText mDeadlineEditText; + RadioButton mWiFiConnectivityRadioButton; + RadioButton mAnyConnectivityRadioButton; + ComponentName mServiceComponent; + /** Service object to interact scheduled jobs. */ + TestJobService mTestService; + + private static int kJobId = 0; + + Handler mHandler = new Handler(/* default looper */) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UNCOLOUR_START: + mShowStartView.setBackgroundColor(defaultColor); + break; + case MSG_UNCOLOUR_STOP: + mShowStopView.setBackgroundColor(defaultColor); + break; + case MSG_SERVICE_OBJ: + mTestService = (TestJobService) msg.obj; + mTestService.setUiCallback(MainActivity.this); + } + } + }; + + private boolean ensureTestService() { + if (mTestService == null) { + Toast.makeText(MainActivity.this, "Service null, never got callback?", + Toast.LENGTH_SHORT).show(); + return false; + } + return true; + } + + /** + * UI onclick listener to schedule a job. What this job is is defined in + * TestJobService#scheduleJob() + */ + public void scheduleJob(View v) { + if (!ensureTestService()) { + return; + } + + JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent); + + String delay = mDelayEditText.getText().toString(); + if (delay != null && !TextUtils.isEmpty(delay)) { + builder.setMinimumLatency(Long.valueOf(delay)); + } + String deadline = mDeadlineEditText.getText().toString(); + if (deadline != null && !TextUtils.isEmpty(deadline)) { + builder.setOverrideDeadline(Long.valueOf(deadline)); + } + boolean requiresUnmetered = mWiFiConnectivityRadioButton.isSelected(); + boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isSelected(); + if (requiresUnmetered) { + builder.setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED); + } else if (requiresAnyConnectivity) { + builder.setRequiredNetworkCapabilities(JobInfo.NetworkType.ANY); + } + + mTestService.scheduleJob(builder.build()); + + } + + /** + * UI onclick listener to call jobFinished() in our service. + */ + public void finishJob(View v) { + if (!ensureTestService()) { + return; + } + mTestService.callJobFinished(); + mParamsTextView.setText(""); + } + + public void onReceivedStartJob(JobParameters params) { + mShowStartView.setBackgroundColor(startJobColor); + Message m = Message.obtain(mHandler, MSG_UNCOLOUR_START); + mHandler.sendMessageDelayed(m, 1000L); // uncolour in 1 second. + mParamsTextView.setText("Executing: " + params.getJobId() + " " + params.getExtras()); + } + + public void onReceivedStopJob() { + mShowStopView.setBackgroundColor(stopJobColor); + Message m = Message.obtain(mHandler, MSG_UNCOLOUR_STOP); + mHandler.sendMessageDelayed(m, 2000L); // uncolour in 1 second. + mParamsTextView.setText(""); + } +} diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java new file mode 100644 index 0000000..bf8e887 --- /dev/null +++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java @@ -0,0 +1,119 @@ +/* + * Copyright 2013 Google Inc. + * + * 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.demo.jobSchedulerApp.service; + +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.content.Context; +import android.content.Intent; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +import com.android.demo.jobSchedulerApp.MainActivity; + +import java.util.LinkedList; + + +/** + * Service to handle sync requests. + * <p> + * This service is invoked in response to Intents with action android.content.SyncAdapter, and + * returns a Binder connection to SyncAdapter. + * <p> + * For performance, only one sync adapter will be initialized within this application's context. + * <p> + * Note: The SyncService itself is not notified when a new sync occurs. It's role is to manage the + * lifecycle of our and provide a handle to said SyncAdapter to the OS on + * request. + */ +public class TestJobService extends JobService { + private static final String TAG = "SyncService"; + + @Override + public void onCreate() { + super.onCreate(); + Log.i(TAG, "Service created"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.i(TAG, "Service destroyed"); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Messenger callback = intent.getParcelableExtra("messenger"); + Message m = Message.obtain(); + m.what = MainActivity.MSG_SERVICE_OBJ; + m.obj = this; + try { + callback.send(m); + } catch (RemoteException e) { + Log.e(TAG, "Error passing service object back to activity."); + } + return START_NOT_STICKY; + } + + @Override + public boolean onStartJob(JobParameters params) { + jobParamsMap.add(params); + if (mActivity != null) { + mActivity.onReceivedStartJob(params); + } + Log.i(TAG, "on start job: " + params.getJobId()); + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + jobParamsMap.remove(params); + mActivity.onReceivedStopJob(); + Log.i(TAG, "on stop job: " + params.getJobId()); + return true; + } + + MainActivity mActivity; + private final LinkedList<JobParameters> jobParamsMap = new LinkedList<JobParameters>(); + + public void setUiCallback(MainActivity activity) { + mActivity = activity; + } + + /** Send job to the JobScheduler. */ + public void scheduleJob(JobInfo t) { + Log.d(TAG, "Scheduling job"); + JobScheduler tm = + (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); + tm.schedule(t); + } + + public boolean callJobFinished() { + JobParameters params = jobParamsMap.poll(); + if (params == null) { + return false; + } else { + jobFinished(params, false); + return true; + } + } + +} diff --git a/tests/VoiceEnrollment/Android.mk b/tests/VoiceEnrollment/Android.mk new file mode 100644 index 0000000..2ab3d02 --- /dev/null +++ b/tests/VoiceEnrollment/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := VoiceEnrollment + +LOCAL_MODULE_TAGS := optional + +LOCAL_PRIVILEGED_MODULE := true + +include $(BUILD_PACKAGE) diff --git a/tests/VoiceEnrollment/AndroidManifest.xml b/tests/VoiceEnrollment/AndroidManifest.xml new file mode 100644 index 0000000..6321222 --- /dev/null +++ b/tests/VoiceEnrollment/AndroidManifest.xml @@ -0,0 +1,16 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.voiceenrollment"> + + <application + android:permission="android.permission.MANAGE_VOICE_KEYPHRASES"> + <activity android:name="TestEnrollmentActivity" android:label="Voice Enrollment Application" + android:theme="@android:style/Theme.Material.Light.Voice"> + <intent-filter> + <action android:name="com.android.intent.action.MANAGE_VOICE_KEYPHRASES" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + <meta-data android:name="android.voice_enrollment" + android:resource="@xml/enrollment_application"/> + </application> +</manifest> diff --git a/tests/VoiceEnrollment/res/xml/enrollment_application.xml b/tests/VoiceEnrollment/res/xml/enrollment_application.xml new file mode 100644 index 0000000..710a0ac --- /dev/null +++ b/tests/VoiceEnrollment/res/xml/enrollment_application.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2014, 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. + */ +--> + +<voice-enrollment-application xmlns:android="http://schemas.android.com/apk/res/android" + android:searchKeyphraseId="101" + android:searchKeyphrase="Hello There" + android:searchKeyphraseSupportedLocales="en-US,en-GB,fr-FR,de-DE" /> diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java new file mode 100644 index 0000000..7fbd965 --- /dev/null +++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2014 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.test.voiceenrollment; + +import android.app.Activity; + +public class TestEnrollmentActivity extends Activity { + // TODO(sansid): Add a test enrollment flow here. +} diff --git a/tests/VoiceInteraction/AndroidManifest.xml b/tests/VoiceInteraction/AndroidManifest.xml index e1a5854..33f000d 100644 --- a/tests/VoiceInteraction/AndroidManifest.xml +++ b/tests/VoiceInteraction/AndroidManifest.xml @@ -3,7 +3,7 @@ <application> <activity android:name="VoiceInteractionMain" android:label="Voice Interaction" - android:theme="@android:style/Theme.Quantum"> + android:theme="@android:style/Theme.Material"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> @@ -24,7 +24,7 @@ android:process=":session"> </service> <activity android:name="TestInteractionActivity" android:label="Voice Interaction Target" - android:theme="@android:style/Theme.Quantum.Light.Voice"> + android:theme="@android:style/Theme.Material.Light.Voice"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java index d40b05f..00c2c64 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java @@ -21,6 +21,8 @@ import android.os.Bundle; import android.service.voice.VoiceInteractionService; import android.util.Log; +import java.util.Arrays; + public class MainInteractionService extends VoiceInteractionService { static final String TAG = "MainInteractionService"; @@ -28,6 +30,9 @@ public class MainInteractionService extends VoiceInteractionService { public void onCreate() { super.onCreate(); Log.i(TAG, "Creating " + this); + Log.i(TAG, "Keyphrase enrollment error? " + getKeyphraseEnrollmentInfo().getParseError()); + Log.i(TAG, "Keyphrase enrollment meta-data: " + + Arrays.toString(getKeyphraseEnrollmentInfo().getKeyphrases())); } @Override |
