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.sampleappwithnoperm;
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 
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 
106                                         mHandler.post(
107                                                 new Runnable() {
108                                                     @Override
109                                                     public void run() {
110                                                         mResultTextView.append(
111                                                                 sdkName
112                                                                         + "'s topics: "
113                                                                         + NEWLINE
114                                                                         + topics
115                                                                         + NEWLINE);
116                                                         mResultTextView.append(
117                                                                 sdkName
118                                                                         + "'s encrypted topics,"
119                                                                         + " decrypted: "
120                                                                         + NEWLINE
121                                                                         + encryptedTopicsDecrypted
122                                                                         + NEWLINE);
123                                                     }
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 
148                                         Log.e(
149                                                 TAG,
150                                                 "Failed to getTopics for sdk "
151                                                         + sdkName
152                                                         + ": "
153                                                         + t.getMessage());
154 
155                                         mHandler.post(
156                                                 new Runnable() {
157                                                     @Override
158                                                     public void run() {
159                                                         mResultTextView.append(
160                                                                 "Failed to getTopics for sdk "
161                                                                         + sdkName
162                                                                         + ": "
163                                                                         + t.toString()
164                                                                         + NEWLINE);
165                                                     }
166                                                 });
167                                     }
168                                 },
169                                 directExecutor());
170                     }
171                 });
172     }
173 
getTopics(List<Topic> arr)174     private String getTopics(List<Topic> arr) {
175         StringBuilder sb = new StringBuilder();
176         int index = 1;
177         for (Topic topic : arr) {
178             sb.append(index++).append(". ").append(topic.toString()).append(NEWLINE);
179         }
180         return sb.toString();
181     }
182 
getDecryptedTopics(List<EncryptedTopic> arr)183     private String getDecryptedTopics(List<EncryptedTopic> arr) {
184         StringBuilder sb = new StringBuilder();
185         int index = 1;
186         for (EncryptedTopic encryptedTopic : arr) {
187             byte[] cipherText =
188                     Bytes.concat(
189                             encryptedTopic.getEncapsulatedKey(),
190                             encryptedTopic.getEncryptedTopic());
191             byte[] decryptedText =
192                     HpkeJni.decrypt(DECODED_PRIVATE_KEY, cipherText, EMPTY_CONTEXT_INFO);
193             sb.append(index++).append(". ").append(new String(decryptedText)).append(NEWLINE);
194         }
195         return sb.toString();
196     }
197 }
198