Skip to content

Commit f2fb0e6

Browse files
Teng Chenfacebook-github-bot
authored andcommitted
add topics manager skeleton
Summary: - add topics manager into android sdk at skeleton level Reviewed By: jjiang10 Differential Revision: D71073660 Privacy Context Container: L1220306 fbshipit-source-id: 461214a98775ad7b6fda271cc3ce667e2610dae3
1 parent 7638285 commit f2fb0e6

File tree

8 files changed

+299
-0
lines changed

8 files changed

+299
-0
lines changed

facebook-core/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,5 @@
4545
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
4646
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
4747
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE" />
48+
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
4849
</manifest>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
package android.adservices.topics;
10+
11+
import androidx.annotation.NonNull;
12+
13+
public final class GetTopicsRequest {
14+
GetTopicsRequest() {
15+
throw new RuntimeException("Stub!");
16+
}
17+
18+
@NonNull
19+
public String getAdsSdkName() {
20+
throw new RuntimeException("Stub!");
21+
}
22+
23+
public boolean shouldRecordObservation() {
24+
throw new RuntimeException("Stub!");
25+
}
26+
27+
public static final class Builder {
28+
public Builder() {
29+
throw new RuntimeException("Stub!");
30+
}
31+
32+
@NonNull
33+
public Builder setAdsSdkName(@NonNull String adsSdkName) {
34+
throw new RuntimeException("Stub!");
35+
}
36+
37+
@NonNull
38+
public Builder setShouldRecordObservation(boolean recordObservation) {
39+
throw new RuntimeException("Stub!");
40+
}
41+
42+
@NonNull
43+
public GetTopicsRequest build() {
44+
throw new RuntimeException("Stub!");
45+
}
46+
}
47+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
package android.adservices.topics;
10+
11+
import androidx.annotation.NonNull;
12+
import java.util.List;
13+
14+
public final class GetTopicsResponse {
15+
GetTopicsResponse() {
16+
throw new RuntimeException("Stub!");
17+
}
18+
19+
@NonNull
20+
public List<Topic> getTopics() {
21+
throw new RuntimeException("Stub!");
22+
}
23+
24+
public boolean equals(Object o) {
25+
throw new RuntimeException("Stub!");
26+
}
27+
28+
public int hashCode() {
29+
throw new RuntimeException("Stub!");
30+
}
31+
32+
public static final class Builder {
33+
public Builder(@NonNull List<Topic> topics) {
34+
throw new RuntimeException("Stub!");
35+
}
36+
37+
@NonNull
38+
public GetTopicsResponse build() {
39+
throw new RuntimeException("Stub!");
40+
}
41+
}
42+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
package android.adservices.topics;
10+
11+
import androidx.annotation.NonNull;
12+
13+
import java.util.List;
14+
15+
public final class Topic {
16+
Topic() {
17+
throw new RuntimeException("Stub!");
18+
}
19+
20+
@NonNull
21+
public List<Topic> getTopics() {
22+
throw new RuntimeException("Stub!");
23+
}
24+
25+
public boolean equals(Object o) {
26+
throw new RuntimeException("Stub!");
27+
}
28+
29+
public int hashCode() {
30+
throw new RuntimeException("Stub!");
31+
}
32+
33+
public static final class Builder {
34+
public Builder(@NonNull List<Topic> topics) {
35+
throw new RuntimeException("Stub!");
36+
}
37+
38+
@NonNull
39+
public Topic build() {
40+
throw new RuntimeException("Stub!");
41+
}
42+
}
43+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
package android.adservices.topics;
10+
11+
import androidx.annotation.NonNull;
12+
import android.content.Context;
13+
import android.os.OutcomeReceiver;
14+
import java.util.concurrent.Executor;
15+
16+
public final class TopicsManager {
17+
TopicsManager() {
18+
throw new RuntimeException("Stub!");
19+
}
20+
21+
@NonNull
22+
public static TopicsManager get(@NonNull Context context) {
23+
throw new RuntimeException("Stub!");
24+
}
25+
26+
@NonNull
27+
public void getTopics(@NonNull GetTopicsRequest getTopicsRequest, @NonNull Executor executor, @NonNull OutcomeReceiver<GetTopicsResponse, Exception> callback) {
28+
throw new RuntimeException("Stub!");
29+
}
30+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
package com.facebook.appevents.gps.topics
9+
10+
import android.adservices.topics.GetTopicsRequest
11+
import android.adservices.topics.GetTopicsResponse
12+
import android.adservices.topics.TopicsManager
13+
import android.annotation.TargetApi
14+
import android.os.OutcomeReceiver
15+
import android.util.Log
16+
import com.facebook.FacebookSdk
17+
import com.facebook.internal.instrument.crashshield.AutoHandleExceptions
18+
import java.util.concurrent.Executor
19+
import java.util.concurrent.Executors
20+
21+
@AutoHandleExceptions
22+
object GpsTopicsManager {
23+
private const val RECORD_OBSERVATION = true
24+
private val TAG = GpsTopicsManager::class.java.toString()
25+
private val executor: Executor by lazy { Executors.newCachedThreadPool() }
26+
27+
@JvmStatic
28+
@TargetApi(34)
29+
fun getTopics(): List<String> {
30+
if (!shouldObserveTopics()) return emptyList()
31+
32+
val context = FacebookSdk.getApplicationContext()
33+
var topicsResult: ArrayList<String> = ArrayList()
34+
try {
35+
val callback: OutcomeReceiver<GetTopicsResponse, Exception> =
36+
object : OutcomeReceiver<GetTopicsResponse, Exception> {
37+
override fun onResult(response: GetTopicsResponse) {
38+
try {
39+
topicsResult.addAll(processObservedTopics(response))
40+
} catch (e: Exception) {
41+
// TODO - customized error handling
42+
Log.w(TAG, "GPS_TOPICS_PROCESSING_FAILED")
43+
}
44+
}
45+
46+
override fun onError(error: java.lang.Exception) {
47+
// TODO - customized error handling
48+
Log.w(TAG, "GPS_TOPICS_OBSERVATION_ERROR")
49+
}
50+
}
51+
52+
val topicsRequestBuilder: GetTopicsRequest.Builder = GetTopicsRequest.Builder()
53+
topicsRequestBuilder.setShouldRecordObservation(RECORD_OBSERVATION)
54+
topicsRequestBuilder.setAdsSdkName(context.packageName)
55+
context.getSystemService(TopicsManager::class.java)?.getTopics(
56+
topicsRequestBuilder.build(),
57+
executor,
58+
callback)
59+
} catch (e: Exception) {
60+
// TODO - customized error handling
61+
Log.w(TAG, "GPS_TOPICS_OBSERVATION_FAILED")
62+
return emptyList()
63+
}
64+
65+
return topicsResult
66+
}
67+
68+
private fun shouldObserveTopics(): Boolean {
69+
// TODO - default in false for fetcher skeleton
70+
return false
71+
}
72+
73+
@TargetApi(34)
74+
private fun processObservedTopics(response: GetTopicsResponse): List<String> {
75+
// TODO - topics parser to process
76+
if (response.topics.isEmpty()) return emptyList()
77+
return response.topics.map { it.toString() }
78+
}
79+
}

facebook-core/src/main/res/xml/ad_services_config.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@
2525
<attribution allowAllToAccess="true" />
2626
<!-- Protected Audience on Android API -->
2727
<custom-audiences allowAllToAccess="true" />
28+
<!-- Topics API -->
29+
<topics allowAllToAccess="true" />
2830
</ad-services-config>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.facebook.appevents.gps.topics
2+
3+
import android.adservices.topics.GetTopicsResponse
4+
import android.adservices.topics.Topic
5+
import android.adservices.topics.TopicsManager
6+
import android.content.Context
7+
import android.os.OutcomeReceiver
8+
import com.facebook.FacebookPowerMockTestCase
9+
import com.facebook.FacebookSdk
10+
import org.junit.Before
11+
import org.junit.Test
12+
import org.mockito.kotlin.any
13+
import org.mockito.kotlin.mock
14+
import org.mockito.kotlin.whenever
15+
import org.powermock.api.mockito.PowerMockito
16+
import org.powermock.core.classloader.annotations.PrepareForTest
17+
import org.robolectric.annotation.Config
18+
import kotlin.test.assertNotNull
19+
20+
@PrepareForTest(
21+
FacebookSdk::class,
22+
TopicsManager::class,
23+
GetTopicsResponse::class,
24+
Topic::class,
25+
)
26+
@Config(sdk = [23])
27+
class GpsTopicsManagerTest : FacebookPowerMockTestCase() {
28+
private lateinit var mockContext: Context
29+
private lateinit var mockTopicsManager: TopicsManager
30+
31+
@Before
32+
fun setUp() {
33+
mockContext = mock()
34+
mockTopicsManager = mock()
35+
whenever(mockContext.applicationContext).thenReturn(mockContext)
36+
PowerMockito.mockStatic(FacebookSdk::class.java)
37+
whenever(FacebookSdk.getApplicationContext()).thenReturn(mockContext)
38+
PowerMockito.mockStatic(TopicsManager::class.java)
39+
whenever(mockContext.getSystemService(TopicsManager::class.java)).thenReturn(
40+
mockTopicsManager
41+
)
42+
whenever(mockTopicsManager.getTopics(any(), any(), any())).thenAnswer { invocation ->
43+
val callback = invocation.getArgument<OutcomeReceiver<GetTopicsResponse, Exception>>(2)
44+
val getTopicsResponse = GetTopicsResponse.Builder(listOf()).build()
45+
callback.onResult(getTopicsResponse)
46+
}
47+
}
48+
49+
@Test
50+
fun testGetTopicsSkeleton() {
51+
// TODO - skeleton workflow validation pending detailed test cases
52+
val topics = GpsTopicsManager.getTopics()
53+
assertNotNull(topics)
54+
}
55+
}

0 commit comments

Comments
 (0)