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 package com.example.adservices.samples.topics.sampleapp1;
17 
18 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
19 
20 import android.adservices.clients.topics.AdvertisingTopicsClient;
21 import android.adservices.topics.EncryptedTopic;
22 import android.adservices.topics.GetTopicsResponse;
23 import android.adservices.topics.Topic;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.util.Log;
29 import android.view.View;
30 import android.widget.Button;
31 import android.widget.TextView;
32 
33 import androidx.appcompat.app.AppCompatActivity;
34 
35 import com.android.adservices.HpkeJni;
36 
37 import com.google.common.primitives.Bytes;
38 import com.google.common.util.concurrent.FutureCallback;
39 import com.google.common.util.concurrent.Futures;
40 import com.google.common.util.concurrent.ListenableFuture;
41 
42 import java.io.PrintWriter;
43 import java.io.StringWriter;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Base64;
47 import java.util.List;
48 import java.util.concurrent.Executor;
49 import java.util.concurrent.Executors;
50 
51 /**
52  * Android application activity for testing Topics API by providing a button in UI that initiate
53  * user's interaction with Topics Manager in the background. Response from Topics API will be shown
54  * in the app as text as well as toast message. In case anything goes wrong in this process, error
55  * message will also be shown in toast to suggest the Exception encountered.
56  */
57 public class MainActivity extends AppCompatActivity {
58 
59     private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
60     private static final String NEWLINE = "\n";
61     private static final String TAG = "SampleApp1";
62     private static final String RB_SETTING_APP_INTENT = "android.adservices.ui.SETTINGS";
63     private static final List<String> SDK_NAMES = new ArrayList<>(Arrays.asList("SdkName1"));
64     // Test constants for testing encryption
65     private static final String TEST_PRIVATE_KEY_BASE64 =
66             "f86EzLmGaVmc+PwjJk5ADPE4ijQvliWf0CQyY/Zyy7I=";
67     private static final byte[] DECODED_PRIVATE_KEY =
68             Base64.getDecoder().decode(TEST_PRIVATE_KEY_BASE64);
69     private static final byte[] EMPTY_CONTEXT_INFO = new byte[] {};
70     private Button mTopicsClientButton;
71     private Button mTopicsPreviewButton;
72     private TextView mResultTextView;
73     private Button mSettingsAppButton;
74     private AdvertisingTopicsClient mAdvertisingTopicsClient;
75     private Handler mHandler;
76 
77     @Override
onCreate(Bundle savedInstanceState)78     protected void onCreate(Bundle savedInstanceState) {
79         super.onCreate(savedInstanceState);
80         setContentView(R.layout.activity_main);
81         mTopicsClientButton = findViewById(R.id.topics_client_button);
82         mSettingsAppButton = findViewById(R.id.settings_app_launch_button);
83         mTopicsPreviewButton = findViewById(R.id.topics_preview_button);
84         mResultTextView = findViewById(R.id.textView);
85         registerGetTopicsButton();
86         registerLauchSettingsAppButton();
87         registerTopicsPreviewButton();
88         mHandler = new Handler();
89     }
90 
registerGetTopicsButton()91     private void registerGetTopicsButton() {
92         mTopicsClientButton.setOnClickListener(
93                 v -> {
94                     mResultTextView.setText("");
95                     mResultTextView.append("Topics -> ");
96                     for (String sdkName : SDK_NAMES) {
97                         getTopics(sdkName, true);
98                     }
99                 });
100     }
101 
registerTopicsPreviewButton()102     private void registerTopicsPreviewButton() {
103         mTopicsPreviewButton.setOnClickListener(
104                 v -> {
105                     mResultTextView.setText("");
106                     mResultTextView.append("Preview Topics -> ");
107                     for (String sdkName : SDK_NAMES) {
108                         getTopics(sdkName, false);
109                     }
110                 });
111     }
112 
getTopics(List<Topic> arr)113     private String getTopics(List<Topic> arr) {
114         StringBuilder sb = new StringBuilder();
115         int index = 1;
116         for (Topic topic : arr) {
117             sb.append(index++).append(". ").append(topic.toString()).append(NEWLINE);
118         }
119         return sb.toString();
120     }
121 
getDecryptedTopics(List<EncryptedTopic> arr)122     private String getDecryptedTopics(List<EncryptedTopic> arr) {
123         StringBuilder sb = new StringBuilder();
124         int index = 1;
125         for (EncryptedTopic encryptedTopic : arr) {
126             byte[] cipherText =
127                     Bytes.concat(
128                             encryptedTopic.getEncapsulatedKey(),
129                             encryptedTopic.getEncryptedTopic());
130             byte[] decryptedText =
131                     HpkeJni.decrypt(DECODED_PRIVATE_KEY, cipherText, EMPTY_CONTEXT_INFO);
132             sb.append(index++).append(". ").append(new String(decryptedText)).append(NEWLINE);
133         }
134         return sb.toString();
135     }
136 
137     @SuppressWarnings("NewApi")
getTopics(String sdkName, boolean shouldRecordObservation)138     private void getTopics(String sdkName, boolean shouldRecordObservation) {
139         // On R, Privacy Sandbox is initially disabled.
140         try {
141             mAdvertisingTopicsClient =
142                     new AdvertisingTopicsClient.Builder()
143                             .setContext(this)
144                             .setSdkName(sdkName)
145                             .setExecutor(CALLBACK_EXECUTOR)
146                             .setShouldRecordObservation(shouldRecordObservation)
147                             .build();
148         } catch (IllegalStateException e) {
149             mHandler.post(
150                     new Runnable() {
151                         @Override
152                         public void run() {
153                             mResultTextView.append("Privacy Sandbox is not available.");
154                         }
155                     });
156             return;
157         }
158         ListenableFuture<GetTopicsResponse> getTopicsResponseFuture =
159                 mAdvertisingTopicsClient.getTopics();
160 
161         Futures.addCallback(
162                 getTopicsResponseFuture,
163                 new FutureCallback<GetTopicsResponse>() {
164                     @Override
165                     public void onSuccess(GetTopicsResponse result) {
166                         Log.d(TAG, "GetTopics for sdk " + sdkName + " succeeded!");
167                         String topics = getTopics(result.getTopics());
168                         String encryptedTopicsDecrypted =
169                                 getDecryptedTopics(result.getEncryptedTopics());
170 
171                         mHandler.post(
172                                 new Runnable() {
173                                     @Override
174                                     public void run() {
175                                         mResultTextView.append(
176                                                 sdkName
177                                                         + "'s topics: "
178                                                         + NEWLINE
179                                                         + topics
180                                                         + NEWLINE);
181                                         mResultTextView.append(
182                                                 sdkName
183                                                         + "'s encrypted topics, decrypted: "
184                                                         + NEWLINE
185                                                         + encryptedTopicsDecrypted
186                                                         + NEWLINE);
187                                     }
188                                 });
189                         Log.d(TAG, sdkName + "'s topics: " + NEWLINE + topics + NEWLINE);
190                         Log.d(
191                                 TAG,
192                                 sdkName
193                                         + "'s encrypted topics, decrypted: "
194                                         + NEWLINE
195                                         + encryptedTopicsDecrypted
196                                         + NEWLINE);
197                     }
198 
199                     @Override
200                     public void onFailure(Throwable t) {
201                         StringWriter sw = new StringWriter();
202                         PrintWriter pw = new PrintWriter(sw);
203                         t.printStackTrace(pw);
204                         Log.e(
205                                 TAG,
206                                 "Failed to getTopics for sdk " + sdkName + ": " + t.getMessage());
207                         mHandler.post(
208                                 new Runnable() {
209                                     @Override
210                                     public void run() {
211                                         mResultTextView.append(
212                                                 "Failed to getTopics for sdk "
213                                                         + sdkName
214                                                         + ": "
215                                                         + t.toString()
216                                                         + NEWLINE);
217                                     }
218                                 });
219                     }
220                 },
221                 directExecutor());
222     }
223 
registerLauchSettingsAppButton()224     private void registerLauchSettingsAppButton() {
225         mSettingsAppButton.setOnClickListener(
226                 new View.OnClickListener() {
227 
228                     @Override
229                     public void onClick(View view) {
230                         // Use try-catch to handle open Settings App failure and show
231                         // error msg in Sample App
232                         try {
233                             Context context = getApplicationContext();
234                             Intent activity2Intent = new Intent(RB_SETTING_APP_INTENT);
235                             activity2Intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
236                             context.startActivity(activity2Intent);
237                         } catch (Exception err) {
238                             mHandler.post(
239                                     new Runnable() {
240                                         @Override
241                                         public void run() {
242                                             mResultTextView.append(
243                                                     "Failed to open SETTINGS APP: "
244                                                             + err.getMessage()
245                                                             + NEWLINE);
246                                         }
247                                     });
248                         }
249                     }
250                 });
251     }
252 }
253