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