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.sampleapp2;
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.os.Bundle;
25 import android.os.Handler;
26 import android.util.Log;
27 import android.widget.Button;
28 import android.widget.TextView;
29 
30 import androidx.appcompat.app.AppCompatActivity;
31 
32 import com.android.adservices.HpkeJni;
33 
34 import com.google.common.primitives.Bytes;
35 import com.google.common.util.concurrent.FutureCallback;
36 import com.google.common.util.concurrent.Futures;
37 import com.google.common.util.concurrent.ListenableFuture;
38 
39 import java.io.PrintWriter;
40 import java.io.StringWriter;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Base64;
44 import java.util.List;
45 import java.util.concurrent.Executor;
46 import java.util.concurrent.Executors;
47 
48 /**
49  * Android application activity for testing Topics API by providing a button in UI that initiate
50  * user's interaction with Topics Manager in the background. Response from Topics API will be shown
51  * in the app as text as well as toast message. In case anything goes wrong in this process, error
52  * message will also be shown in toast to suggest the Exception encountered.
53  */
54 public class MainActivity extends AppCompatActivity {
55 
56     private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
57     private static final String NEWLINE = "\n";
58     private static final String TAG = "SampleApp";
59     private static final List<String> SDK_NAMES =
60             new ArrayList<>(Arrays.asList("SdkName1", "SdkName2"));
61     // Test constants for testing encryption
62     private static final String TEST_PRIVATE_KEY_BASE64 =
63             "f86EzLmGaVmc+PwjJk5ADPE4ijQvliWf0CQyY/Zyy7I=";
64     private static final byte[] DECODED_PRIVATE_KEY =
65             Base64.getDecoder().decode(TEST_PRIVATE_KEY_BASE64);
66     private static final byte[] EMPTY_CONTEXT_INFO = new byte[] {};
67     private Button mTopicsClientButton;
68     private TextView mResultTextView;
69     private AdvertisingTopicsClient mAdvertisingTopicsClient;
70     private Handler mHandler;
71 
72     @Override
onCreate(Bundle savedInstanceState)73     protected void onCreate(Bundle savedInstanceState) {
74         super.onCreate(savedInstanceState);
75         setContentView(R.layout.activity_main);
76         mTopicsClientButton = findViewById(R.id.topics_client_button);
77         mResultTextView = findViewById(R.id.textView);
78         registerGetTopicsButton();
79         mHandler = new Handler();
80     }
81 
82     @SuppressWarnings("NewApi")
registerGetTopicsButton()83     private void registerGetTopicsButton() {
84         mTopicsClientButton.setOnClickListener(
85                 v -> {
86                     mResultTextView.setText("");
87                     for (String sdkName : SDK_NAMES) {
88                         mAdvertisingTopicsClient =
89                                 new AdvertisingTopicsClient.Builder()
90                                         .setContext(this)
91                                         .setSdkName(sdkName)
92                                         .setExecutor(CALLBACK_EXECUTOR)
93                                         .build();
94                         ListenableFuture<GetTopicsResponse> getTopicsResponseFuture =
95                                 mAdvertisingTopicsClient.getTopics();
96 
97                         Futures.addCallback(
98                                 getTopicsResponseFuture,
99                                 new FutureCallback<GetTopicsResponse>() {
100                                     @Override
101                                     public void onSuccess(GetTopicsResponse result) {
102                                         Log.d(TAG, "GetTopics for sdk " + sdkName + " succeeded!");
103                                         String topics = getTopics(result.getTopics());
104                                         String encryptedTopicsDecrypted =
105                                                 getDecryptedTopics(result.getEncryptedTopics());
106 
107                                         mHandler.post(
108                                                 new Runnable() {
109                                                     @Override
110                                                     public void run() {
111                                                         mResultTextView.append(
112                                                                 sdkName
113                                                                         + "'s topics: "
114                                                                         + NEWLINE
115                                                                         + topics
116                                                                         + NEWLINE);
117                                                         mResultTextView.append(
118                                                                 sdkName
119                                                                         + "'s encrypted topics,"
120                                                                         + " decrypted: "
121                                                                         + NEWLINE
122                                                                         + encryptedTopicsDecrypted
123                                                                         + NEWLINE);
124                                                     }
125                                                 });
126 
127                                         Log.d(
128                                                 TAG,
129                                                 sdkName
130                                                         + "'s topics: "
131                                                         + NEWLINE
132                                                         + topics
133                                                         + NEWLINE);
134                                         Log.d(
135                                                 TAG,
136                                                 sdkName
137                                                         + "'s encrypted topics, decrypted: "
138                                                         + NEWLINE
139                                                         + encryptedTopicsDecrypted
140                                                         + NEWLINE);
141                                     }
142 
143                                     @Override
144                                     public void onFailure(Throwable t) {
145                                         StringWriter sw = new StringWriter();
146                                         PrintWriter pw = new PrintWriter(sw);
147                                         t.printStackTrace(pw);
148 
149                                         Log.e(
150                                                 TAG,
151                                                 "Failed to getTopics for sdk "
152                                                         + sdkName
153                                                         + ": "
154                                                         + t.getMessage());
155 
156                                         mHandler.post(
157                                                 new Runnable() {
158                                                     @Override
159                                                     public void run() {
160                                                         mResultTextView.append(
161                                                                 "Failed to getTopics for sdk "
162                                                                         + sdkName
163                                                                         + ": "
164                                                                         + t.toString()
165                                                                         + NEWLINE);
166                                                     }
167                                                 });
168                                     }
169                                 },
170                                 directExecutor());
171                     }
172                 });
173     }
174 
getTopics(List<Topic> arr)175     private String getTopics(List<Topic> arr) {
176         StringBuilder sb = new StringBuilder();
177         int index = 1;
178         for (Topic topic : arr) {
179             sb.append(index++).append(". ").append(topic.toString()).append(NEWLINE);
180         }
181         return sb.toString();
182     }
183 
getDecryptedTopics(List<EncryptedTopic> arr)184     private String getDecryptedTopics(List<EncryptedTopic> arr) {
185         StringBuilder sb = new StringBuilder();
186         int index = 1;
187         for (EncryptedTopic encryptedTopic : arr) {
188             byte[] cipherText =
189                     Bytes.concat(
190                             encryptedTopic.getEncapsulatedKey(),
191                             encryptedTopic.getEncryptedTopic());
192             byte[] decryptedText =
193                     HpkeJni.decrypt(DECODED_PRIVATE_KEY, cipherText, EMPTY_CONTEXT_INFO);
194             sb.append(index++).append(". ").append(new String(decryptedText)).append(NEWLINE);
195         }
196         return sb.toString();
197     }
198 }
199