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.Trace;
33 import android.util.Log;
34 import android.view.SurfaceControlViewHost.SurfacePackage;
35 import android.view.SurfaceHolder;
36 import android.view.SurfaceView;
37 import android.view.View;
38 import android.widget.Button;
39 import android.widget.EditText;
40 import android.widget.Toast;
41 
42 import com.google.common.util.concurrent.Futures;
43 
44 import java.util.concurrent.CountDownLatch;
45 import java.util.concurrent.Executor;
46 import java.util.concurrent.Executors;
47 import java.util.concurrent.atomic.AtomicReference;
48 
49 public class MainActivity extends Activity {
50     private static final String TAG = "OdpClient";
51     private static final String SERVICE_PACKAGE = "com.example.odpsamplenetwork";
52     private static final String SERVICE_CLASS = "com.example.odpsamplenetwork.SampleService";
53     private static final String ODP_APEX = "com.google.android.ondevicepersonalization";
54     private static final String ADSERVICES_APEX = "com.google.android.adservices";
55 
56     private EditText mTextBox;
57     private Button mGetAdButton;
58     private EditText mScheduleTrainingTextBox;
59     private EditText mScheduleIntervalTextBox;
60     private Button mScheduleTrainingButton;
61     private Button mCancelTrainingButton;
62     private EditText mReportConversionTextBox;
63     private Button mReportConversionButton;
64     private SurfaceView mRenderedView;
65     private Context mContext;
66     private static Executor sCallbackExecutor = Executors.newSingleThreadExecutor();
67 
68     class SurfaceCallback implements SurfaceHolder.Callback {
surfaceCreated(SurfaceHolder holder)69         @Override public void surfaceCreated(SurfaceHolder holder) {
70             Log.d(TAG, "surfaceCreated");
71         }
surfaceDestroyed(SurfaceHolder holder)72         @Override public void surfaceDestroyed(SurfaceHolder holder) {
73             Log.d(TAG, "surfaceDestroyed");
74         }
surfaceChanged( SurfaceHolder holder, int format, int width, int height)75         @Override public void surfaceChanged(
76                 SurfaceHolder holder, int format, int width, int height) {
77             Log.d(TAG, "surfaceChanged");
78         }
79     }
80 
81     @Override
onCreate(Bundle savedInstanceState)82     public void onCreate(Bundle savedInstanceState) {
83         Log.d(TAG, "onCreate");
84         super.onCreate(savedInstanceState);
85         setContentView(R.layout.activity_main);
86         mContext = getApplicationContext();
87         mRenderedView = findViewById(R.id.rendered_view);
88         mRenderedView.setVisibility(View.INVISIBLE);
89         mRenderedView.getHolder().addCallback(new SurfaceCallback());
90         mGetAdButton = findViewById(R.id.get_ad_button);
91         mScheduleTrainingButton = findViewById(R.id.schedule_training_button);
92         mCancelTrainingButton = findViewById(R.id.cancel_training_button);
93         mReportConversionButton = findViewById(R.id.report_conversion_button);
94         mTextBox = findViewById(R.id.text_box);
95         mScheduleTrainingTextBox = findViewById(R.id.schedule_training_text_box);
96         mScheduleIntervalTextBox = findViewById(R.id.schedule_interval_text_box);
97         mReportConversionTextBox = findViewById(R.id.report_conversion_text_box);
98         registerGetAdButton();
99         registerScheduleTrainingButton();
100         registerReportConversionButton();
101         registerCancelTrainingButton();
102 
103         Futures.submit(
104                 () -> printDebuggingInfo(),
105                 sCallbackExecutor);
106     }
107 
registerGetAdButton()108     private void registerGetAdButton() {
109         mGetAdButton.setOnClickListener(
110                 v -> makeRequest());
111     }
112 
registerReportConversionButton()113     private void registerReportConversionButton() {
114         mReportConversionButton.setOnClickListener(v -> reportConversion());
115     }
116 
getOdpManager()117     private OnDevicePersonalizationManager getOdpManager() {
118         return mContext.getSystemService(OnDevicePersonalizationManager.class);
119     }
120 
makeRequest()121     private void makeRequest() {
122         try {
123             var odpManager = getOdpManager();
124             CountDownLatch latch = new CountDownLatch(1);
125             Log.i(TAG, "Starting execute() " + getResources().getString(R.string.get_ad)
126                     + " with " + mTextBox.getHint().toString() + ": "
127                     + mTextBox.getText().toString());
128             AtomicReference<ExecuteResult> slotResultHandle = new AtomicReference<>();
129             PersistableBundle appParams = new PersistableBundle();
130             appParams.putString("keyword", mTextBox.getText().toString());
131 
132             Trace.beginAsyncSection("OdpClient:makeRequest:odpManager.execute", 0);
133             odpManager.execute(
134                     ComponentName.createRelative(
135                             SERVICE_PACKAGE,
136                             SERVICE_CLASS),
137                     appParams,
138                     sCallbackExecutor,
139                     new OutcomeReceiver<ExecuteResult, Exception>() {
140                         @Override
141                         public void onResult(ExecuteResult result) {
142                             Trace.endAsyncSection("OdpClient:makeRequest:odpManager.execute", 0);
143                             Log.i(TAG, "execute() success: " + result);
144                             if (result != null) {
145                                 slotResultHandle.set(result);
146                             } else {
147                                 Log.e(TAG, "No results!");
148                             }
149                             latch.countDown();
150                         }
151 
152                         @Override
153                         public void onError(Exception e) {
154                             Trace.endAsyncSection("OdpClient:makeRequest:odpManager.execute", 0);
155                             makeToast("execute() error: " + e.toString());
156                             latch.countDown();
157                         }
158                     });
159             latch.await();
160             Log.d(TAG, "makeRequest:odpManager.execute wait success");
161 
162             Trace.beginAsyncSection("OdpClient:makeRequest:odpManager.requestSurfacePackage", 0);
163             odpManager.requestSurfacePackage(
164                     slotResultHandle.get().getSurfacePackageToken(),
165                     mRenderedView.getHostToken(),
166                     getDisplay().getDisplayId(),
167                     mRenderedView.getWidth(),
168                     mRenderedView.getHeight(),
169                     sCallbackExecutor,
170                     new OutcomeReceiver<SurfacePackage, Exception>() {
171                         @Override
172                         public void onResult(SurfacePackage surfacePackage) {
173                             Trace.endAsyncSection(
174                                     "OdpClient:makeRequest:odpManager.requestSurfacePackage", 0);
175                             Log.i(TAG,
176                                     "requestSurfacePackage() success: "
177                                     + surfacePackage.toString());
178                             new Handler(Looper.getMainLooper()).post(() -> {
179                                 if (surfacePackage != null) {
180                                     mRenderedView.setChildSurfacePackage(
181                                             surfacePackage);
182                                 }
183                                 mRenderedView.setZOrderOnTop(true);
184                                 mRenderedView.setVisibility(View.VISIBLE);
185                             });
186                         }
187 
188                         @Override
189                         public void onError(Exception e) {
190                             Trace.endAsyncSection(
191                                     "OdpClient:makeRequest:odpManager.requestSurfacePackage", 0);
192                             makeToast("requestSurfacePackage() error: " + e.toString());
193                         }
194                     });
195         } catch (Exception e) {
196             Log.e(TAG, "Error", e);
197         }
198     }
199 
registerScheduleTrainingButton()200     private void registerScheduleTrainingButton() {
201         mScheduleTrainingButton.setOnClickListener(
202                 v -> scheduleTraining());
203     }
204 
scheduleTraining()205     private void scheduleTraining() {
206         try {
207             var odpManager = getOdpManager();
208             CountDownLatch latch = new CountDownLatch(1);
209             Log.i(
210                     TAG,
211                     "Starting execute() "
212                             + getResources().getString(R.string.schedule_training)
213                             + " with "
214                             + mScheduleTrainingTextBox.getHint().toString()
215                             + ": "
216                             + mScheduleTrainingTextBox.getText().toString());
217             PersistableBundle appParams = new PersistableBundle();
218             appParams.putString("schedule_training", mScheduleTrainingTextBox.getText().toString());
219             if (mScheduleIntervalTextBox.getText() != null
220                     && mScheduleIntervalTextBox.getText().toString() != null
221                     && !mScheduleIntervalTextBox.getText().toString().isBlank()) {
222                 appParams.putLong(
223                         "schedule_interval",
224                         Long.parseUnsignedLong(mScheduleIntervalTextBox.getText().toString()));
225             }
226 
227             Trace.beginAsyncSection("OdpClient:scheduleTraining:odpManager.execute", 0);
228             odpManager.execute(
229                     ComponentName.createRelative(
230                             SERVICE_PACKAGE,
231                             SERVICE_CLASS),
232                     appParams,
233                     sCallbackExecutor,
234                     new OutcomeReceiver<ExecuteResult, Exception>() {
235                         @Override
236                         public void onResult(ExecuteResult result) {
237                             Trace.endAsyncSection(
238                                     "OdpClient:scheduleTraining:odpManager.execute", 0);
239                             Log.i(TAG, "execute() success: " + result);
240                             latch.countDown();
241                         }
242 
243                         @Override
244                         public void onError(Exception e) {
245                             Trace.endAsyncSection(
246                                     "OdpClient:scheduleTraining:odpManager.execute", 0);
247                             makeToast("execute() error: " + e.toString());
248                             latch.countDown();
249                         }
250                     });
251             latch.await();
252             Log.d(TAG, "scheduleTraining:odpManager.execute wait success");
253         } catch (Exception e) {
254             Log.e(TAG, "Error", e);
255         }
256     }
257 
registerCancelTrainingButton()258     private void registerCancelTrainingButton() {
259         mCancelTrainingButton.setOnClickListener(
260                 v -> cancelTraining());
261     }
262 
cancelTraining()263     private void cancelTraining() {
264         Log.d(TAG, "Odp Client Cancel Training called!");
265         try {
266             var odpManager = getOdpManager();
267             CountDownLatch latch = new CountDownLatch(1);
268             Log.i(TAG, "Starting execute() " + getResources().getString(R.string.cancel_training)
269                     + " with " + mScheduleTrainingTextBox.getHint().toString() + ": "
270                     + mScheduleTrainingTextBox.getText().toString());
271             PersistableBundle appParams = new PersistableBundle();
272             appParams.putString("cancel_training", mScheduleTrainingTextBox.getText().toString());
273 
274             Trace.beginAsyncSection("OdpClient:cancelTraining:odpManager.execute", 0);
275             odpManager.execute(
276                     ComponentName.createRelative(
277                             SERVICE_PACKAGE,
278                             SERVICE_CLASS),
279                     appParams,
280                     sCallbackExecutor,
281                     new OutcomeReceiver<ExecuteResult, Exception>() {
282                         @Override
283                         public void onResult(ExecuteResult result) {
284                             Trace.endAsyncSection(
285                                     "OdpClient:cancelTraining:odpManager.execute", 0);
286                             Log.i(TAG, "execute() success: " + result);
287                             latch.countDown();
288                         }
289 
290                         @Override
291                         public void onError(Exception e) {
292                             Trace.endAsyncSection(
293                                     "OdpClient:cancelTraining:odpManager.execute", 0);
294                             makeToast("execute() error: " + e.toString());
295                             latch.countDown();
296                         }
297                     });
298             latch.await();
299             Log.d(TAG, "cancelTraining:odpManager.execute wait success");
300         } catch (Exception e) {
301             Log.e(TAG, "Error", e);
302         }
303     }
304 
reportConversion()305     private void reportConversion() {
306         try {
307             var odpManager = getOdpManager();
308             CountDownLatch latch = new CountDownLatch(1);
309             Log.i(TAG, "Starting execute() " + getResources().getString(R.string.report_conversion)
310                     + " with " + mReportConversionTextBox.getHint().toString() + ": "
311                     + mReportConversionTextBox.getText().toString());
312             PersistableBundle appParams = new PersistableBundle();
313             appParams.putString("conversion_ad_id", mReportConversionTextBox.getText().toString());
314 
315             Trace.beginAsyncSection("OdpClient:reportConversion:odpManager.execute", 0);
316             odpManager.execute(
317                     ComponentName.createRelative(
318                             SERVICE_PACKAGE,
319                             SERVICE_CLASS),
320                     appParams,
321                     sCallbackExecutor,
322                     new OutcomeReceiver<ExecuteResult, Exception>() {
323                         @Override
324                         public void onResult(ExecuteResult result) {
325                             Trace.endAsyncSection(
326                                     "OdpClient:reportConversion:odpManager.execute", 0);
327                             Log.i(TAG, "execute() success: " + result);
328                             latch.countDown();
329                         }
330 
331                         @Override
332                         public void onError(Exception e) {
333                             Trace.endAsyncSection(
334                                     "OdpClient:reportConversion:odpManager.execute", 0);
335                             makeToast("execute() error: " + e.toString());
336                             latch.countDown();
337                         }
338                     });
339             latch.await();
340             Log.d(TAG, "reportConversion:odpManager.execute wait success");
341         } catch (Exception e) {
342             Log.e(TAG, "Error", e);
343         }
344     }
345 
makeToast(String message)346     private void makeToast(String message) {
347         Log.i(TAG, message);
348         runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show());
349     }
350 
351     @Override
onPause()352     public void onPause() {
353         Log.d(TAG, "onPause");
354         super.onPause();
355     }
356     @Override
onSaveInstanceState(Bundle outState)357     public void onSaveInstanceState(Bundle outState) {
358         Log.d(TAG, "onSaveInstanceState");
359         super.onSaveInstanceState(outState);
360     }
361 
362     @Override
onDestroy()363     public void onDestroy() {
364         Log.d(TAG, "onDestroy");
365         super.onDestroy();
366     }
367 
368     @Override
onRestoreInstanceState(Bundle savedInstanceState)369     public void onRestoreInstanceState(Bundle savedInstanceState) {
370         Log.d(TAG, "onRestoreInstanceState");
371         super.onRestoreInstanceState(savedInstanceState);
372     }
373 
374     @Override
onResume()375     public void onResume() {
376         Log.d(TAG, "onResume");
377         super.onResume();
378     }
379 
380     @Override
onConfigurationChanged(Configuration newConfig)381     public void onConfigurationChanged(Configuration newConfig) {
382         Log.d(TAG, "onConfigurationChanged");
383         super.onConfigurationChanged(newConfig);
384     }
385 
printDebuggingInfo()386     private void printDebuggingInfo() {
387         printPackageVersion(getPackageName());
388         printPackageVersion(SERVICE_PACKAGE);
389         printApexVersion(ODP_APEX);
390         printApexVersion(ADSERVICES_APEX);
391     }
392 
printPackageVersion(String packageName)393     private void printPackageVersion(String packageName) {
394         try {
395             PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 0);
396             String versionName = packageInfo.versionName;
397             Log.i(TAG, "packageName: " + packageName + ", versionName: " + versionName);
398         } catch (PackageManager.NameNotFoundException e) {
399             Log.e(TAG, "can't find package name " + packageName);
400         }
401     }
402 
printApexVersion(String apexName)403     private void printApexVersion(String apexName) {
404         try {
405             PackageInfo apexInfo =
406                     getPackageManager().getPackageInfo(apexName, PackageManager.MATCH_APEX);
407             if (apexInfo != null && apexInfo.isApex) {
408                 Long apexVersionCode = apexInfo.getLongVersionCode();
409                 Log.i(TAG, "apexName: " + apexName + ", longVersionCode: " + apexVersionCode);
410             }
411         } catch (PackageManager.NameNotFoundException e) {
412             Log.e(TAG, "apex " + apexName + " not found");
413         }
414     }
415 
416 }
417