aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2019-09-11 12:54:54 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2019-09-11 12:54:54 +0000
commitf24b408a258fd197434e5c278b1203968327bc05 (patch)
treeedba58c6c0ca1a336bb9341318ffcd3bf00ffb6d
parent708a89255b62a7f0ab45165ce76ab092c6d68930 (diff)
parent9aabe430a3e1e2ab09c787cbc8a6b1c09fecd781 (diff)
downloadexperimental-f24b408a258fd197434e5c278b1203968327bc05.tar.gz
Merge "Add HiddenApiLogging sample"
-rw-r--r--HiddenApiLoggingApp/Android.bp36
-rw-r--r--HiddenApiLoggingApp/AndroidManifest.xml33
-rw-r--r--HiddenApiLoggingApp/README12
-rw-r--r--HiddenApiLoggingApp/src/com/android/hiddenapiapp/Receiver.java116
-rw-r--r--HiddenApiLoggingApp/tool/hidden_api_log_tracking.mf1
-rw-r--r--HiddenApiLoggingApp/tool/src/com/hiddenapi/HiddenApiLogTracking.java133
6 files changed, 331 insertions, 0 deletions
diff --git a/HiddenApiLoggingApp/Android.bp b/HiddenApiLoggingApp/Android.bp
new file mode 100644
index 0000000..c29e7b0
--- /dev/null
+++ b/HiddenApiLoggingApp/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_app {
+ name: "HiddenApiLoggingApp",
+ certificate: "platform",
+ sdk_version: "system_current",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.core_core",
+ "androidx.fragment_fragment",
+ ],
+}
+
+java_binary_host {
+ name: "hidden_api_log_tracking",
+ manifest: "tool/hidden_api_log_tracking.mf",
+ srcs: [
+ "tool/src/com/hiddenapi/HiddenApiLogTracking.java",
+ ],
+ static_libs: [
+ "platformprotos",
+ ]
+} \ No newline at end of file
diff --git a/HiddenApiLoggingApp/AndroidManifest.xml b/HiddenApiLoggingApp/AndroidManifest.xml
new file mode 100644
index 0000000..87e3081
--- /dev/null
+++ b/HiddenApiLoggingApp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.hiddenapiapp">
+ <!-- Add permission for changing DeviceConfig -->
+ <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+ <!-- Add permissions for StatsManager.addConfig -->
+ <uses-permission android:name="android.permission.DUMP" />
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+ <application
+ android:label="Hidden Api Logging Manager">
+ <receiver
+ android:name=".Receiver"
+ android:exported="true">
+ </receiver>
+ </application>
+</manifest>
diff --git a/HiddenApiLoggingApp/README b/HiddenApiLoggingApp/README
new file mode 100644
index 0000000..fe6af71
--- /dev/null
+++ b/HiddenApiLoggingApp/README
@@ -0,0 +1,12 @@
+HiddenApiLoggingApp.apk:
+
+Demonstrates how to obtain the hidden api logging data.
+Usage:
+1. mmma packages/experimental/HiddenApiLoggingApp -j
+2. adb install -r ${OUT}/system/app/HiddenApiLoggingApp/HiddenApiLoggingApp.apk
+3 ./out/host/linux-x86/bin/hidden_api_log_tracking
+4. <wait for the 'Waiting for hidden api accesses... Press Ctrl-c to read logs' prompt>
+5. <trigger hidden api usage>
+6. <press Ctrl-c to stop execution and read accumulated hidden api usage logs>
+
+Owner: Andrei Onea <andreionea@google.com>
diff --git a/HiddenApiLoggingApp/src/com/android/hiddenapiapp/Receiver.java b/HiddenApiLoggingApp/src/com/android/hiddenapiapp/Receiver.java
new file mode 100644
index 0000000..96e1172
--- /dev/null
+++ b/HiddenApiLoggingApp/src/com/android/hiddenapiapp/Receiver.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.hiddenapiapp;
+
+import android.app.StatsManager;
+import android.app.StatsManager.StatsUnavailableException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.DeviceConfig;
+import android.util.Log;
+import java.util.Base64;
+
+public class Receiver extends BroadcastReceiver {
+ public static final String SET_RATE = "com.android.hiddenapiapp.SET_RATE";
+ public static final String SET_CONFIG = "com.android.hiddenapiapp.SET_CONFIG";
+ public static final String GET_DATA = "com.android.hiddenapiapp.GET_DATA";
+ public static final String REMOVE_CONFIG = "com.android.hiddenapiapp.REMOVE_CONFIG";
+
+ // ID of the config to be sent to statsd; this is an arbitrary value - but, obviously,
+ // every config must have its own id (can be assigned either client or server side,
+ // depending on use case)
+ private static final long CONFIG_ID = 54321;
+ private static final String TAG = "HiddenApiLogging";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ StatsManager statsManager = (StatsManager) context.getSystemService(StatsManager.class);
+ switch (intent.getAction()) {
+ case SET_RATE:
+ setLogRate(intent.getIntExtra("rate", 0));
+ setResult(1, null, null);
+ break;
+ case SET_CONFIG:
+ String config = intent.getStringExtra("config");
+ if (setupConfig(Base64.getDecoder().decode(config), statsManager)) {
+ setResult(1, null, null);
+ } else {
+ setResult(2, null, null);
+ }
+ case GET_DATA:
+ // This is for demo purposes only; in real applications, this data would be accumulated
+ // periodically in a buffer, and sent when the buffer fills up or otherwise convenient
+ setResult(1, Base64.getEncoder().encodeToString(getData(statsManager)), null);
+ break;
+ case REMOVE_CONFIG:
+ if (removeConfig(statsManager)) {
+ setResult(1, null, null);
+ } else {
+ setResult(2, null, null);
+ }
+ default:
+ setResult(2, null, null);
+ }
+ }
+
+ private void setLogRate(int rate) {
+ DeviceConfig.setProperty(
+ "app_compat",
+ "hidden_api_access_statslog_sampling_rate",
+ new Integer(rate).toString(),
+ false);
+ }
+
+ private boolean setupConfig(byte[] config, StatsManager statsManager) {
+ if (statsManager == null) {
+ return false;
+ }
+ try {
+ statsManager.addConfig(CONFIG_ID, config);
+ return true;
+ } catch (StatsUnavailableException e) {
+ Log.d(TAG, "Failed to send config to statsd");
+ return false;
+ }
+ }
+
+ private byte[] getData(StatsManager statsManager) {
+ if (statsManager == null) {
+ return null;
+ }
+ try {
+ return statsManager.getReports(CONFIG_ID);
+ } catch (StatsUnavailableException e) {
+ return null;
+ }
+ }
+
+ private boolean removeConfig(StatsManager statsManager) {
+ if (statsManager == null) {
+ return false;
+ }
+ try {
+ statsManager.removeConfig(CONFIG_ID);
+ statsManager = null;
+ return true;
+ } catch (StatsUnavailableException e) {
+ Log.d(TAG, "Failed to remove config from statsd");
+ return false;
+ }
+ }
+}
diff --git a/HiddenApiLoggingApp/tool/hidden_api_log_tracking.mf b/HiddenApiLoggingApp/tool/hidden_api_log_tracking.mf
new file mode 100644
index 0000000..6249801
--- /dev/null
+++ b/HiddenApiLoggingApp/tool/hidden_api_log_tracking.mf
@@ -0,0 +1 @@
+Main-Class: com.hiddenapi.HiddenApiLogTracking
diff --git a/HiddenApiLoggingApp/tool/src/com/hiddenapi/HiddenApiLogTracking.java b/HiddenApiLoggingApp/tool/src/com/hiddenapi/HiddenApiLogTracking.java
new file mode 100644
index 0000000..75f0cd2
--- /dev/null
+++ b/HiddenApiLoggingApp/tool/src/com/hiddenapi/HiddenApiLogTracking.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.hiddenapi;
+
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.EventMetric;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Base64;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+// This tool represents an approximation of what a server would be doing
+// - except the directionality of the flow of data is reversed at times;
+// The device should send its data directly to the server when its internal
+// buffers are full or when otherwise convenient (e.g. connecting to Wi-fi)
+public class HiddenApiLogTracking {
+ private static String runOnDevice(String cmd) {
+ String[] finalCommand = ("adb shell " + cmd).split(" ");
+ System.out.println(String.join(" ", finalCommand));
+ ProcessBuilder pb = new ProcessBuilder(finalCommand);
+ Process process;
+ try {
+ process = pb.start();
+ } catch (IOException e) {
+ throw new RuntimeException("Could not start new process.", e);
+ }
+ StringBuilder output = new StringBuilder();
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+ for (String line = br.readLine(); line != null; line = br.readLine()) {
+ output.append(line).append('\n');
+ }
+ process.waitFor();
+ return output.toString();
+ } catch (Exception e) {
+ process.destroy();
+ throw new RuntimeException("Failed to read command output", e);
+ }
+ }
+
+ private static String createConfig() {
+ StatsdConfig.Builder builder = StatsdConfig.newBuilder().setId(54321);
+ final String atomName = "Atom" + System.nanoTime();
+ final String eventName = "Event" + System.nanoTime();
+
+ SimpleAtomMatcher.Builder sam =
+ SimpleAtomMatcher.newBuilder().setAtomId(Atom.HIDDEN_API_USED_FIELD_NUMBER);
+ builder.addAtomMatcher(
+ AtomMatcher.newBuilder().setId(atomName.hashCode()).setSimpleAtomMatcher(sam));
+ builder.addEventMetric(
+ EventMetric.newBuilder().setId(eventName.hashCode()).setWhat(atomName.hashCode()));
+ builder.addAllowedLogSource("AID_STATSD");
+ return Base64.getEncoder().encodeToString(builder.build().toByteArray());
+ }
+
+ private static void processData(String dataAsString) {
+ dataAsString = dataAsString.substring(1, dataAsString.length() - 1);
+ byte[] data = Base64.getDecoder().decode(dataAsString);
+ try {
+ ConfigMetricsReportList cmrl = ConfigMetricsReportList.parser().parseFrom(data);
+ String output =
+ cmrl.getReportsList().stream()
+ .flatMap(report -> report.getMetricsList().stream())
+ .flatMap(metric -> metric.getEventMetrics().getDataList().stream())
+ .map(eventMetric -> eventMetric.getAtom())
+ .map(atom -> atom.getHiddenApiUsed().getSignature())
+ .collect(Collectors.joining("\n"));
+ System.out.println("Used hidden apis");
+ System.out.println(output);
+ } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+ throw new RuntimeException("Cannot parse protocol buffer.", e);
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println("Setting rate to max rate");
+ runOnDevice(
+ "am broadcast -n com.android.hiddenapiapp/.Receiver "
+ + "-a com.android.hiddenapiapp.SET_RATE --ei rate 65535");
+ System.out.println("Sending config");
+ String config = createConfig();
+ runOnDevice(
+ "am broadcast -n com.android.hiddenapiapp/.Receiver "
+ + "-a com.android.hiddenapiapp.SET_CONFIG --es config "
+ + config);
+
+ System.out.println("Waiting for hidden api accesses... Press enter to read logs");
+
+ try {
+ System.in.read();
+ } catch (IOException e) {
+ System.err.println("There was an IO error");
+ e.printStackTrace(System.err);
+ System.err.println("Proceeding normally...");
+ }
+
+ String output =
+ runOnDevice(
+ "am broadcast -n com.android.hiddenapiapp/.Receiver "
+ + "-a com.android.hiddenapiapp.GET_DATA");
+ Pattern p = Pattern.compile("\"([^\"]+)\"");
+ Matcher m = p.matcher(output);
+ if (m.find()) {
+ processData(m.group(0));
+ } else {
+ System.out.println("No config received!");
+ }
+
+ runOnDevice(
+ "am broadcast -n com.android.hiddenapiapp/.Receiver "
+ + "-a com.android.hiddenapiapp.REMOVE_CONFIG");
+ }
+}