1 /* 2 * Copyright (C) 2011 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 17 package com.android.cts.verifier.streamquality; 18 19 import com.android.cts.verifier.ArrayTestListAdapter; 20 import com.android.cts.verifier.PassFailButtons; 21 import com.android.cts.verifier.R; 22 import com.android.cts.verifier.TestListAdapter; 23 import com.android.cts.verifier.TestListAdapter.TestListItem; 24 25 import android.app.AlertDialog; 26 import android.app.Dialog; 27 import android.content.DialogInterface; 28 import android.content.DialogInterface.OnClickListener; 29 import android.content.Intent; 30 import android.database.DataSetObserver; 31 import android.os.AsyncTask; 32 import android.os.Bundle; 33 import android.util.Log; 34 import android.widget.TextView; 35 36 import java.io.BufferedReader; 37 import java.io.InputStream; 38 import java.io.InputStreamReader; 39 import java.io.IOException; 40 import java.io.Serializable; 41 import java.net.HttpURLConnection; 42 import java.net.URL; 43 44 /** 45 * Tests for verifying the quality of streaming videos. Plays streams of different formats over 46 * different protocols for a short amount of time, after which users can mark Pass/Fail depending 47 * on the smoothness and subjective quality of the video. 48 */ 49 public class StreamingVideoActivity extends PassFailButtons.TestListActivity { 50 /** 51 * Simple storage class for stream information. 52 */ 53 static class Stream implements Serializable { 54 /** 55 * Human-readable name for the stream. 56 */ 57 public final String name; 58 59 /** 60 * Code name to append to the class name to identify this test. 61 */ 62 public final String code; 63 64 /** 65 * URI of the stream 66 */ 67 public final String uri; 68 Stream(String name, String code, String uri)69 public Stream(String name, String code, String uri) { 70 this.name = name; 71 this.code = code; 72 this.uri = uri; 73 } 74 75 @Override equals(Object o)76 public boolean equals(Object o) { 77 if (this == o) { 78 return true; 79 } else if (o == null || !(o instanceof Stream)) { 80 return false; 81 } else { 82 Stream stream = (Stream) o; 83 return name.equals(stream.name) 84 && code.equals(stream.code) 85 && uri.equals(stream.uri); 86 } 87 } 88 89 @Override hashCode()90 public int hashCode() { 91 return name.hashCode() ^ uri.hashCode() ^ code.hashCode(); 92 } 93 94 @Override toString()95 public String toString() { 96 return name; 97 } 98 } 99 100 private static final String TAG = StreamingVideoActivity.class.getName(); 101 private static final int RTSP_URL_ERROR = 1; 102 private static final String ITAG_13_SIGNATURE = 103 "53A3A3A46DAB71E3C599DE8FA6A1484593DFACE3" + 104 ".9476B91AD5035D88C3895CC2A4703B6DD442E972"; 105 private static final String ITAG_17_SIGNATURE = 106 "10D6D263112C41DA98822D74821DF47340F1A361" + 107 ".803649A2258E26BF40E76A95E646FBAE4009CEE8"; 108 private static final String ITAG_18_SIGNATURE = 109 "618FBB112E1B2FBB66DA9F203AE8CC7DF93C7400" + 110 ".20498AA006E999F42BE69D66E3596F2C7CA18114"; 111 private static final String RTSP_LOOKUP_URI_TEMPLATE = 112 "http://redirector.gvt1.com/videoplayback?id=271de9756065677e" + 113 "&source=youtube&protocol=rtsp&sparams=ip,ipbits,expire,id,itag,source" + 114 "&ip=0.0.0.0&ipbits=0&expire=19000000000&key=ik0&alr=yes" + 115 "&itag=%d" + 116 "&signature=%s"; 117 118 private static final Stream[] HTTP_STREAMS = { 119 new Stream("H263 Video, AMR Audio", "http_h263_amr", 120 "http://redirector.gvt1.com/" 121 + "videoplayback?id=271de9756065677e" 122 + "&itag=13&ip=0.0.0.0&ipbits=0&expire=19000000000" 123 + "&sparams=ip,ipbits,expire,id,itag" 124 + "&signature=073A731E2BDF1E05206AC7B9B895C922ABCBA01D" 125 + ".1DDA3F999541D2136E6755F16FC44CA972767169" 126 + "&source=youtube" 127 + "&key=ik0&user=android-device-test"), 128 new Stream("MPEG4 SP Video, AAC Audio", "http_mpeg4_aac", 129 "http://redirector.gvt1.com/" 130 + "videoplayback?id=271de9756065677e" 131 + "&itag=17&ip=0.0.0.0&ipbits=0&expire=19000000000" 132 + "&sparams=ip,ipbits,expire,id,itag" 133 + "&signature=6B0F8B8A6A7FD9E4CDF123349C2E061ED2020D74" 134 + ".3460FC81D6C8894BA2D241597D2E1D059845F5F0" 135 + "&source=youtube" 136 + "&key=ik0&user=android-device-test"), 137 new Stream("H264 Base Video, AAC Audio", "http_h264_aac", 138 "http://redirector.gvt1.com/" 139 + "videoplayback?id=271de9756065677e" 140 + "&itag=18&ip=0.0.0.0&ipbits=0&expire=19000000000" 141 + "&sparams=ip,ipbits,expire,id,itag" 142 + "&signature=75627CD4CEA73D7868CBDE3CE5C4011955164107" 143 + ".1DCFB0EF1372B48DDCFBE69645FE137AC02AF561" 144 + "&source=youtube" 145 + "&key=ik0&user=android-device-test"), 146 }; 147 148 @Override onCreate(Bundle savedInstanceState)149 protected void onCreate(Bundle savedInstanceState) { 150 super.onCreate(savedInstanceState); 151 setContentView(R.layout.pass_fail_list); 152 setPassFailButtonClickListeners(); 153 setInfoResources(R.string.streaming_video, R.string.streaming_video_info, -1); 154 155 TextView empty = (TextView) findViewById(android.R.id.empty); 156 empty.setText(R.string.sv_no_data); 157 158 getPassButton().setEnabled(false); 159 setTestListAdapter(getStreamAdapter()); 160 } 161 162 @Override onCreateDialog(int id, Bundle args)163 public Dialog onCreateDialog(int id, Bundle args) { 164 switch (id) { 165 case RTSP_URL_ERROR: 166 return new AlertDialog.Builder(this) 167 .setTitle(getString(R.string.sv_failed_title)) 168 .setMessage(getString(R.string.sv_failed_message)) 169 .setNegativeButton("Close", new OnClickListener() { 170 @Override 171 public void onClick(DialogInterface dialog, int which) { 172 setTestResultAndFinish(false); 173 } 174 }).show(); 175 default: 176 return super.onCreateDialog(id, args); 177 } 178 } 179 180 private TestListAdapter getStreamAdapter() { 181 ArrayTestListAdapter adapter = new ArrayTestListAdapter(this); 182 183 adapter.add(TestListItem.newCategory("RTSP")); 184 addRtspStreamToTest( 185 adapter, "H263 Video, AMR Audio", "rtsp_h263_amr", 13, ITAG_13_SIGNATURE); 186 addRtspStreamToTest( 187 adapter, "MPEG4 SP Video, AAC Audio", "rtsp_mpeg4_aac", 17, ITAG_17_SIGNATURE); 188 addRtspStreamToTest( 189 adapter, "H264 Base Video, AAC Audio", "rtsp_h264_aac", 18, ITAG_18_SIGNATURE); 190 191 adapter.add(TestListItem.newCategory("HTTP Progressive")); 192 for (Stream stream : HTTP_STREAMS) { 193 addStreamToTests(adapter, stream); 194 } 195 196 adapter.registerDataSetObserver(new DataSetObserver() { 197 @Override 198 public void onChanged() { 199 updatePassButton(); 200 } 201 }); 202 203 return adapter; 204 } 205 206 private void addRtspStreamToTest( 207 ArrayTestListAdapter adapter, String name, String code, int itag, String signature) { 208 String rtspUrl = lookupRtspUrl(itag, signature); 209 if (rtspUrl == null) { 210 showDialog(RTSP_URL_ERROR); 211 } 212 Stream stream = new Stream(name, code, rtspUrl); 213 addStreamToTests(adapter, stream); 214 } 215 216 private void addStreamToTests(ArrayTestListAdapter streams, Stream stream) { 217 Intent i = new Intent(StreamingVideoActivity.this, PlayVideoActivity.class); 218 i.putExtra(PlayVideoActivity.EXTRA_STREAM, stream); 219 streams.add(TestListItem.newTest(stream.name, PlayVideoActivity.getTestId(stream.code), 220 i, null)); 221 } 222 223 /** @returns the appropriate RTSP url, or null in case of failure */ 224 private String lookupRtspUrl(int itag, String signature) { 225 String rtspLookupUri = String.format(RTSP_LOOKUP_URI_TEMPLATE, itag, signature); 226 try { 227 return new LookupRtspUrlTask().execute(rtspLookupUri).get(); 228 } catch (Exception e) { 229 Log.e(TAG, "RTSP URL lookup time out.", e); 230 showDialog(RTSP_URL_ERROR); 231 return null; 232 } 233 } 234 235 /** Retrieve the URL for an RTSP stream */ 236 private class LookupRtspUrlTask extends AsyncTask<String, Void, String> { 237 protected String doInBackground(String... rtspLookupUri) { 238 HttpURLConnection urlConnection = null; 239 try { 240 URL url = new URL(rtspLookupUri[0]); 241 urlConnection = (HttpURLConnection) url.openConnection(); 242 if (urlConnection.getResponseCode() != 200) { 243 throw new IOException("unable to get rtsp uri. Response Code:" 244 + urlConnection.getResponseCode()); 245 } 246 BufferedReader reader = new BufferedReader( 247 new InputStreamReader(urlConnection.getInputStream())); 248 return reader.readLine(); 249 } catch (Exception e) { 250 Log.e(TAG, "RTSP URL lookup failed.", e); 251 return null; 252 } finally { 253 if (urlConnection != null) { 254 urlConnection.disconnect(); 255 } 256 } 257 } 258 } 259 } 260