1 // Copyright 2023 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.cronet_sample_apk; 6 7 import android.os.Bundle; 8 import android.os.Handler; 9 import android.os.Looper; 10 import android.view.LayoutInflater; 11 import android.view.View; 12 import android.view.ViewGroup; 13 import android.widget.Button; 14 import android.widget.EditText; 15 import android.widget.TextView; 16 17 import androidx.annotation.NonNull; 18 import androidx.annotation.Nullable; 19 import androidx.fragment.app.Fragment; 20 import androidx.fragment.app.FragmentActivity; 21 import androidx.lifecycle.ViewModelProvider; 22 23 import org.chromium.base.Log; 24 import org.chromium.net.CronetEngine; 25 import org.chromium.net.CronetException; 26 import org.chromium.net.UrlRequest; 27 import org.chromium.net.UrlResponseInfo; 28 29 import java.io.ByteArrayOutputStream; 30 import java.io.IOException; 31 import java.nio.ByteBuffer; 32 import java.nio.channels.Channels; 33 import java.nio.channels.WritableByteChannel; 34 import java.util.concurrent.Executor; 35 import java.util.concurrent.Executors; 36 37 public class MainFragment extends Fragment { 38 private static final String TAG = "CronetSampleApp"; 39 private EditText mUrlEditText; 40 private TextView mResultText; 41 private Button mStartButton; 42 private Button mResetEngineButton; 43 private Button mClearTextButton; 44 private SampleActivityViewModel mActivityViewModel; 45 getCronetEngine()46 private CronetEngine getCronetEngine() { 47 return ((CronetSampleApplication) requireActivity().getApplication()).getCronetEngine(); 48 } 49 resetEngine()50 private void resetEngine() { 51 ((CronetSampleApplication) requireActivity().getApplication()).restartCronetEngine(); 52 } 53 init(View view)54 private void init(View view) { 55 mUrlEditText = view.findViewById(R.id.url_edittext); 56 mResultText = view.findViewById(R.id.result_textview); 57 mStartButton = view.findViewById(R.id.start_button); 58 mResetEngineButton = view.findViewById(R.id.reset_button); 59 mClearTextButton = view.findViewById(R.id.clear_button); 60 mStartButton.setOnClickListener( 61 v -> { 62 Executor executor = Executors.newSingleThreadExecutor(); 63 UrlRequest.Callback callback = new SimpleUrlRequestCallback(); 64 UrlRequest.Builder builder = 65 getCronetEngine() 66 .newUrlRequestBuilder( 67 mUrlEditText.getText().toString(), callback, executor); 68 builder.build().start(); 69 }); 70 71 mResetEngineButton.setOnClickListener(v -> resetEngine()); 72 mClearTextButton.setOnClickListener(v -> mResultText.setText("")); 73 mActivityViewModel = 74 new ViewModelProvider((FragmentActivity) requireActivity()) 75 .get(SampleActivityViewModel.class); 76 } 77 78 @Nullable 79 @Override onCreateView( @onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)80 public View onCreateView( 81 @NonNull LayoutInflater inflater, 82 @Nullable ViewGroup container, 83 @Nullable Bundle savedInstanceState) { 84 View view = inflater.inflate(R.layout.main_fragment, container, false); 85 init(view); 86 return view; 87 } 88 89 class SimpleUrlRequestCallback extends UrlRequest.Callback { 90 private ByteArrayOutputStream mBytesReceived = new ByteArrayOutputStream(); 91 private WritableByteChannel mReceiveChannel = Channels.newChannel(mBytesReceived); 92 93 @Override onRedirectReceived( UrlRequest request, UrlResponseInfo info, String newLocationUrl)94 public void onRedirectReceived( 95 UrlRequest request, UrlResponseInfo info, String newLocationUrl) { 96 Log.i(TAG, "****** onRedirectReceived ******"); 97 request.followRedirect(); 98 } 99 100 @Override onResponseStarted(UrlRequest request, UrlResponseInfo info)101 public void onResponseStarted(UrlRequest request, UrlResponseInfo info) { 102 Log.i(TAG, "****** Response Started ******"); 103 Log.i(TAG, "*** Headers Are *** " + info.getAllHeaders()); 104 if (Options.isBooleanOptionOn(Options.OptionsIdentifier.SLOW_DOWNLOAD)) { 105 try { 106 Thread.sleep(10000); 107 } catch (InterruptedException e) { 108 Thread.currentThread().interrupt(); 109 throw new RuntimeException(e); 110 } 111 } 112 request.read(ByteBuffer.allocateDirect(32 * 1024)); 113 } 114 115 @Override onReadCompleted( UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer)116 public void onReadCompleted( 117 UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) { 118 byteBuffer.flip(); 119 Log.i(TAG, "****** onReadCompleted ******" + byteBuffer); 120 121 try { 122 mReceiveChannel.write(byteBuffer); 123 } catch (IOException e) { 124 Log.i(TAG, "IOException during ByteBuffer read. Details: ", e); 125 } 126 byteBuffer.clear(); 127 request.read(byteBuffer); 128 if (Options.isBooleanOptionOn(Options.OptionsIdentifier.SLOW_DOWNLOAD)) { 129 try { 130 Thread.sleep(10000); 131 } catch (InterruptedException e) { 132 Thread.currentThread().interrupt(); 133 throw new RuntimeException(e); 134 } 135 } 136 } 137 138 @Override onSucceeded(UrlRequest request, UrlResponseInfo info)139 public void onSucceeded(UrlRequest request, UrlResponseInfo info) { 140 Log.i( 141 TAG, 142 "****** Request Completed, status code is " 143 + info.getHttpStatusCode() 144 + ", total received bytes is " 145 + info.getReceivedByteCount()); 146 final String receivedData = mBytesReceived.toString(); 147 final String url = info.getUrl(); 148 final String text = "Completed " + url + " (" + info.getHttpStatusCode() + ")"; 149 new Handler(Looper.getMainLooper()) 150 .post(() -> mResultText.setText(String.format("%s\n%s", text, receivedData))); 151 } 152 153 @Override onFailed(UrlRequest request, UrlResponseInfo info, CronetException error)154 public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) { 155 Log.i(TAG, "****** onFailed, error is: " + error.getMessage()); 156 final String text = "Failed " + " (" + error.getMessage() + ")"; 157 new Handler(Looper.getMainLooper()) 158 .post(() -> mResultText.setText(String.format("%s", text))); 159 } 160 } 161 162 // Starts writing NetLog to disk. startNetLog() should be called afterwards. startNetLog()163 private void startNetLog() { 164 getCronetEngine() 165 .startNetLogToFile( 166 requireActivity().getCacheDir().getPath() + "/netlog.json", false); 167 } 168 169 // Stops writing NetLog to disk. Should be called after calling startNetLog(). 170 // NetLog can be downloaded afterwards via: 171 // adb root 172 // adb pull /data/data/org.chromium.cronet_sample_apk/cache/netlog.json 173 // netlog.json can then be viewed in a Chrome tab navigated to chrome://net-internals/#import stopNetLog()174 private void stopNetLog() { 175 getCronetEngine().stopNetLog(); 176 } 177 } 178