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.sampleapp5;
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("SdkName3", "SdkName4", "SdkName5"));
61     // Test constants for testing encryption
62     private static final String PRIVATE_KEY_BASE64 = "f86EzLmGaVmc+PwjJk5ADPE4ijQvliWf0CQyY/Zyy7I=";
63     private static final byte[] DECODED_PRIVATE_KEY =
64             Base64.getDecoder().decode(PRIVATE_KEY_BASE64);
65     private static final byte[] EMPTY_CONTEXT_INFO = new byte[] {};
66     private Button mTopicsClientButton;
67     private TextView mResultTextView;
68     private AdvertisingTopicsClient mAdvertisingTopicsClient;
69     private Handler mHandler;
70 
71     @Override
onCreate(Bundle savedInstanceState)72     protected void onCreate(Bundle savedInstanceState) {
73         super.onCreate(savedInstanceState);
74         setContentView(R.layout.activity_main);
75         mTopicsClientButton = findViewById(R.id.topics_client_button);
76         mResultTextView = findViewById(R.id.textView);
77         registerGetTopicsButton();
78         mHandler = new Handler();
79     }
80 
81     @SuppressWarnings("NewApi")
registerGetTopicsButton()82     private void registerGetTopicsButton() {
83         mTopicsClientButton.setOnClickListener(
84                 v -> {
85                     mResultTextView.setText("");
86                     for (String sdkName : SDK_NAMES) {
87                         mAdvertisingTopicsClient =
88                                 new AdvertisingTopicsClient.Builder()
89                                         .setContext(this)
90                                         .setSdkName(sdkName)
91                                         .setExecutor(CALLBACK_EXECUTOR)
92                                         .build();
93                         ListenableFuture<GetTopicsResponse> getTopicsResponseFuture =
94                                 mAdvertisingTopicsClient.getTopics();
95 
96                         Futures.addCallback(
97                                 getTopicsResponseFuture,
98                                 new FutureCallback<GetTopicsResponse>() {
99                                     @Override
100                                     public void onSuccess(GetTopicsResponse result) {
101                                         Log.d(TAG, "GetTopics for sdk " + sdkName + " succeeded!");
102                                         String topics = getTopics(result.getTopics());
103                                         String encryptedTopicsDecrypted =
104                                                 getDecryptedTopics(result.getEncryptedTopics());
105                                         mHandler.post(
106                                                 new Runnable() {
107                                                     @Override
108                                                     public void run() {
109                                                         mResultTextView.append(
110                                                                 sdkName
111                                                                         + "'s topics: "
112                                                                         + NEWLINE
113                                                                         + topics
114                                                                         + NEWLINE);
115                                                         mResultTextView.append(
116                                                                 sdkName
117                                                                         + "'s encrypted topics,"
118                                                                         + " decrypted: "
119                                                                         + NEWLINE
120                                                                         + encryptedTopicsDecrypted
121                                                                         + NEWLINE);
122                                                     }
123                                                 });
124 
125                                         Log.d(
126                                                 TAG,
127                                                 sdkName
128                                                         + "'s topics: "
129                                                         + NEWLINE
130                                                         + topics
131                                                         + NEWLINE);
132                                         Log.d(
133                                                 TAG,
134                                                 sdkName
135                                                         + "'s encrypted topics, decrypted: "
136                                                         + NEWLINE
137                                                         + encryptedTopicsDecrypted
138                                                         + NEWLINE);
139                                     }
140 
141                                     @Override
142                                     public void onFailure(Throwable t) {
143                                         StringWriter sw = new StringWriter();
144                                         PrintWriter pw = new PrintWriter(sw);
145                                         t.printStackTrace(pw);
146 
147                                         Log.e(
148                                                 TAG,
149                                                 "Failed to getTopics for sdk "
150                                                         + sdkName
151                                                         + ": "
152                                                         + t.getMessage());
153 
154                                         mHandler.post(
155                                                 new Runnable() {
156                                                     @Override
157                                                     public void run() {
158                                                         mResultTextView.append(
159                                                                 "Failed to getTopics for sdk "
160                                                                         + sdkName
161                                                                         + ": "
162                                                                         + t.toString()
163                                                                         + NEWLINE);
164                                                     }
165                                                 });
166                                     }
167                                 },
168                                 directExecutor());
169                     }
170                 });
171     }
172 
getTopics(List<Topic> arr)173     private String getTopics(List<Topic> arr) {
174         StringBuilder sb = new StringBuilder();
175         int index = 1;
176         for (Topic topic : arr) {
177             sb.append(index++).append(". ").append(topic.toString()).append(NEWLINE);
178         }
179         return sb.toString();
180     }
181 
getDecryptedTopics(List<EncryptedTopic> arr)182     private String getDecryptedTopics(List<EncryptedTopic> arr) {
183         StringBuilder sb = new StringBuilder();
184         int index = 1;
185         for (EncryptedTopic encryptedTopic : arr) {
186             byte[] cipherText =
187                     Bytes.concat(
188                             encryptedTopic.getEncapsulatedKey(),
189                             encryptedTopic.getEncryptedTopic());
190             byte[] decryptedText =
191                     HpkeJni.decrypt(DECODED_PRIVATE_KEY, cipherText, EMPTY_CONTEXT_INFO);
192             sb.append(index++).append(". ").append(new String(decryptedText)).append(NEWLINE);
193         }
194         return sb.toString();
195     }
196 }
197