1 /* 2 * Copyright (C) 2023 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.development; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.content.DialogInterface; 22 import android.net.http.HttpEngine; 23 import android.net.http.HttpException; 24 import android.net.http.UploadDataProvider; 25 import android.net.http.UploadDataSink; 26 import android.net.http.UrlRequest; 27 import android.net.http.UrlResponseInfo; 28 import android.os.Bundle; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.widget.EditText; 33 import android.widget.TextView; 34 35 import java.io.ByteArrayOutputStream; 36 import java.io.IOException; 37 import java.nio.ByteBuffer; 38 import java.nio.channels.Channels; 39 import java.nio.channels.WritableByteChannel; 40 import java.util.concurrent.Executor; 41 import java.util.concurrent.Executors; 42 43 /** 44 * Activity for managing HttpEngine interactions. 45 */ 46 public class HttpEngineActivity extends Activity { 47 private static final String TAG = HttpEngineActivity.class.getSimpleName(); 48 49 private HttpEngine mHttpEngine; 50 51 private String mUrl; 52 private TextView mResultText; 53 private TextView mReceiveDataText; 54 55 class SimpleUrlRequestCallback implements UrlRequest.Callback { 56 private ByteArrayOutputStream mBytesReceived = new ByteArrayOutputStream(); 57 private WritableByteChannel mReceiveChannel = Channels.newChannel(mBytesReceived); 58 59 @Override onRedirectReceived( UrlRequest request, UrlResponseInfo info, String newLocationUrl)60 public void onRedirectReceived( 61 UrlRequest request, UrlResponseInfo info, String newLocationUrl) { 62 Log.i(TAG, "****** onRedirectReceived ******"); 63 request.followRedirect(); 64 } 65 66 @Override onResponseStarted(UrlRequest request, UrlResponseInfo info)67 public void onResponseStarted(UrlRequest request, UrlResponseInfo info) { 68 Log.i(TAG, "****** Response Started ******"); 69 Log.i(TAG, "*** Headers Are *** " + info.getHeaders()); 70 71 request.read(ByteBuffer.allocateDirect(32 * 1024)); 72 } 73 74 @Override onReadCompleted( UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer)75 public void onReadCompleted( 76 UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) { 77 byteBuffer.flip(); 78 Log.i(TAG, "****** onReadCompleted ******" + byteBuffer); 79 80 try { 81 mReceiveChannel.write(byteBuffer); 82 } catch (IOException e) { 83 Log.i(TAG, "IOException during ByteBuffer read. Details: ", e); 84 } 85 byteBuffer.clear(); 86 request.read(byteBuffer); 87 } 88 89 @Override onSucceeded(UrlRequest request, UrlResponseInfo info)90 public void onSucceeded(UrlRequest request, UrlResponseInfo info) { 91 Log.i(TAG, "****** Request Completed, status code is " + info.getHttpStatusCode() 92 + ", total received bytes is " + info.getReceivedByteCount()); 93 94 final String receivedData = mBytesReceived.toString(); 95 final String url = info.getUrl(); 96 final String text = "Completed " + url + " (" + info.getHttpStatusCode() + ")"; 97 HttpEngineActivity.this.runOnUiThread(new Runnable() { 98 @Override 99 public void run() { 100 mResultText.setText(text); 101 mReceiveDataText.setText(receivedData); 102 promptForURL(url); 103 } 104 }); 105 } 106 107 @Override onFailed(UrlRequest request, UrlResponseInfo info, HttpException error)108 public void onFailed(UrlRequest request, UrlResponseInfo info, HttpException error) { 109 Log.i(TAG, "****** onFailed, error is: " + error.getMessage()); 110 111 final String url = mUrl; 112 final String text = "Failed " + mUrl + " (" + error.getMessage() + ")"; 113 HttpEngineActivity.this.runOnUiThread(new Runnable() { 114 @Override 115 public void run() { 116 mResultText.setText(text); 117 promptForURL(url); 118 } 119 }); 120 } 121 122 @Override onCanceled(UrlRequest request, UrlResponseInfo info)123 public void onCanceled(UrlRequest request, UrlResponseInfo info) { 124 Log.i(TAG, "****** onCanceled ******"); 125 } 126 } 127 128 @Override onCreate(final Bundle savedInstanceState)129 protected void onCreate(final Bundle savedInstanceState) { 130 super.onCreate(savedInstanceState); 131 setContentView(R.layout.http_engine_activity); 132 mResultText = (TextView) findViewById(R.id.resultView); 133 mReceiveDataText = (TextView) findViewById(R.id.dataView); 134 mReceiveDataText.setOnClickListener(new View.OnClickListener() { 135 @Override 136 public void onClick(View v) { 137 promptForURL(mUrl); 138 } 139 }); 140 141 HttpEngine.Builder myBuilder = new HttpEngine.Builder(this); 142 myBuilder.setEnableHttpCache(HttpEngine.Builder.HTTP_CACHE_IN_MEMORY, 100 * 1024) 143 .setEnableHttp2(true) 144 .setEnableQuic(true); 145 146 mHttpEngine = myBuilder.build(); 147 148 String appUrl = (getIntent() != null ? getIntent().getDataString() : null); 149 if (appUrl == null) { 150 promptForURL("https://"); 151 } else { 152 startWithURL(appUrl); 153 } 154 } 155 promptForURL(String url)156 private void promptForURL(String url) { 157 Log.i(TAG, "No URL provided via intent, prompting user..."); 158 AlertDialog.Builder alert = new AlertDialog.Builder(this); 159 alert.setTitle("Enter a URL"); 160 LayoutInflater inflater = getLayoutInflater(); 161 View alertView = inflater.inflate(R.layout.http_engine_dialog, null); 162 final EditText urlInput = (EditText) alertView.findViewById(R.id.urlText); 163 urlInput.setText(url); 164 final EditText postInput = (EditText) alertView.findViewById(R.id.postText); 165 alert.setView(alertView); 166 167 alert.setPositiveButton("Load", new DialogInterface.OnClickListener() { 168 @Override 169 public void onClick(DialogInterface dialog, int button) { 170 String url = urlInput.getText().toString(); 171 String postData = postInput.getText().toString(); 172 startWithURL(url, postData); 173 } 174 }); 175 alert.show(); 176 } 177 applyPostDataToUrlRequestBuilder( UrlRequest.Builder builder, Executor executor, String postData)178 private void applyPostDataToUrlRequestBuilder( 179 UrlRequest.Builder builder, Executor executor, String postData) { 180 if (postData != null && postData.length() > 0) { 181 builder.setHttpMethod("POST"); 182 builder.addHeader("Content-Type", "application/x-www-form-urlencoded"); 183 // TODO: make android.net.http.apihelpers.UploadDataProviders accessible. 184 builder.setUploadDataProvider(new UploadDataProvider() { 185 @Override 186 public long getLength() { 187 return postData.length(); 188 } 189 190 @Override 191 public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) { 192 byteBuffer.put(postData.getBytes()); 193 uploadDataSink.onReadSucceeded(/*finalChunk*/ false); 194 } 195 196 @Override 197 public void rewind(UploadDataSink uploadDataSink) { 198 // noop 199 uploadDataSink.onRewindSucceeded(); 200 } 201 }, executor); 202 } 203 } 204 startWithURL(String url)205 private void startWithURL(String url) { 206 startWithURL(url, null); 207 } 208 startWithURL(String url, String postData)209 private void startWithURL(String url, String postData) { 210 Log.i(TAG, "UrlRequest started: " + url); 211 mUrl = url; 212 213 Executor executor = Executors.newSingleThreadExecutor(); 214 UrlRequest.Callback callback = new SimpleUrlRequestCallback(); 215 UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(url, executor, callback); 216 applyPostDataToUrlRequestBuilder(builder, executor, postData); 217 builder.build().start(); 218 } 219 } 220