• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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