Home | History | Annotate | Download | only in intents
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.cts.verifier.camera.intents;
     17 
     18 import android.app.job.JobInfo;
     19 import android.app.job.JobParameters;
     20 import android.app.job.JobScheduler;
     21 import android.content.BroadcastReceiver;
     22 import android.content.ComponentName;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.pm.PackageManager;
     27 import android.content.pm.PermissionInfo;
     28 import android.hardware.Camera;
     29 import android.media.MediaMetadataRetriever;
     30 import android.net.Uri;
     31 import android.os.AsyncTask;
     32 import android.os.Bundle;
     33 import android.provider.MediaStore;
     34 import android.util.Log;
     35 import android.view.SurfaceHolder;
     36 import android.view.View;
     37 import android.view.View.OnClickListener;
     38 import android.widget.Button;
     39 import android.widget.ImageButton;
     40 import android.widget.TextView;
     41 import androidx.core.content.FileProvider;
     42 import android.Manifest;
     43 
     44 import com.android.cts.verifier.camera.intents.CameraContentJobService;
     45 import com.android.cts.verifier.PassFailButtons;
     46 import com.android.cts.verifier.R;
     47 import com.android.cts.verifier.TestResult;
     48 import android.widget.Toast;
     49 
     50 import static android.media.MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO;
     51 import static android.media.MediaMetadataRetriever.METADATA_KEY_LOCATION;
     52 
     53 import java.io.File;
     54 import java.util.TreeSet;
     55 import java.util.Date;
     56 import java.text.SimpleDateFormat;
     57 
     58 /**
     59  * Tests for manual verification of uri trigger and camera intents being fired.
     60  *
     61  * MediaStore.Images.Media.EXTERNAL_CONTENT_URI:
     62  * android.hardware.Camera.ACTION_NEW_PICTURE:
     63  *  These should fire when a new picture was captured by the camera app, and
     64  *  it has been added to the media store.
     65  * MediaStore.Video.Media.EXTERNAL_CONTENT_URI:
     66  * android.hardware.Camera.ACTION_NEW_VIDEO:
     67  *  These should fire when a new video has been captured by the camera app, and
     68  *  it has been added to the media store.
     69  *
     70  * The tests verify this both by asking the user to manually launch
     71  *  the camera activity, as well as by programatically launching the camera
     72  *  activity via MediaStore intents.
     73  *
     74  * Please ensure when replacing the default camera app on a device,
     75  *  that these intents are still firing as a lot of 3rd party applications
     76  *  (e.g. social network apps that upload a photo after you take a picture)
     77  *  rely on this functionality present and correctly working.
     78  */
     79 public class CameraIntentsActivity extends PassFailButtons.Activity
     80 implements OnClickListener, SurfaceHolder.Callback {
     81 
     82     private static final String TAG = "CameraIntents";
     83     private static final int STATE_OFF = 0;
     84     private static final int STATE_STARTED = 1;
     85     private static final int STATE_SUCCESSFUL = 2;
     86     private static final int STATE_FAILED = 3;
     87 
     88     private static final int STAGE_APP_PICTURE = 0;
     89     private static final int STAGE_APP_VIDEO = 1;
     90     private static final int STAGE_INTENT_PICTURE = 2;
     91     private static final int STAGE_INTENT_VIDEO = 3;
     92     private static final int NUM_STAGES = 4;
     93     private static final String STAGE_INDEX_EXTRA = "stageIndex";
     94 
     95     private static String[]  EXPECTED_INTENTS = new String[] {
     96         Camera.ACTION_NEW_PICTURE,
     97         Camera.ACTION_NEW_VIDEO,
     98         null,
     99         Camera.ACTION_NEW_VIDEO
    100     };
    101 
    102     private ImageButton mPassButton;
    103     private ImageButton mFailButton;
    104     private Button mStartTestButton;
    105     private Button mSettingsButton;
    106     private File mVideoTargetDir = null;
    107     private File mVideoTarget = null;
    108     private int mState = STATE_OFF;
    109     // MediaStore.Images.Media.EXTERNAL_CONTENT_URI or
    110     // MediaStore.Video.Media.EXTERNAL_CONTENT_URI are successfully received.
    111     private boolean mUriSuccess = false;
    112     // android.hardware.Camera.ACTION_NEW_PICTURE or
    113     // android.hardware.Camera.ACTION_NEW_VIDEO are successfully received.
    114     private boolean mActionSuccess = false;
    115     private Object mLock = new Object();
    116 
    117     private BroadcastReceiver mReceiver;
    118     private IntentFilter mFilterPicture;
    119     private boolean mActivityResult = false;
    120     private boolean mDetectCheating = false;
    121 
    122     private StringBuilder mReportBuilder = new StringBuilder();
    123     private final TreeSet<String> mTestedCombinations = new TreeSet<String>();
    124     private final TreeSet<String> mUntestedCombinations = new TreeSet<String>();
    125 
    126     private CameraContentJobService.TestEnvironment mTestEnv;
    127     private static final int CAMERA_JOB_ID = CameraIntentsActivity.class.hashCode();
    128     private static final int JOB_TYPE_IMAGE = 0;
    129     private static final int JOB_TYPE_VIDEO = 1;
    130 
    131     private static int[] TEST_JOB_TYPES = new int[] {
    132         JOB_TYPE_IMAGE,
    133         JOB_TYPE_VIDEO,
    134         JOB_TYPE_IMAGE,
    135         JOB_TYPE_VIDEO
    136     };
    137 
    138     private JobInfo makeJobInfo(int jobType) {
    139         JobInfo.Builder builder = new JobInfo.Builder(CAMERA_JOB_ID,
    140                 new ComponentName(this, CameraContentJobService.class));
    141         // Look for specific changes to images in the provider.
    142         Uri uriToTrigger = null;
    143         switch (jobType) {
    144             case JOB_TYPE_IMAGE:
    145                 uriToTrigger = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    146                 break;
    147             case JOB_TYPE_VIDEO:
    148                 uriToTrigger = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    149                 break;
    150             default:
    151                 Log.e(TAG, "Unknown jobType" + jobType);
    152                 return null;
    153         }
    154         builder.addTriggerContentUri(new JobInfo.TriggerContentUri(
    155                 uriToTrigger,
    156                 JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
    157         // For testing purposes, react quickly.
    158         builder.setTriggerContentUpdateDelay(100);
    159         builder.setTriggerContentMaxDelay(100);
    160         return builder.build();
    161     }
    162 
    163     /* Callback from mReceiver#onReceive */
    164     public void onReceivedIntent(Intent intent) {
    165         Log.v(TAG, "Received intent " + intent.toString());
    166         synchronized(mLock) {
    167             if (mState == STATE_STARTED) {
    168 
    169                 /* this can happen if..
    170                   the camera apps intent finishes,
    171                   user returns to cts verifier,
    172                   user leaves cts verifier and tries to fake receiver intents
    173                   */
    174                 if (mDetectCheating) {
    175                     Log.w(TAG, "Cheating attempt suppressed");
    176 
    177                     mState = STATE_FAILED;
    178                 }
    179 
    180                 String expectedIntent = EXPECTED_INTENTS[getStageIndex()];
    181                 if (expectedIntent != intent.getAction()) {
    182                     Log.e(TAG, "FAIL: Test # " + getStageIndex()
    183                         + " must not broadcast "
    184                         + intent.getAction()
    185                         + ", expected: "
    186                         + (expectedIntent != null ? expectedIntent : "no intent"));
    187 
    188                     mState = STATE_FAILED;
    189                 }
    190 
    191                 if (mState != STATE_FAILED) {
    192                     mActionSuccess = true;
    193                 }
    194                 updateSuccessState();
    195             }
    196         }
    197     }
    198 
    199     private void updateSuccessState() {
    200         if (mActionSuccess && mUriSuccess) {
    201             mState = STATE_SUCCESSFUL;
    202         }
    203 
    204         setPassButton(mState == STATE_SUCCESSFUL);
    205     }
    206 
    207     private void setPassButton(Boolean pass) {
    208         mPassButton.setEnabled(pass);
    209         mFailButton.setEnabled(!pass);
    210     }
    211 
    212     private int getStageIndex()
    213     {
    214         final int stageIndex = getIntent().getIntExtra(STAGE_INDEX_EXTRA, 0);
    215         return stageIndex;
    216     }
    217 
    218     private String getStageString(int stageIndex)
    219     {
    220         if (stageIndex == STAGE_APP_PICTURE) {
    221             return "Application Picture";
    222         }
    223         if (stageIndex == STAGE_APP_VIDEO) {
    224             return "Application Video";
    225         }
    226         if (stageIndex == STAGE_INTENT_PICTURE) {
    227             return "Intent Picture";
    228         }
    229         if (stageIndex == STAGE_INTENT_VIDEO) {
    230             return "Intent Video";
    231         }
    232 
    233         return "Unknown!!!";
    234     }
    235 
    236     private String getStageIntentString(int stageIndex)
    237     {
    238         if (stageIndex == STAGE_APP_PICTURE) {
    239             return android.hardware.Camera.ACTION_NEW_PICTURE;
    240         }
    241         if (stageIndex == STAGE_APP_VIDEO) {
    242             return android.hardware.Camera.ACTION_NEW_VIDEO;
    243         }
    244         if (stageIndex == STAGE_INTENT_PICTURE) {
    245             return android.hardware.Camera.ACTION_NEW_PICTURE;
    246         }
    247         if (stageIndex == STAGE_INTENT_VIDEO) {
    248             return android.hardware.Camera.ACTION_NEW_VIDEO;
    249         }
    250 
    251         return "Unknown Intent!!!";
    252     }
    253 
    254     private String getStageInstructionLabel(int stageIndex)
    255     {
    256         if (stageIndex == STAGE_APP_PICTURE) {
    257             return getString(R.string.ci_instruction_text_app_picture_label);
    258         }
    259         if (stageIndex == STAGE_APP_VIDEO) {
    260             return getString(R.string.ci_instruction_text_app_video_label);
    261         }
    262         if (stageIndex == STAGE_INTENT_PICTURE) {
    263             return getString(R.string.ci_instruction_text_intent_picture_label);
    264         }
    265         if (stageIndex == STAGE_INTENT_VIDEO) {
    266             return getString(R.string.ci_instruction_text_intent_video_label);
    267         }
    268 
    269         return "Unknown Instruction Label!!!";
    270     }
    271 
    272     @Override
    273     public void onCreate(Bundle savedInstanceState) {
    274         super.onCreate(savedInstanceState);
    275 
    276         setContentView(R.layout.ci_main);
    277         setPassFailButtonClickListeners();
    278         setInfoResources(R.string.camera_intents, R.string.ci_info, -1);
    279 
    280         mPassButton         = (ImageButton) findViewById(R.id.pass_button);
    281         mFailButton         = (ImageButton) findViewById(R.id.fail_button);
    282         mStartTestButton  = (Button) findViewById(R.id.start_test_button);
    283         mSettingsButton  = (Button) findViewById(R.id.settings_button);
    284         mStartTestButton.setOnClickListener(this);
    285         mSettingsButton.setOnClickListener(this);
    286 
    287         // This activity is reused multiple times
    288         // to test each camera/intents combination
    289         final int stageIndex = getIntent().getIntExtra(STAGE_INDEX_EXTRA, 0);
    290 
    291         // Hitting the pass button goes to the next test activity.
    292         // Only the last one uses the PassFailButtons click callback function,
    293         // which gracefully terminates the activity.
    294         if (stageIndex + 1 < NUM_STAGES) {
    295             setPassButtonGoesToNextStage(stageIndex);
    296         }
    297         resetButtons();
    298 
    299         // Set initial values
    300 
    301         TextView intentsLabel =
    302                 (TextView) findViewById(R.id.intents_text);
    303         intentsLabel.setText(
    304                 getString(R.string.ci_intents_label)
    305                 + " "
    306                 + Integer.toString(getStageIndex()+1)
    307                 + " of "
    308                 + Integer.toString(NUM_STAGES)
    309                 + ": "
    310                 + getStageIntentString(getStageIndex())
    311                 );
    312 
    313         TextView instructionLabel =
    314                 (TextView) findViewById(R.id.instruction_text);
    315         instructionLabel.setText(R.string.ci_instruction_text_photo_label);
    316 
    317         /* Display the instructions to launch camera app and take a photo */
    318         TextView cameraExtraLabel =
    319                 (TextView) findViewById(R.id.instruction_extra_text);
    320         cameraExtraLabel.setText(getStageInstructionLabel(getStageIndex()));
    321 
    322         mStartTestButton.setEnabled(true);
    323         mSettingsButton.setEnabled(true);
    324 
    325         mReceiver = new BroadcastReceiver() {
    326             @Override
    327             public void onReceive(Context context, Intent intent) {
    328                 onReceivedIntent(intent);
    329             }
    330         };
    331 
    332         mFilterPicture = new IntentFilter();
    333         mFilterPicture.addAction(Camera.ACTION_NEW_PICTURE);
    334         mFilterPicture.addAction(Camera.ACTION_NEW_VIDEO);
    335 
    336         try {
    337             mFilterPicture.addDataType("video/*");
    338             mFilterPicture.addDataType("image/*");
    339         }
    340         catch(IntentFilter.MalformedMimeTypeException e) {
    341             Log.e(TAG, "Caught exceptione e " + e.toString());
    342         }
    343         registerReceiver(mReceiver, mFilterPicture);
    344     }
    345 
    346     @Override
    347     public void onDestroy() {
    348         super.onDestroy();
    349         Log.v(TAG, "onDestroy");
    350         this.unregisterReceiver(mReceiver);
    351     }
    352 
    353     @Override
    354     public void onResume() {
    355         super.onResume();
    356         mFailButton.setEnabled(false);
    357         /**
    358          * If location is not enabled, fail buttons should be disabled, since they take us back to
    359          * the original CTS Verifier activity where other tests might depend on these
    360          * If we're in STAGE_INTENT_VIDEO even the pass button should be disabled till location
    361          * access is turned back on for CTS Verifier.
    362          */
    363         Boolean locationEnabled = (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) ==
    364                 PackageManager.PERMISSION_GRANTED);
    365 
    366         if (getStageIndex() == STAGE_INTENT_VIDEO) {
    367                 /**
    368                  * Don't enable the pass /fail button till the user grants CTS verifier location
    369                  * access again.
    370                  */
    371                 if (mActionSuccess) {
    372                     mState = STATE_SUCCESSFUL;
    373                 }
    374                 mPassButton.setEnabled(false);
    375                 if (locationEnabled) {
    376                     if (mState == STATE_SUCCESSFUL) {
    377                         mPassButton.setEnabled(true);
    378                     } else {
    379                         mFailButton.setEnabled(true);
    380                     }
    381                 } else if (mState != STATE_OFF) {
    382                     Toast.makeText(this, R.string.ci_location_permissions_error,
    383                             Toast.LENGTH_SHORT).show();
    384                 }
    385         } else {
    386             if (locationEnabled) {
    387                 mFailButton.setEnabled(true);
    388             } else {
    389                 Toast.makeText(this, R.string.ci_location_permissions_fail_error,
    390                     Toast.LENGTH_SHORT).show();
    391             }
    392         }
    393     }
    394 
    395     @Override
    396     public void onPause() {
    397         super.onPause();
    398         /*
    399         When testing INTENT_PICTURE, INTENT_VIDEO,
    400         do not allow user to cheat by going to camera app and re-firing
    401         the intents by taking a photo/video
    402         */
    403         if (getStageIndex() == STAGE_INTENT_PICTURE ||
    404             getStageIndex() == STAGE_INTENT_VIDEO) {
    405 
    406             if (mActivityResult && mState == STATE_STARTED) {
    407                 mDetectCheating = true;
    408                 Log.w(TAG, "Potential cheating detected");
    409             }
    410         }
    411 
    412     }
    413 
    414     @Override
    415     protected void onActivityResult(
    416         int requestCode, int resultCode, Intent data) {
    417         int stageIndex = getStageIndex();
    418         if (requestCode == 1337 + stageIndex) {
    419             Log.v(TAG, "Activity we launched was finished");
    420             mActivityResult = true;
    421             synchronized(mLock) {
    422                 if (mState != STATE_FAILED) {
    423                     /**
    424                      * For images, we don't need to do more checks, since location in image exif is
    425                      * checked by cts test: MediaStoreUiTest .
    426                      */
    427                     if (stageIndex == STAGE_INTENT_PICTURE) {
    428                         mActionSuccess = true;
    429                         // Set UriSuccess to true to avoid long wait in WaitForTriggerTask
    430                         mUriSuccess = true;
    431                         updateSuccessState();
    432                         return;
    433                     }
    434                     if (stageIndex != STAGE_INTENT_VIDEO) {
    435                         return;
    436                     }
    437 
    438                     if (mVideoTarget == null) {
    439                         Log.d(TAG, "Video target was not set");
    440                         return;
    441                     }
    442                     /**
    443                      * Check that there is no location data in video.
    444                      */
    445                     MediaMetadataRetriever mediaRetriever = new MediaMetadataRetriever();
    446                     mediaRetriever.setDataSource(mVideoTarget.toString());
    447                     if (mediaRetriever.extractMetadata(METADATA_KEY_HAS_VIDEO) == null ||
    448                         mediaRetriever.extractMetadata(METADATA_KEY_LOCATION) != null) {
    449                         mState = STATE_FAILED;
    450                     } else {
    451                         mVideoTarget.delete();
    452                     }
    453                     Log.d(TAG, "METADATA_KEY_HAS_VIDEO: " +
    454                             mediaRetriever.extractMetadata(METADATA_KEY_HAS_VIDEO) +
    455                             " METADATA_KEY_LOCATION: " +
    456                             mediaRetriever.extractMetadata(METADATA_KEY_LOCATION));
    457                     mediaRetriever.release();
    458                     /* successful, unless we get the URI trigger back
    459                      at some point later on */
    460                     mActionSuccess = true;
    461                 }
    462             }
    463         }
    464     }
    465 
    466     @Override
    467     public String getTestDetails() {
    468         return mReportBuilder.toString();
    469     }
    470 
    471     private class WaitForTriggerTask extends AsyncTask<Void, Void, Boolean> {
    472         protected Boolean doInBackground(Void... param) {
    473             try {
    474                 boolean executed = mTestEnv.awaitExecution();
    475                 synchronized(mLock) {
    476                     // Check latest test param
    477                     if (executed && mState == STATE_STARTED) {
    478 
    479                         // this can happen if..
    480                         //  the camera apps intent finishes,
    481                         //  user returns to cts verifier,
    482                         //  user leaves cts verifier and tries to fake receiver intents
    483                         if (mDetectCheating) {
    484                             Log.w(TAG, "Cheating attempt suppressed");
    485                             mState = STATE_FAILED;
    486                         }
    487 
    488                         // For STAGE_INTENT_PICTURE test, if EXTRA_OUTPUT is not assigned in intent,
    489                         // file should NOT be saved so triggering this is a test failure.
    490                         if (getStageIndex() == STAGE_INTENT_PICTURE) {
    491                             Log.e(TAG, "FAIL: STAGE_INTENT_PICTURE test should not create file");
    492                             mState = STATE_FAILED;
    493                         }
    494 
    495                         if (mState != STATE_FAILED) {
    496                             return true;
    497                         } else {
    498                             return false;
    499                         }
    500                     }
    501                 }
    502             } catch (InterruptedException e) {
    503                 e.printStackTrace();
    504             }
    505 
    506             if (getStageIndex() == STAGE_INTENT_PICTURE) {
    507                 // STAGE_INTENT_PICTURE should timeout
    508                 return true;
    509             } else {
    510                 Log.e(TAG, "FAIL: timeout waiting for URI trigger");
    511                 return false;
    512             }
    513         }
    514 
    515         protected void onPostExecute(Boolean pass) {
    516             synchronized(mLock) {
    517                 mUriSuccess = pass;
    518                 updateSuccessState();
    519             }
    520         }
    521     }
    522 
    523     @Override
    524     public void onClick(View view) {
    525         Log.v(TAG, "Click detected");
    526 
    527         final int stageIndex = getStageIndex();
    528         if (view == mSettingsButton) {
    529             Log.v(TAG, "Opening up Settings app");
    530             startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
    531         }
    532 
    533         if (view == mStartTestButton) {
    534             Log.v(TAG, "Starting testing... ");
    535 
    536             mState = STATE_STARTED;
    537             mUriSuccess = false;
    538             mActionSuccess = false;
    539 
    540             JobScheduler jobScheduler = (JobScheduler) getSystemService(
    541                     Context.JOB_SCHEDULER_SERVICE);
    542             jobScheduler.cancelAll();
    543 
    544             mTestEnv = CameraContentJobService.TestEnvironment.getTestEnvironment();
    545 
    546             mTestEnv.setUp();
    547 
    548             /**
    549              * Video intents do not need to wait on a ContentProvider broadcast since we're starting
    550              * the intent activity with EXTRA_OUTPUT set
    551              */
    552             if (stageIndex != STAGE_INTENT_VIDEO) {
    553                 JobInfo job = makeJobInfo(TEST_JOB_TYPES[stageIndex]);
    554                 jobScheduler.schedule(job);
    555                 new WaitForTriggerTask().execute();
    556             }
    557 
    558             /* we can allow user to fail immediately if location is on, otherwise they must
    559              * enable location */
    560             if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) ==
    561                 PackageManager.PERMISSION_GRANTED) {
    562                 mFailButton.setEnabled(true);
    563             }
    564 
    565             /* trigger an ACTION_IMAGE_CAPTURE intent
    566                 which will run the camera app itself */
    567             String intentStr = null;
    568             Intent cameraIntent = null;
    569             if (stageIndex == STAGE_INTENT_PICTURE) {
    570                 intentStr = android.provider.MediaStore.ACTION_IMAGE_CAPTURE;
    571             }
    572             else if (stageIndex == STAGE_INTENT_VIDEO) {
    573                 intentStr = android.provider.MediaStore.ACTION_VIDEO_CAPTURE;
    574             }
    575 
    576             if (intentStr != null) {
    577                 cameraIntent = new Intent(intentStr);
    578                 if (stageIndex == STAGE_INTENT_VIDEO) {
    579                     String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    580                     mVideoTargetDir = new File(this.getFilesDir(), "debug");
    581                     mVideoTarget = new File(mVideoTargetDir, timeStamp  + "video.mp4");
    582                     mVideoTargetDir.mkdirs();
    583                     if (!mVideoTargetDir.exists()) {
    584                         Toast.makeText(this, R.string.ci_directory_creation_error,
    585                                 Toast.LENGTH_SHORT).show();
    586                         Log.v(TAG, "Could not create directory");
    587                         return;
    588                     }
    589                     cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(this,
    590                               "com.android.cts.verifier.managedprovisioning.fileprovider",
    591                               mVideoTarget));
    592                 }
    593                 startActivityForResult(cameraIntent, 1337 + getStageIndex());
    594             }
    595 
    596             mStartTestButton.setEnabled(false);
    597         }
    598 
    599         if(view == mPassButton || view == mFailButton) {
    600             // Stop any running wait
    601             mTestEnv.cancelWait();
    602 
    603             for (int counter = 0; counter < NUM_STAGES; counter++) {
    604                 String combination = getStageString(counter) + "\n";
    605 
    606                 if(counter < stageIndex) {
    607                     // test already passed, or else wouldn't have made
    608                     // it to current stageIndex
    609                     mTestedCombinations.add(combination);
    610                 }
    611 
    612                 if(counter == stageIndex) {
    613                     // current test configuration
    614                     if(view == mPassButton) {
    615                         mTestedCombinations.add(combination);
    616                     }
    617                     else if(view == mFailButton) {
    618                         mUntestedCombinations.add(combination);
    619                     }
    620                 }
    621 
    622                 if(counter > stageIndex) {
    623                     // test not passed yet, since haven't made it to
    624                     // stageIndex
    625                     mUntestedCombinations.add(combination);
    626                 }
    627 
    628                 counter++;
    629             }
    630 
    631             mReportBuilder = new StringBuilder();
    632             mReportBuilder.append("Passed combinations:\n");
    633             for (String combination : mTestedCombinations) {
    634                 mReportBuilder.append(combination);
    635             }
    636             mReportBuilder.append("Failed/untested combinations:\n");
    637             for (String combination : mUntestedCombinations) {
    638                 mReportBuilder.append(combination);
    639             }
    640 
    641             if(view == mPassButton) {
    642                 TestResult.setPassedResult(this, "CameraIntentsActivity",
    643                         getTestDetails());
    644             }
    645             if(view == mFailButton) {
    646                 TestResult.setFailedResult(this, "CameraIntentsActivity",
    647                         getTestDetails());
    648             }
    649 
    650             // restart activity to test next intents
    651             Intent intent = new Intent(CameraIntentsActivity.this,
    652                     CameraIntentsActivity.class);
    653             intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
    654                     | Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    655             intent.putExtra(STAGE_INDEX_EXTRA, stageIndex + 1);
    656             startActivity(intent);
    657         }
    658     }
    659 
    660     private void resetButtons() {
    661         enablePassFailButtons(false);
    662     }
    663 
    664     private void enablePassFailButtons(boolean enable) {
    665         mPassButton.setEnabled(enable);
    666         mFailButton.setEnabled(enable);
    667     }
    668 
    669     @Override
    670     public void surfaceChanged(SurfaceHolder holder, int format, int width,
    671             int height) {
    672     }
    673 
    674     @Override
    675     public void surfaceCreated(SurfaceHolder holder) {
    676         // Auto-generated method stub
    677     }
    678 
    679     @Override
    680     public void surfaceDestroyed(SurfaceHolder holder) {
    681         // Auto-generated method stub
    682     }
    683 
    684     private void setPassButtonGoesToNextStage(final int stageIndex) {
    685         findViewById(R.id.pass_button).setOnClickListener(this);
    686     }
    687 
    688 }
    689