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