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.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.hardware.Camera;
     26 import android.net.Uri;
     27 import android.os.AsyncTask;
     28 import android.os.Bundle;
     29 import android.provider.MediaStore;
     30 import android.util.Log;
     31 import android.view.SurfaceHolder;
     32 import android.view.View;
     33 import android.view.View.OnClickListener;
     34 import android.widget.Button;
     35 import android.widget.ImageButton;
     36 import android.widget.TextView;
     37 
     38 import com.android.cts.verifier.camera.intents.CameraContentJobService;
     39 import com.android.cts.verifier.PassFailButtons;
     40 import com.android.cts.verifier.R;
     41 import com.android.cts.verifier.TestResult;
     42 
     43 import java.util.TreeSet;
     44 
     45 /**
     46  * Tests for manual verification of uri trigger being fired.
     47  *
     48  * MediaStore.Images.Media.EXTERNAL_CONTENT_URI - this should fire
     49  *  when a new picture was captured by the camera app, and it has been
     50  *  added to the media store.
     51  * MediaStore.Video.Media.EXTERNAL_CONTENT_URI - this should fire when a new
     52  *  video has been captured by the camera app, and it has been added
     53  *  to the media store.
     54  *
     55  * The tests verify this both by asking the user to manually launch
     56  *  the camera activity, as well as by programatically launching the camera
     57  *  activity via MediaStore intents.
     58  *
     59  * Please ensure when replacing the default camera app on a device,
     60  *  that these intents are still firing as a lot of 3rd party applications
     61  *  (e.g. social network apps that upload a photo after you take a picture)
     62  *  rely on this functionality present and correctly working.
     63  */
     64 public class CameraIntentsActivity extends PassFailButtons.Activity
     65 implements OnClickListener, SurfaceHolder.Callback {
     66 
     67     private static final String TAG = "CameraIntents";
     68     private static final int STATE_OFF = 0;
     69     private static final int STATE_STARTED = 1;
     70     private static final int STATE_SUCCESSFUL = 2;
     71     private static final int STATE_FAILED = 3;
     72     private static final int NUM_STAGES = 4;
     73     private static final String STAGE_INDEX_EXTRA = "stageIndex";
     74 
     75     private static final int STAGE_APP_PICTURE = 0;
     76     private static final int STAGE_APP_VIDEO = 1;
     77     private static final int STAGE_INTENT_PICTURE = 2;
     78     private static final int STAGE_INTENT_VIDEO = 3;
     79 
     80     private ImageButton mPassButton;
     81     private ImageButton mFailButton;
     82     private Button mStartTestButton;
     83 
     84     private int mState = STATE_OFF;
     85 
     86     private boolean mActivityResult = false;
     87     private boolean mDetectCheating = false;
     88 
     89     private StringBuilder mReportBuilder = new StringBuilder();
     90     private final TreeSet<String> mTestedCombinations = new TreeSet<String>();
     91     private final TreeSet<String> mUntestedCombinations = new TreeSet<String>();
     92 
     93     private CameraContentJobService.TestEnvironment mTestEnv;
     94     private static final int CAMERA_JOB_ID = CameraIntentsActivity.class.hashCode();
     95     private static final int JOB_TYPE_IMAGE = 0;
     96     private static final int JOB_TYPE_VIDEO = 1;
     97 
     98     private static int[] TEST_JOB_TYPES = new int[] {
     99         JOB_TYPE_IMAGE,
    100         JOB_TYPE_VIDEO,
    101         JOB_TYPE_IMAGE,
    102         JOB_TYPE_VIDEO
    103     };
    104 
    105     private JobInfo makeJobInfo(int jobType) {
    106         JobInfo.Builder builder = new JobInfo.Builder(CAMERA_JOB_ID,
    107                 new ComponentName(this, CameraContentJobService.class));
    108         // Look for specific changes to images in the provider.
    109         Uri uriToTrigger = null;
    110         switch (jobType) {
    111             case JOB_TYPE_IMAGE:
    112                 uriToTrigger = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    113                 break;
    114             case JOB_TYPE_VIDEO:
    115                 uriToTrigger = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    116                 break;
    117             default:
    118                 Log.e(TAG, "Unknown jobType" + jobType);
    119                 return null;
    120         }
    121         builder.addTriggerContentUri(new JobInfo.TriggerContentUri(
    122                 uriToTrigger,
    123                 JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
    124         // For testing purposes, react quickly.
    125         builder.setTriggerContentUpdateDelay(100);
    126         builder.setTriggerContentMaxDelay(100);
    127         return builder.build();
    128     }
    129 
    130     private int getStageIndex()
    131     {
    132         final int stageIndex = getIntent().getIntExtra(STAGE_INDEX_EXTRA, 0);
    133         return stageIndex;
    134     }
    135 
    136     private String getStageString(int stageIndex)
    137     {
    138         if (stageIndex == STAGE_APP_PICTURE) {
    139             return "Application Picture";
    140         }
    141         if (stageIndex == STAGE_APP_VIDEO) {
    142             return "Application Video";
    143         }
    144         if (stageIndex == STAGE_INTENT_PICTURE) {
    145             return "Intent Picture";
    146         }
    147         if (stageIndex == STAGE_INTENT_VIDEO) {
    148             return "Intent Video";
    149         }
    150 
    151         return "Unknown!!!";
    152     }
    153 
    154     private String getStageIntentString(int stageIndex)
    155     {
    156         if (stageIndex == STAGE_APP_PICTURE) {
    157             return android.hardware.Camera.ACTION_NEW_PICTURE;
    158         }
    159         if (stageIndex == STAGE_APP_VIDEO) {
    160             return android.hardware.Camera.ACTION_NEW_VIDEO;
    161         }
    162         if (stageIndex == STAGE_INTENT_PICTURE) {
    163             return android.hardware.Camera.ACTION_NEW_PICTURE;
    164         }
    165         if (stageIndex == STAGE_INTENT_VIDEO) {
    166             return android.hardware.Camera.ACTION_NEW_VIDEO;
    167         }
    168 
    169         return "Unknown Intent!!!";
    170     }
    171 
    172     private String getStageInstructionLabel(int stageIndex)
    173     {
    174         if (stageIndex == STAGE_APP_PICTURE) {
    175             return getString(R.string.ci_instruction_text_app_picture_label);
    176         }
    177         if (stageIndex == STAGE_APP_VIDEO) {
    178             return getString(R.string.ci_instruction_text_app_video_label);
    179         }
    180         if (stageIndex == STAGE_INTENT_PICTURE) {
    181             return getString(R.string.ci_instruction_text_intent_picture_label);
    182         }
    183         if (stageIndex == STAGE_INTENT_VIDEO) {
    184             return getString(R.string.ci_instruction_text_intent_video_label);
    185         }
    186 
    187         return "Unknown Instruction Label!!!";
    188     }
    189 
    190     @Override
    191     public void onCreate(Bundle savedInstanceState) {
    192         super.onCreate(savedInstanceState);
    193 
    194         setContentView(R.layout.ci_main);
    195         setPassFailButtonClickListeners();
    196         setInfoResources(R.string.camera_intents, R.string.ci_info, -1);
    197 
    198         mPassButton         = (ImageButton) findViewById(R.id.pass_button);
    199         mFailButton         = (ImageButton) findViewById(R.id.fail_button);
    200         mStartTestButton  = (Button) findViewById(R.id.start_test_button);
    201         mStartTestButton.setOnClickListener(this);
    202 
    203         // This activity is reused multiple times
    204         // to test each camera/intents combination
    205         final int stageIndex = getIntent().getIntExtra(STAGE_INDEX_EXTRA, 0);
    206 
    207         // Hitting the pass button goes to the next test activity.
    208         // Only the last one uses the PassFailButtons click callback function,
    209         // which gracefully terminates the activity.
    210         if (stageIndex + 1 < NUM_STAGES) {
    211             setPassButtonGoesToNextStage(stageIndex);
    212         }
    213         resetButtons();
    214 
    215         // Set initial values
    216 
    217         TextView intentsLabel =
    218                 (TextView) findViewById(R.id.intents_text);
    219         intentsLabel.setText(
    220                 getString(R.string.ci_intents_label)
    221                 + " "
    222                 + Integer.toString(getStageIndex()+1)
    223                 + " of "
    224                 + Integer.toString(NUM_STAGES)
    225                 + ": "
    226                 + getStageIntentString(getStageIndex())
    227                 );
    228 
    229         TextView instructionLabel =
    230                 (TextView) findViewById(R.id.instruction_text);
    231         instructionLabel.setText(R.string.ci_instruction_text_photo_label);
    232 
    233         /* Display the instructions to launch camera app and take a photo */
    234         TextView cameraExtraLabel =
    235                 (TextView) findViewById(R.id.instruction_extra_text);
    236         cameraExtraLabel.setText(getStageInstructionLabel(getStageIndex()));
    237 
    238         mStartTestButton.setEnabled(true);
    239     }
    240 
    241     @Override
    242     public void onDestroy() {
    243         super.onDestroy();
    244         Log.v(TAG, "onDestroy");
    245     }
    246 
    247     @Override
    248     public void onResume() {
    249         super.onResume();
    250     }
    251 
    252     @Override
    253     public void onPause() {
    254         super.onPause();
    255         /*
    256         When testing INTENT_PICTURE, INTENT_VIDEO,
    257         do not allow user to cheat by going to camera app and re-firing
    258         the intents by taking a photo/video
    259         */
    260         if (getStageIndex() == STAGE_INTENT_PICTURE ||
    261             getStageIndex() == STAGE_INTENT_VIDEO) {
    262 
    263             if (mActivityResult && mState == STATE_STARTED) {
    264                 mDetectCheating = true;
    265                 Log.w(TAG, "Potential cheating detected");
    266             }
    267         }
    268 
    269     }
    270 
    271     @Override
    272     protected void onActivityResult(
    273         int requestCode, int resultCode, Intent data) {
    274         if (requestCode == 1337 + getStageIndex()) {
    275             Log.v(TAG, "Activity we launched was finished");
    276             mActivityResult = true;
    277 
    278             if (mState != STATE_FAILED
    279                 && getStageIndex() == STAGE_INTENT_PICTURE) {
    280                 mPassButton.setEnabled(true);
    281                 mFailButton.setEnabled(false);
    282 
    283                 mState = STATE_SUCCESSFUL;
    284                 /* successful, unless we get the URI trigger back
    285                  at some point later on */
    286             }
    287         }
    288     }
    289 
    290     @Override
    291     public String getTestDetails() {
    292         return mReportBuilder.toString();
    293     }
    294 
    295     private class WaitForTriggerTask extends AsyncTask<Void, Void, Boolean> {
    296         protected Boolean doInBackground(Void... param) {
    297             try {
    298                 boolean executed = mTestEnv.awaitExecution();
    299                 // Check latest test param
    300                 if (executed && mState == STATE_STARTED) {
    301 
    302                     // this can happen if..
    303                     //  the camera apps intent finishes,
    304                     //  user returns to cts verifier,
    305                     //  user leaves cts verifier and tries to fake receiver intents
    306                     if (mDetectCheating) {
    307                         Log.w(TAG, "Cheating attempt suppressed");
    308                         mState = STATE_FAILED;
    309                     }
    310 
    311                     // For STAGE_INTENT_PICTURE test, if EXTRA_OUTPUT is not assigned in intent,
    312                     // file should NOT be saved so triggering this is a test failure.
    313                     if (getStageIndex() == STAGE_INTENT_PICTURE) {
    314                         Log.e(TAG, "FAIL: STAGE_INTENT_PICTURE test should not create file");
    315                         mState = STATE_FAILED;
    316                     }
    317 
    318                     if (mState != STATE_FAILED) {
    319                         mState = STATE_SUCCESSFUL;
    320                         return true;
    321                     } else {
    322                         return false;
    323                     }
    324                 }
    325             } catch (InterruptedException e) {
    326                 e.printStackTrace();
    327             }
    328 
    329             if (getStageIndex() == STAGE_INTENT_PICTURE) {
    330                 // STAGE_INTENT_PICTURE should timeout
    331                 return true;
    332             } else {
    333                 Log.e(TAG, "FAIL: timeout waiting for URI trigger");
    334                 return false;
    335             }
    336         }
    337 
    338         protected void onPostExecute(Boolean pass) {
    339             if (pass) {
    340                 mPassButton.setEnabled(true);
    341                 mFailButton.setEnabled(false);
    342             } else {
    343                 mPassButton.setEnabled(false);
    344                 mFailButton.setEnabled(true);
    345             }
    346         }
    347     }
    348 
    349     @Override
    350     public void onClick(View view) {
    351         Log.v(TAG, "Click detected");
    352 
    353         final int stageIndex = getStageIndex();
    354 
    355         if (view == mStartTestButton) {
    356             Log.v(TAG, "Starting testing... ");
    357 
    358 
    359             mState = STATE_STARTED;
    360 
    361             JobScheduler jobScheduler = (JobScheduler) getSystemService(
    362                     Context.JOB_SCHEDULER_SERVICE);
    363             jobScheduler.cancelAll();
    364 
    365             mTestEnv = CameraContentJobService.TestEnvironment.getTestEnvironment();
    366 
    367             mTestEnv.setUp();
    368 
    369             JobInfo job = makeJobInfo(TEST_JOB_TYPES[stageIndex]);
    370             jobScheduler.schedule(job);
    371 
    372             new WaitForTriggerTask().execute();
    373 
    374             /* we can allow user to fail immediately */
    375             mFailButton.setEnabled(true);
    376 
    377             /* trigger an ACTION_IMAGE_CAPTURE intent
    378                 which will run the camera app itself */
    379             String intentStr = null;
    380             Intent cameraIntent = null;
    381             if (stageIndex == STAGE_INTENT_PICTURE) {
    382                 intentStr = android.provider.MediaStore.ACTION_IMAGE_CAPTURE;
    383             }
    384             else if (stageIndex == STAGE_INTENT_VIDEO) {
    385                 intentStr = android.provider.MediaStore.ACTION_VIDEO_CAPTURE;
    386             }
    387 
    388             if (intentStr != null) {
    389                 cameraIntent = new Intent(intentStr);
    390                 startActivityForResult(cameraIntent, 1337 + getStageIndex());
    391             }
    392 
    393             mStartTestButton.setEnabled(false);
    394         }
    395 
    396         if(view == mPassButton || view == mFailButton) {
    397             // Stop any running wait
    398             mTestEnv.cancelWait();
    399 
    400             for (int counter = 0; counter < NUM_STAGES; counter++) {
    401                 String combination = getStageString(counter) + "\n";
    402 
    403                 if(counter < stageIndex) {
    404                     // test already passed, or else wouldn't have made
    405                     // it to current stageIndex
    406                     mTestedCombinations.add(combination);
    407                 }
    408 
    409                 if(counter == stageIndex) {
    410                     // current test configuration
    411                     if(view == mPassButton) {
    412                         mTestedCombinations.add(combination);
    413                     }
    414                     else if(view == mFailButton) {
    415                         mUntestedCombinations.add(combination);
    416                     }
    417                 }
    418 
    419                 if(counter > stageIndex) {
    420                     // test not passed yet, since haven't made it to
    421                     // stageIndex
    422                     mUntestedCombinations.add(combination);
    423                 }
    424 
    425                 counter++;
    426             }
    427 
    428             mReportBuilder = new StringBuilder();
    429             mReportBuilder.append("Passed combinations:\n");
    430             for (String combination : mTestedCombinations) {
    431                 mReportBuilder.append(combination);
    432             }
    433             mReportBuilder.append("Failed/untested combinations:\n");
    434             for (String combination : mUntestedCombinations) {
    435                 mReportBuilder.append(combination);
    436             }
    437 
    438             if(view == mPassButton) {
    439                 TestResult.setPassedResult(this, "CameraIntentsActivity",
    440                         getTestDetails());
    441             }
    442             if(view == mFailButton) {
    443                 TestResult.setFailedResult(this, "CameraIntentsActivity",
    444                         getTestDetails());
    445             }
    446 
    447             // restart activity to test next intents
    448             Intent intent = new Intent(CameraIntentsActivity.this,
    449                     CameraIntentsActivity.class);
    450             intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
    451                     | Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    452             intent.putExtra(STAGE_INDEX_EXTRA, stageIndex + 1);
    453             startActivity(intent);
    454         }
    455     }
    456 
    457     private void resetButtons() {
    458         enablePassFailButtons(false);
    459     }
    460 
    461     private void enablePassFailButtons(boolean enable) {
    462         mPassButton.setEnabled(enable);
    463         mFailButton.setEnabled(enable);
    464     }
    465 
    466     @Override
    467     public void surfaceChanged(SurfaceHolder holder, int format, int width,
    468             int height) {
    469     }
    470 
    471     @Override
    472     public void surfaceCreated(SurfaceHolder holder) {
    473         // Auto-generated method stub
    474     }
    475 
    476     @Override
    477     public void surfaceDestroyed(SurfaceHolder holder) {
    478         // Auto-generated method stub
    479     }
    480 
    481     private void setPassButtonGoesToNextStage(final int stageIndex) {
    482         findViewById(R.id.pass_button).setOnClickListener(this);
    483     }
    484 
    485 }
    486