1 /* 2 * Copyright (C) 2022 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.example.odpclient; 18 19 import android.adservices.ondevicepersonalization.OnDevicePersonalizationManager; 20 import android.adservices.ondevicepersonalization.OnDevicePersonalizationManager.ExecuteResult; 21 import android.app.Activity; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.pm.PackageInfo; 25 import android.content.pm.PackageManager; 26 import android.content.res.Configuration; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.os.OutcomeReceiver; 31 import android.os.PersistableBundle; 32 import android.os.Process; 33 import android.os.StrictMode; 34 import android.os.Trace; 35 import android.text.method.ScrollingMovementMethod; 36 import android.util.Log; 37 import android.view.SurfaceControlViewHost.SurfacePackage; 38 import android.view.SurfaceHolder; 39 import android.view.SurfaceView; 40 import android.view.View; 41 import android.widget.Button; 42 import android.widget.EditText; 43 import android.widget.TextView; 44 import android.widget.ViewSwitcher; 45 46 import com.google.common.util.concurrent.Futures; 47 import com.google.common.util.concurrent.ListeningExecutorService; 48 import com.google.common.util.concurrent.MoreExecutors; 49 import com.google.common.util.concurrent.ThreadFactoryBuilder; 50 51 import java.io.PrintWriter; 52 import java.io.StringWriter; 53 import java.util.Optional; 54 import java.util.concurrent.CountDownLatch; 55 import java.util.concurrent.Executor; 56 import java.util.concurrent.Executors; 57 import java.util.concurrent.ThreadFactory; 58 import java.util.concurrent.atomic.AtomicReference; 59 60 public class MainActivity extends Activity { 61 private static final String TAG = "OdpClient"; 62 private static final String SERVICE_PACKAGE = "com.example.odpsamplenetwork"; 63 private static final String SERVICE_CLASS = "com.example.odpsamplenetwork.SampleService"; 64 private static final String ODP_APEX = "com.google.android.ondevicepersonalization"; 65 private static final String ADSERVICES_APEX = "com.google.android.adservices"; 66 private static final int SURFACE_VIEW_INDEX = 0; 67 private static final int MESSAGE_BOX_INDEX = 1; 68 private EditText mTextBox; 69 private Button mGetAdButton; 70 private EditText mScheduleTrainingTextBox; 71 private EditText mScheduleIntervalTextBox; 72 private Button mScheduleTrainingButton; 73 private Button mCancelTrainingButton; 74 private EditText mReportConversionTextBox; 75 private Button mReportConversionButton; 76 private SurfaceView mRenderedView; 77 private TextView mMessageBox; 78 private ViewSwitcher mViewSwitcher; 79 private Context mContext; 80 private static Executor sCallbackExecutor = Executors.newSingleThreadExecutor(); 81 82 private static final ListeningExecutorService sLightweightExecutor = 83 MoreExecutors.listeningDecorator( 84 Executors.newSingleThreadExecutor( 85 createThreadFactory( 86 "Lite Thread", 87 Process.THREAD_PRIORITY_DEFAULT, 88 Optional.of(getAsyncThreadPolicy())))); 89 createThreadFactory( final String name, final int priority, final Optional<StrictMode.ThreadPolicy> policy)90 private static ThreadFactory createThreadFactory( 91 final String name, final int priority, final Optional<StrictMode.ThreadPolicy> policy) { 92 return new ThreadFactoryBuilder() 93 .setDaemon(true) 94 .setNameFormat(name + " #%d") 95 .setThreadFactory( 96 new ThreadFactory() { 97 @Override 98 public Thread newThread(final Runnable runnable) { 99 return new Thread(new Runnable() { 100 @Override 101 public void run() { 102 if (policy.isPresent()) { 103 StrictMode.setThreadPolicy(policy.get()); 104 } 105 // Process class operates on the current thread. 106 Process.setThreadPriority(priority); 107 runnable.run(); 108 } 109 }); 110 } 111 }) 112 .build(); 113 } 114 115 private static StrictMode.ThreadPolicy getAsyncThreadPolicy() { 116 return new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build(); 117 } 118 119 class SurfaceCallback implements SurfaceHolder.Callback { 120 @Override public void surfaceCreated(SurfaceHolder holder) { 121 Log.d(TAG, "surfaceCreated"); 122 } 123 @Override public void surfaceDestroyed(SurfaceHolder holder) { 124 Log.d(TAG, "surfaceDestroyed"); 125 } 126 @Override public void surfaceChanged( 127 SurfaceHolder holder, int format, int width, int height) { 128 Log.d(TAG, "surfaceChanged"); 129 } 130 } 131 132 @Override 133 public void onCreate(Bundle savedInstanceState) { 134 Log.d(TAG, "onCreate"); 135 super.onCreate(savedInstanceState); 136 setContentView(R.layout.activity_main); 137 mContext = getApplicationContext(); 138 mRenderedView = findViewById(R.id.rendered_view); 139 mRenderedView.setVisibility(View.INVISIBLE); 140 mRenderedView.getHolder().addCallback(new SurfaceCallback()); 141 mGetAdButton = findViewById(R.id.get_ad_button); 142 mScheduleTrainingButton = findViewById(R.id.schedule_training_button); 143 mCancelTrainingButton = findViewById(R.id.cancel_training_button); 144 mReportConversionButton = findViewById(R.id.report_conversion_button); 145 mTextBox = findViewById(R.id.text_box); 146 mScheduleTrainingTextBox = findViewById(R.id.schedule_training_text_box); 147 mScheduleIntervalTextBox = findViewById(R.id.schedule_interval_text_box); 148 mReportConversionTextBox = findViewById(R.id.report_conversion_text_box); 149 mMessageBox = findViewById(R.id.message_box); 150 mMessageBox.setMovementMethod(new ScrollingMovementMethod()); 151 mViewSwitcher = findViewById(R.id.view_switcher); 152 registerGetAdButton(); 153 registerScheduleTrainingButton(); 154 registerReportConversionButton(); 155 registerCancelTrainingButton(); 156 157 Object unusedFuture = Futures.submit( 158 () -> printDebuggingInfo(), 159 sCallbackExecutor); 160 } 161 162 private void registerGetAdButton() { 163 mGetAdButton.setOnClickListener( 164 v -> { 165 var unused = sLightweightExecutor.submit(() -> makeRequest()); 166 }); 167 } 168 169 private void registerReportConversionButton() { 170 mReportConversionButton.setOnClickListener( 171 v -> { 172 var unused = sLightweightExecutor.submit(() -> reportConversion()); 173 }); 174 } 175 176 private OnDevicePersonalizationManager getOdpManager() throws NoClassDefFoundError { 177 return mContext.getSystemService(OnDevicePersonalizationManager.class); 178 } 179 180 private void makeRequest() { 181 try { 182 var odpManager = getOdpManager(); 183 CountDownLatch latch = new CountDownLatch(1); 184 Log.i(TAG, "Starting execute() " + getResources().getString(R.string.get_ad) 185 + " with " + mTextBox.getHint().toString() + ": " 186 + mTextBox.getText().toString()); 187 AtomicReference<ExecuteResult> executeResult = new AtomicReference<>(); 188 PersistableBundle appParams = new PersistableBundle(); 189 appParams.putString("keyword", mTextBox.getText().toString()); 190 191 Trace.beginAsyncSection("OdpClient:makeRequest:odpManager.execute", 0); 192 odpManager.execute( 193 ComponentName.createRelative( 194 SERVICE_PACKAGE, 195 SERVICE_CLASS), 196 appParams, 197 sCallbackExecutor, 198 new OutcomeReceiver<ExecuteResult, Exception>() { 199 @Override 200 public void onResult(ExecuteResult result) { 201 Trace.endAsyncSection("OdpClient:makeRequest:odpManager.execute", 0); 202 Log.i(TAG, "execute() success: " + result); 203 if (result != null) { 204 executeResult.set(result); 205 } else { 206 Log.e(TAG, "No results!"); 207 } 208 clearText(); 209 latch.countDown(); 210 } 211 212 @Override 213 public void onError(Exception e) { 214 Trace.endAsyncSection("OdpClient:makeRequest:odpManager.execute", 0); 215 showError("OdpClient:makeRequest:odpManager.execute", e); 216 latch.countDown(); 217 } 218 }); 219 latch.await(); 220 Log.d(TAG, "makeRequest:odpManager.execute wait success"); 221 222 if (executeResult.get() == null 223 || executeResult.get().getSurfacePackageToken() == null) { 224 Log.i(TAG, "No surfacePackageToken returned, skipping render."); 225 return; 226 } 227 228 Trace.beginAsyncSection("OdpClient:makeRequest:odpManager.requestSurfacePackage", 0); 229 odpManager.requestSurfacePackage( 230 executeResult.get().getSurfacePackageToken(), 231 mRenderedView.getHostToken(), 232 getDisplay().getDisplayId(), 233 mRenderedView.getWidth(), 234 mRenderedView.getHeight(), 235 sCallbackExecutor, 236 new OutcomeReceiver<SurfacePackage, Exception>() { 237 @Override 238 public void onResult(SurfacePackage surfacePackage) { 239 Trace.endAsyncSection( 240 "OdpClient:makeRequest:odpManager.requestSurfacePackage", 0); 241 Log.i(TAG, 242 "requestSurfacePackage() success: " 243 + surfacePackage.toString()); 244 clearText(); 245 new Handler(Looper.getMainLooper()).post(() -> { 246 if (surfacePackage != null) { 247 mRenderedView.setChildSurfacePackage( 248 surfacePackage); 249 } 250 mRenderedView.setZOrderOnTop(true); 251 mRenderedView.setVisibility(View.VISIBLE); 252 mViewSwitcher.setDisplayedChild(SURFACE_VIEW_INDEX); 253 }); 254 } 255 256 @Override 257 public void onError(Exception e) { 258 Trace.endAsyncSection( 259 "OdpClient:makeRequest:odpManager.requestSurfacePackage", 0); 260 showError( 261 "OdpClient:makeRequest:odpManager.requestSurfacePackage", e); 262 } 263 }); 264 } catch (Throwable e) { 265 showError("makeRequest", e); 266 } 267 } 268 269 private void registerScheduleTrainingButton() { 270 mScheduleTrainingButton.setOnClickListener( 271 v -> { 272 var unused = sLightweightExecutor.submit(() -> scheduleTraining()); 273 }); 274 } 275 276 private void scheduleTraining() { 277 try { 278 var odpManager = getOdpManager(); 279 CountDownLatch latch = new CountDownLatch(1); 280 Log.i( 281 TAG, 282 "Starting execute() " 283 + getResources().getString(R.string.schedule_training) 284 + " with " 285 + mScheduleTrainingTextBox.getHint().toString() 286 + ": " 287 + mScheduleTrainingTextBox.getText().toString()); 288 PersistableBundle appParams = new PersistableBundle(); 289 appParams.putString("schedule_training", mScheduleTrainingTextBox.getText().toString()); 290 if (mScheduleIntervalTextBox.getText() != null 291 && mScheduleIntervalTextBox.getText().toString() != null 292 && !mScheduleIntervalTextBox.getText().toString().isBlank()) { 293 appParams.putLong( 294 "schedule_interval", 295 Long.parseUnsignedLong(mScheduleIntervalTextBox.getText().toString())); 296 } 297 298 Trace.beginAsyncSection("OdpClient:scheduleTraining:odpManager.execute", 0); 299 odpManager.execute( 300 ComponentName.createRelative( 301 SERVICE_PACKAGE, 302 SERVICE_CLASS), 303 appParams, 304 sCallbackExecutor, 305 new OutcomeReceiver<ExecuteResult, Exception>() { 306 @Override 307 public void onResult(ExecuteResult result) { 308 Trace.endAsyncSection( 309 "OdpClient:scheduleTraining:odpManager.execute", 0); 310 Log.i(TAG, "execute() success: " + result); 311 clearText(); 312 latch.countDown(); 313 } 314 315 @Override 316 public void onError(Exception e) { 317 Trace.endAsyncSection( 318 "OdpClient:scheduleTraining:odpManager.execute", 0); 319 showError("OdpClient:scheduleTraining:odpManager.execute", e); 320 latch.countDown(); 321 } 322 }); 323 latch.await(); 324 Log.d(TAG, "scheduleTraining:odpManager.execute wait success"); 325 } catch (Throwable e) { 326 showError("scheduleTraining", e); 327 } 328 } 329 330 private void registerCancelTrainingButton() { 331 mCancelTrainingButton.setOnClickListener( 332 v -> { 333 var unused = sLightweightExecutor.submit(() -> cancelTraining()); 334 }); 335 } 336 337 private void cancelTraining() { 338 Log.d(TAG, "Odp Client Cancel Training called!"); 339 try { 340 var odpManager = getOdpManager(); 341 CountDownLatch latch = new CountDownLatch(1); 342 Log.i(TAG, "Starting execute() " + getResources().getString(R.string.cancel_training) 343 + " with " + mScheduleTrainingTextBox.getHint().toString() + ": " 344 + mScheduleTrainingTextBox.getText().toString()); 345 PersistableBundle appParams = new PersistableBundle(); 346 appParams.putString("cancel_training", mScheduleTrainingTextBox.getText().toString()); 347 348 Trace.beginAsyncSection("OdpClient:cancelTraining:odpManager.execute", 0); 349 odpManager.execute( 350 ComponentName.createRelative( 351 SERVICE_PACKAGE, 352 SERVICE_CLASS), 353 appParams, 354 sCallbackExecutor, 355 new OutcomeReceiver<ExecuteResult, Exception>() { 356 @Override 357 public void onResult(ExecuteResult result) { 358 Trace.endAsyncSection( 359 "OdpClient:cancelTraining:odpManager.execute", 0); 360 Log.i(TAG, "execute() success: " + result); 361 clearText(); 362 latch.countDown(); 363 } 364 365 @Override 366 public void onError(Exception e) { 367 Trace.endAsyncSection( 368 "OdpClient:cancelTraining:odpManager.execute", 0); 369 showError("OdpClient:cancelTraining:odpManager.execute", e); 370 latch.countDown(); 371 } 372 }); 373 latch.await(); 374 Log.d(TAG, "cancelTraining:odpManager.execute wait success"); 375 } catch (Throwable e) { 376 showError("cancelTraining", e); 377 } 378 } 379 380 private void reportConversion() { 381 try { 382 var odpManager = getOdpManager(); 383 CountDownLatch latch = new CountDownLatch(1); 384 Log.i(TAG, "Starting execute() " + getResources().getString(R.string.report_conversion) 385 + " with " + mReportConversionTextBox.getHint().toString() + ": " 386 + mReportConversionTextBox.getText().toString()); 387 PersistableBundle appParams = new PersistableBundle(); 388 appParams.putString("conversion_ad_id", mReportConversionTextBox.getText().toString()); 389 390 Trace.beginAsyncSection("OdpClient:reportConversion:odpManager.execute", 0); 391 odpManager.execute( 392 ComponentName.createRelative( 393 SERVICE_PACKAGE, 394 SERVICE_CLASS), 395 appParams, 396 sCallbackExecutor, 397 new OutcomeReceiver<ExecuteResult, Exception>() { 398 @Override 399 public void onResult(ExecuteResult result) { 400 Trace.endAsyncSection( 401 "OdpClient:reportConversion:odpManager.execute", 0); 402 Log.i(TAG, "execute() success: " + result); 403 clearText(); 404 latch.countDown(); 405 } 406 407 @Override 408 public void onError(Exception e) { 409 Trace.endAsyncSection( 410 "OdpClient:reportConversion:odpManager.execute", 0); 411 showError("OdpClient:reportConversion:odpManager.execute", e); 412 latch.countDown(); 413 } 414 }); 415 latch.await(); 416 Log.d(TAG, "reportConversion:odpManager.execute wait success"); 417 } catch (Throwable e) { 418 showError("reportConversion", e); 419 } 420 } 421 422 private void showError(String message, Throwable e) { 423 Log.i(TAG, "Error: " + message, e); 424 StringWriter out = new StringWriter(); 425 PrintWriter pw = new PrintWriter(out); 426 pw.println("Error: " + message); 427 e.printStackTrace(pw); 428 pw.flush(); 429 showText(out.toString()); 430 } 431 432 private void showText(String s) { 433 runOnUiThread(() -> { 434 mMessageBox.setText(s); 435 mViewSwitcher.setDisplayedChild(MESSAGE_BOX_INDEX); 436 }); 437 } 438 439 private void clearText() { 440 runOnUiThread(() -> mMessageBox.setText("")); 441 } 442 443 @Override 444 public void onPause() { 445 Log.d(TAG, "onPause"); 446 super.onPause(); 447 } 448 @Override 449 public void onSaveInstanceState(Bundle outState) { 450 Log.d(TAG, "onSaveInstanceState"); 451 super.onSaveInstanceState(outState); 452 } 453 454 @Override 455 public void onDestroy() { 456 Log.d(TAG, "onDestroy"); 457 super.onDestroy(); 458 } 459 460 @Override 461 public void onRestoreInstanceState(Bundle savedInstanceState) { 462 Log.d(TAG, "onRestoreInstanceState"); 463 super.onRestoreInstanceState(savedInstanceState); 464 } 465 466 @Override 467 public void onResume() { 468 Log.d(TAG, "onResume"); 469 super.onResume(); 470 } 471 472 @Override 473 public void onConfigurationChanged(Configuration newConfig) { 474 Log.d(TAG, "onConfigurationChanged"); 475 super.onConfigurationChanged(newConfig); 476 } 477 478 private void printDebuggingInfo() { 479 printPackageVersion(getPackageName()); 480 printPackageVersion(SERVICE_PACKAGE); 481 printApexVersion(ODP_APEX); 482 printApexVersion(ADSERVICES_APEX); 483 } 484 485 private void printPackageVersion(String packageName) { 486 try { 487 PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 0); 488 String versionName = packageInfo.versionName; 489 Log.i(TAG, "packageName: " + packageName + ", versionName: " + versionName); 490 } catch (PackageManager.NameNotFoundException e) { 491 showError("can't find package name " + packageName, e); 492 } 493 } 494 495 private void printApexVersion(String apexName) { 496 try { 497 PackageInfo apexInfo = 498 getPackageManager().getPackageInfo(apexName, PackageManager.MATCH_APEX); 499 if (apexInfo != null && apexInfo.isApex) { 500 Long apexVersionCode = apexInfo.getLongVersionCode(); 501 Log.i(TAG, "apexName: " + apexName + ", longVersionCode: " + apexVersionCode); 502 } 503 } catch (PackageManager.NameNotFoundException e) { 504 showError("apex " + apexName + " not found", e); 505 } 506 } 507 508 } 509