aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathew Inwood <mathewi@google.com>2020-01-06 16:39:22 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-01-06 16:39:22 +0000
commit87332125be2be864bd423fa6cc6a4b9ef604e640 (patch)
treeda3aadf478563a266cc45e05c10075d4360a83e9
parentf24b408a258fd197434e5c278b1203968327bc05 (diff)
parent680846486608774b86b34e0d70571d6ab15d66cd (diff)
downloadexperimental-87332125be2be864bd423fa6cc6a4b9ef604e640.tar.gz
Merge "Add HiddenApiTestApp."HEADmastermain
-rw-r--r--HiddenApiTestApp/Android.bp30
-rw-r--r--HiddenApiTestApp/AndroidManifest.xml47
-rw-r--r--HiddenApiTestApp/README4
-rw-r--r--HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexField.java12
-rw-r--r--HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexMember.java74
-rw-r--r--HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexMethod.java131
-rw-r--r--HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/MainActivity.java175
-rw-r--r--HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/MainInstrumentation.java28
-rw-r--r--HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/NoChecksActivity.java69
-rw-r--r--HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/ReflectReceiver.java99
-rw-r--r--HiddenApiTestApp/javatests/AndroidManifest.xml12
-rw-r--r--HiddenApiTestApp/javatests/com/google/android/hiddenapi/testapp/tests/TestInstrumentation.java28
-rw-r--r--HiddenApiTestApp/res/drawable-v24/ic_launcher_foreground.xml34
-rw-r--r--HiddenApiTestApp/res/drawable/ic_launcher_background.xml171
-rw-r--r--HiddenApiTestApp/res/layout/activity_main.xml94
-rw-r--r--HiddenApiTestApp/res/layout/activity_nochecks.xml16
-rw-r--r--HiddenApiTestApp/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--HiddenApiTestApp/res/mipmap-anydpi-v26/ic_launcher_round.xml5
-rw-r--r--HiddenApiTestApp/res/mipmap-hdpi/ic_launcher.pngbin0 -> 3056 bytes
-rw-r--r--HiddenApiTestApp/res/mipmap-hdpi/ic_launcher_round.pngbin0 -> 5024 bytes
-rw-r--r--HiddenApiTestApp/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2096 bytes
-rw-r--r--HiddenApiTestApp/res/mipmap-mdpi/ic_launcher_round.pngbin0 -> 2858 bytes
-rw-r--r--HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 4569 bytes
-rw-r--r--HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher_round.pngbin0 -> 7098 bytes
-rw-r--r--HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 6464 bytes
-rw-r--r--HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher_round.pngbin0 -> 10676 bytes
-rw-r--r--HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 9250 bytes
-rw-r--r--HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher_round.pngbin0 -> 15523 bytes
-rw-r--r--HiddenApiTestApp/res/values/colors.xml6
-rw-r--r--HiddenApiTestApp/res/values/strings.xml4
-rw-r--r--HiddenApiTestApp/res/values/styles.xml3
31 files changed, 1047 insertions, 0 deletions
diff --git a/HiddenApiTestApp/Android.bp b/HiddenApiTestApp/Android.bp
new file mode 100644
index 0000000..91a5cf7
--- /dev/null
+++ b/HiddenApiTestApp/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 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: "HiddenApiTestApp",
+ platform_apis: true,
+ srcs: ["java/**/*.java"],
+ resource_dirs: ["res"],
+ min_sdk_version: "27",
+}
+
+android_test {
+ name: "HiddenApiTestAppTests",
+ srcs: ["javatests/**/*.java"],
+ manifest: "javatests/AndroidManifest.xml",
+ instrumentation_for: "HiddenApiTestApp",
+ sdk_version: "26",
+ min_sdk_version: "21",
+}
diff --git a/HiddenApiTestApp/AndroidManifest.xml b/HiddenApiTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..271020a
--- /dev/null
+++ b/HiddenApiTestApp/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.hiddenapi.testapp">
+
+ <application
+ android:allowBackup="true"
+ android:debuggable="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true">
+ <activity android:name=".MainActivity"
+ android:process="hiddenapi.testappprocess" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".NoChecksActivity"
+ android:label="@string/disable_checks_activity_name"
+ android:process=":disablechecks" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <receiver
+ android:name=".ReflectReceiver"
+ android:exported="true" />
+
+ <service
+ android:name=".IsolatedService"
+ android:isolatedProcess="true" />
+
+ </application>
+
+ <!-- Note this doesn't work, as it is a signature permission: -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+ <instrumentation
+ android:name=".MainInstrumentation"
+ android:targetPackage="com.google.android.hiddenapi.testapp" />
+
+</manifest>
diff --git a/HiddenApiTestApp/README b/HiddenApiTestApp/README
new file mode 100644
index 0000000..0cf6c1b
--- /dev/null
+++ b/HiddenApiTestApp/README
@@ -0,0 +1,4 @@
+HiddenApiTestApp.apk:
+
+Exercises various ways of accessing hidden APIs, ways of
+disabling the checks.
diff --git a/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexField.java b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexField.java
new file mode 100644
index 0000000..7397c52
--- /dev/null
+++ b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexField.java
@@ -0,0 +1,12 @@
+package com.google.android.hiddenapi.testapp;
+
+public class DexField extends DexMember {
+ public DexField(String className, String name, String type) {
+ super(className, name, type);
+ }
+
+ @Override
+ public String toString() {
+ return getJavaType() + " " + getJavaClassName() + "." + getName();
+ }
+}
diff --git a/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexMember.java b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexMember.java
new file mode 100644
index 0000000..b26c939
--- /dev/null
+++ b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexMember.java
@@ -0,0 +1,74 @@
+package com.google.android.hiddenapi.testapp;
+
+/**
+ * Represents one class member parsed from the reader of dex signatures.
+ */
+public abstract class DexMember {
+ private final String mName;
+ private final String mClassDescriptor;
+ private final String mType;
+
+ protected DexMember(String className, String name, String type) {
+ mName = name;
+ mClassDescriptor = className;
+ mType = type;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getDexClassName() {
+ return mClassDescriptor;
+ }
+
+ public String getJavaClassName() {
+ return dexToJavaType(mClassDescriptor);
+ }
+
+ public String getDexType() {
+ return mType;
+ }
+
+ public String getJavaType() {
+ return dexToJavaType(mType);
+ }
+
+ /**
+ * Converts `type` to a Java type.
+ */
+ public static String dexToJavaType(String type) {
+ String javaDimension = "";
+ while (type.startsWith("[")) {
+ javaDimension += "[]";
+ type = type.substring(1);
+ }
+
+ String javaType = null;
+ if ("V".equals(type)) {
+ javaType = "void";
+ } else if ("Z".equals(type)) {
+ javaType = "boolean";
+ } else if ("B".equals(type)) {
+ javaType = "byte";
+ } else if ("C".equals(type)) {
+ javaType = "char";
+ } else if ("S".equals(type)) {
+ javaType = "short";
+ } else if ("I".equals(type)) {
+ javaType = "int";
+ } else if ("J".equals(type)) {
+ javaType = "long";
+ } else if ("F".equals(type)) {
+ javaType = "float";
+ } else if ("D".equals(type)) {
+ javaType = "double";
+ } else if (type.startsWith("L") && type.endsWith(";")) {
+ javaType = type.substring(1, type.length() - 1).replace('/', '.');
+ } else {
+ throw new IllegalStateException("Unexpected type " + type);
+ }
+
+ return javaType + javaDimension;
+ }
+}
diff --git a/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexMethod.java b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexMethod.java
new file mode 100644
index 0000000..5cd7328
--- /dev/null
+++ b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/DexMethod.java
@@ -0,0 +1,131 @@
+package com.google.android.hiddenapi.testapp;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class DexMethod extends DexMember {
+ private final List<String> mParamTypeList;
+
+ public DexMethod(String className, String name, String signature) {
+ super(className, name, parseDexReturnType(signature));
+ mParamTypeList = parseDexTypeList(signature);
+ }
+
+ public String getDexSignature() {
+ return "(" + String.join("", mParamTypeList) + ")" + getDexType();
+ }
+
+ public List<String> getJavaParameterTypes() {
+ return mParamTypeList.stream().map(DexMember::dexToJavaType).collect(Collectors.toList());
+ }
+
+ public boolean isConstructor() {
+ return "<init>".equals(getName()) && "V".equals(getDexType());
+ }
+
+ public boolean isStaticConstructor() {
+ return "<clinit>".equals(getName()) && "V".equals(getDexType());
+ }
+
+ @Override
+ public String toString() {
+ return getJavaType() + " " + getJavaClassName() + "." + getName()
+ + "(" + String.join(", ", getJavaParameterTypes()) + ")";
+ }
+
+ public static Class<?> getTypeClass(String type) throws Exception {
+ switch (type) {
+ case "V":
+ return Void.TYPE;
+ case "Z":
+ return Boolean.TYPE;
+ case "B":
+ return Byte.TYPE;
+ case "C":
+ return Character.TYPE;
+ case "S":
+ return Short.TYPE;
+ case "I":
+ return Integer.TYPE;
+ case "J":
+ return Long.TYPE;
+ case "F":
+ return Float.TYPE;
+ case "D":
+ return Double.TYPE;
+ default:
+ if (type.startsWith("L")) {
+ return Class.forName(dexToJavaType(type));
+ } else {
+ throw new IllegalArgumentException("Unknown type: " + type);
+ }
+
+ }
+ }
+
+ public Class<?>[] getParameterTypes() throws Exception {
+ Class<?>[] types = new Class<?>[mParamTypeList.size()];
+ for (int i = 0; i < mParamTypeList.size(); ++i) {
+ String type = mParamTypeList.get(i);
+ types[i] = getTypeClass(type);
+ }
+ return types;
+ }
+
+ private static Matcher matchSignature(String signature) {
+ Matcher m = Pattern.compile("^\\((.*)\\)(.*)$").matcher(signature);
+ if (!m.matches()) {
+ throw new RuntimeException("Could not parse method signature: " + signature);
+ }
+ return m;
+ }
+
+ private static String parseDexReturnType(String signature) {
+ return matchSignature(signature).group(2);
+ }
+
+ private static List<String> parseDexTypeList(String signature) {
+ String typeSequence = matchSignature(signature).group(1);
+ List<String> list = new ArrayList<String>();
+ while (!typeSequence.isEmpty()) {
+ String type = firstDexTypeFromList(typeSequence);
+ list.add(type);
+ typeSequence = typeSequence.substring(type.length());
+ }
+ return list;
+ }
+
+ /**
+ * Returns the first dex type in `typeList` or throws a ParserException
+ * if a dex type is not recognized. The input is not changed.
+ */
+ private static String firstDexTypeFromList(String typeList) {
+ String dexDimension = "";
+ while (typeList.startsWith("[")) {
+ dexDimension += "[";
+ typeList = typeList.substring(1);
+ }
+
+ String type = null;
+ if (typeList.startsWith("V")
+ || typeList.startsWith("Z")
+ || typeList.startsWith("B")
+ || typeList.startsWith("C")
+ || typeList.startsWith("S")
+ || typeList.startsWith("I")
+ || typeList.startsWith("J")
+ || typeList.startsWith("F")
+ || typeList.startsWith("D")) {
+ type = typeList.substring(0, 1);
+ } else if (typeList.startsWith("L") && typeList.indexOf(";") > 0) {
+ type = typeList.substring(0, typeList.indexOf(";") + 1);
+ } else {
+ throw new RuntimeException("Unexpected dex type in \"" + typeList + "\"");
+ }
+
+ return dexDimension + type;
+ }
+}
diff --git a/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/MainActivity.java b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/MainActivity.java
new file mode 100644
index 0000000..af2e174
--- /dev/null
+++ b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/MainActivity.java
@@ -0,0 +1,175 @@
+package com.google.android.hiddenapi.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.StrictMode;
+import android.os.strictmode.Violation;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import dalvik.system.VMRuntime;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class MainActivity extends Activity implements OnClickListener {
+
+ enum AppStrictMode {
+ OFF,
+ LOG,
+ DEATH,
+ CALLBACK;
+ private static AppStrictMode[] vals = values();
+ public AppStrictMode next()
+ {
+ return vals[(this.ordinal()+1) % vals.length];
+ }
+ }
+
+ private final StrictMode.OnVmViolationListener mViolationListener = (v) -> onVmViolation(v);
+
+ private TextView mTextView;
+ private TextView mCurrentStrictMode;
+ private TextView mStrictModeText;
+
+ private AppStrictMode mCurrentMode = AppStrictMode.OFF;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ mTextView = findViewById(R.id.textView);
+ mCurrentStrictMode = findViewById(R.id.strict_mode);
+ mStrictModeText = findViewById(R.id.strict_mode_text);
+
+ setAllButtonClickListeners(findViewById(R.id.layout));
+
+ setStrictMode(AppStrictMode.OFF);
+ }
+
+ private void setAllButtonClickListeners(View v) {
+ if (v instanceof Button) {
+ v.setOnClickListener(this);
+ } else if (v instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) v;
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ setAllButtonClickListeners(vg.getChildAt(i));
+ }
+ }
+ }
+
+ private void onVmViolation(Violation v) {
+ setStrictModeText(toString(v));
+ }
+
+ private void setStrictModeText(String s) {
+ if (TextUtils.isEmpty(s)) {
+ mStrictModeText.setText("");
+ mStrictModeText.setVisibility(View.GONE);
+ } else {
+ mStrictModeText.setText(s);
+ mStrictModeText.setVisibility(View.VISIBLE);
+ }
+
+ }
+ private void setStrictMode(AppStrictMode mode) {
+ StrictMode.VmPolicy policy;
+ switch (mode) {
+ case OFF:
+ policy = new StrictMode.VmPolicy.Builder().permitNonSdkApiUsage().build();
+ break;
+ case LOG:
+ policy = new StrictMode.VmPolicy.Builder().detectNonSdkApiUsage().penaltyLog().build();
+ break;
+ case DEATH:
+ policy = new StrictMode.VmPolicy.Builder().detectNonSdkApiUsage().penaltyDeath().build();
+ break;
+ case CALLBACK:
+ policy = new StrictMode.VmPolicy.Builder()
+ .detectNonSdkApiUsage()
+ .penaltyListener(r -> mStrictModeText.post(r), mViolationListener)
+ .build();
+ break;
+ default:
+ return;
+ }
+ mCurrentMode = mode;
+ StrictMode.setVmPolicy(policy);
+ mCurrentStrictMode.setText("StrictMode: " + mode);
+ }
+
+ @Override
+ public void onClick(View view) {
+ setStrictModeText(null);
+ switch (view.getId()) {
+ case R.id.disable_checks_reflection:
+ try {
+ tryDisableApiChecksReflectively();
+ mTextView.setText("Ok \uD83D\uDE00");
+ } catch (Exception e) {
+ handleException(e);
+ }
+ break;
+ case R.id.disable_checks_linking:
+ try {
+ tryDisableApiChecksLinking();
+ } catch (LinkageError e) {
+ handleException(e);
+ }
+ break;
+ case R.id.strict_mode:
+ setStrictMode(mCurrentMode.next());
+ break;
+ default:
+ String name = ((TextView) view).getText().toString();
+ accessActivityMember(name);
+ break;
+ }
+ }
+
+ private void accessActivityMember(String name) {
+ try {
+ Field f = Activity.class.getDeclaredField(name);
+ f.setAccessible(true);
+ Object o = f.get(this);
+ mTextView.setText(String.format("%s: %s", name, o == null ? "null" : o.toString()));
+ } catch (NoSuchFieldException e) {
+ handleException(e);
+ } catch (IllegalAccessException e) {
+ handleException(e);
+ }
+ }
+
+ private void handleException(Throwable e) {
+ Log.e(getClass().getSimpleName(), "Failed", e);
+ mTextView.setText(toString(e));
+ }
+
+ private static String toString(Throwable t) {
+ StringWriter s = new StringWriter();
+ t.printStackTrace(new PrintWriter(s));
+ return s.toString();
+ }
+
+ private static void tryDisableApiChecksReflectively()
+ throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
+ IllegalAccessException {
+ Class vmRuntimeClass = Class.forName("dalvik.system.VMRuntime");
+ Method getRuntime = vmRuntimeClass.getDeclaredMethod("getRuntime");
+ Object vmRuntime = getRuntime.invoke(null);
+ Method setExemptions = vmRuntimeClass.getDeclaredMethod(
+ "setHiddenApiExemptions", String[].class);
+ setExemptions.invoke(vmRuntime, (Object) new String[]{"L"});
+ }
+
+ private static void tryDisableApiChecksLinking() {
+ VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{"L"});
+ }
+
+}
diff --git a/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/MainInstrumentation.java b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/MainInstrumentation.java
new file mode 100644
index 0000000..b1b75ac
--- /dev/null
+++ b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/MainInstrumentation.java
@@ -0,0 +1,28 @@
+package com.google.android.hiddenapi.testapp;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Looper;
+import android.util.Log;
+
+public class MainInstrumentation extends Instrumentation {
+
+ private static final String TAG = "compat-test";
+
+ @Override
+ public void onCreate(Bundle args) {
+ super.onCreate(args);
+ Log.d(TAG, "MainInstrumentation.onCreate()");
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ Log.d(TAG, "MainInstrumentation.onStart()");
+ Intent i = new Intent(getTargetContext(), MainActivity.class);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(i);
+ Log.d(TAG, "Started Activity");
+ }
+}
diff --git a/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/NoChecksActivity.java b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/NoChecksActivity.java
new file mode 100644
index 0000000..39f972e
--- /dev/null
+++ b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/NoChecksActivity.java
@@ -0,0 +1,69 @@
+package com.google.android.hiddenapi.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+
+public class NoChecksActivity extends Activity {
+
+ public static String TAG = "compat-test";
+
+ private TextView mTextView;
+ private final StringBuilder mText = new StringBuilder();
+ private final Runnable mUpdateRunnable = () -> mTextView.setText(mText);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_nochecks);
+ mTextView = (TextView) findViewById(R.id.textView);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ try {
+ Process am = Runtime.getRuntime().exec(
+ "am instrument --no-hidden-api-checks mypackage/.MainInstrumentation");
+ new ReaderThread(am.getErrorStream()).start();
+ new ReaderThread(am.getInputStream()).start();
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ // TODO get proc output, display in text box
+ }
+
+ public synchronized void logLine(String line) {
+ if (line == null) {
+ return;
+ }
+ Log.d(TAG, line);
+ mText.append(line);
+ mTextView.post(mUpdateRunnable);
+ }
+
+ class ReaderThread extends Thread {
+ private final BufferedReader mInput;
+ public ReaderThread(InputStream in) {
+ mInput = new BufferedReader(new InputStreamReader(in));
+ }
+
+ public void run() {
+ try {
+ String line;
+ do {
+ line = mInput.readLine();
+ logLine(line);
+ } while (line != null);
+ } catch (IOException e) {
+ Log.e(TAG, "ReaderThread threw", e);
+ }
+ }
+ }
+
+}
diff --git a/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/ReflectReceiver.java b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/ReflectReceiver.java
new file mode 100644
index 0000000..8ae32e3
--- /dev/null
+++ b/HiddenApiTestApp/java/com/google/android/hiddenapi/testapp/ReflectReceiver.java
@@ -0,0 +1,99 @@
+package com.google.android.hiddenapi.testapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class ReflectReceiver extends BroadcastReceiver {
+
+ public static final String EXTRA_CLASS = "class";
+ public static final String EXTRA_SIGNATURE = "signature";
+
+ private static final Pattern REGEX_CLASS = Pattern.compile("^L[^->]*;$");
+ private static final Pattern REGEX_FIELD = Pattern.compile("^(L[^->]*;)->(.*):(.*)$");
+ private static final Pattern REGEX_METHOD = Pattern.compile("^(L[^->]*;)->(.*)(\\(.*\\).*)$");
+
+ public ReflectReceiver() {
+ try {
+ lookupSignature(
+ "Landroid/bluetooth/BluetoothAdapter;->setDiscoverableTimeout(I)V");
+ Log.d(getClass().getSimpleName(), "Successfully accessed method!");
+ } catch (Exception e) {
+ Log.e(getClass().getSimpleName(), "Failed", e);
+ }
+ }
+
+ private void lookupClass(String className) throws Exception {
+ Class<?> clazz= Class.forName(className);
+ setResult(0, "Got class: " + clazz, null);
+ }
+
+ private void lookupSignature(String signature) throws Exception {
+ Matcher matchClass = REGEX_CLASS.matcher(signature);
+ Matcher matchField = REGEX_FIELD.matcher(signature);
+ Matcher matchMethod = REGEX_METHOD.matcher(signature);
+ int matchCount = (matchClass.matches() ? 1 : 0) + (matchField.matches() ? 1 : 0) +
+ (matchMethod.matches() ? 1 : 0);
+ if (matchCount == 0) {
+ setResult(1, "Failed to parse signature: " + signature, null);
+ return;
+ } else if (matchCount > 1) {
+ setResult(1, "Ambiguous signature: " + signature, null);
+ }
+
+ if (matchClass.matches()) {
+ String className = DexMember.dexToJavaType(signature);
+ lookupClass(className);
+ return;
+ } else if (matchField.matches()) {
+ DexField dexField = new DexField(
+ matchField.group(1), matchField.group(2), matchField.group(3));
+ Class<?> clazz = Class.forName(dexField.getJavaClassName());
+ Field field = clazz.getDeclaredField(dexField.getName());
+ setResult(0, "Got field " + field, null);
+ } else if (matchMethod.matches()) {
+ DexMethod dexMethod = new DexMethod(
+ matchMethod.group(1),matchMethod.group(2), matchMethod.group(3));
+ Class<?> clazz = Class.forName(dexMethod.getJavaClassName());
+ Executable method;
+ if (dexMethod.isConstructor()) {
+ method = clazz.getConstructor(dexMethod.getParameterTypes());
+ } else {
+ method = clazz.getMethod(dexMethod.getName(), dexMethod.getParameterTypes());
+ }
+ setResult(0, "Got method " + method, null);
+ }
+
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ String className = intent.getStringExtra(EXTRA_CLASS);
+ if (className != null) {
+ lookupClass(className);
+ return;
+ }
+ String signature = intent.getStringExtra(EXTRA_SIGNATURE);
+ if (signature != null) {
+ lookupSignature(signature);
+ return;
+ }
+
+ setResult(1, "No action specified", null);
+ } catch (Exception e) {
+ StringWriter stack = new StringWriter();
+ e.printStackTrace(new PrintWriter(stack));
+ setResult(1, stack.toString(), null);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/HiddenApiTestApp/javatests/AndroidManifest.xml b/HiddenApiTestApp/javatests/AndroidManifest.xml
new file mode 100644
index 0000000..413543e
--- /dev/null
+++ b/HiddenApiTestApp/javatests/AndroidManifest.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.hiddenapi.testapp.tests">
+
+ <application >
+ </application>
+
+ <instrumentation
+ android:name=".TestInstrumentation"
+ android:targetPackage="com.google.android.hiddenapi.testapp" />
+
+</manifest>
diff --git a/HiddenApiTestApp/javatests/com/google/android/hiddenapi/testapp/tests/TestInstrumentation.java b/HiddenApiTestApp/javatests/com/google/android/hiddenapi/testapp/tests/TestInstrumentation.java
new file mode 100644
index 0000000..0b78198
--- /dev/null
+++ b/HiddenApiTestApp/javatests/com/google/android/hiddenapi/testapp/tests/TestInstrumentation.java
@@ -0,0 +1,28 @@
+package com.google.android.hiddenapi.testapp.tests;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import com.google.android.hiddenapi.testapp.MainActivity;
+
+public class TestInstrumentation extends Instrumentation {
+
+ private static final String TAG = "compat-test";
+
+ @Override
+ public void onCreate(Bundle args) {
+ super.onCreate(args);
+ Log.d(TAG, "MainInstrumentation.onCreate()");
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ Log.d(TAG, "MainInstrumentation.onStart()");
+ Intent i = new Intent(getTargetContext(), MainActivity.class);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(i);
+ Log.d(TAG, "Started Activity");
+ }
+}
diff --git a/HiddenApiTestApp/res/drawable-v24/ic_launcher_foreground.xml b/HiddenApiTestApp/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..b1517ed
--- /dev/null
+++ b/HiddenApiTestApp/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportHeight="108"
+ android:viewportWidth="108">
+ <path
+ android:fillType="evenOdd"
+ android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="78.5885"
+ android:endY="90.9159"
+ android:startX="48.7653"
+ android:startY="61.0927"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0"/>
+ <item
+ android:color="#00000000"
+ android:offset="1.0"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/HiddenApiTestApp/res/drawable/ic_launcher_background.xml b/HiddenApiTestApp/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..88e3187
--- /dev/null
+++ b/HiddenApiTestApp/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportHeight="108"
+ android:viewportWidth="108">
+ <path
+ android:fillColor="#26A69A"
+ android:pathData="M0,0h108v108h-108z"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeColor="#33FFFFFF"
+ android:strokeWidth="0.8"/>
+</vector>
diff --git a/HiddenApiTestApp/res/layout/activity_main.xml b/HiddenApiTestApp/res/layout/activity_main.xml
new file mode 100644
index 0000000..093ae5d
--- /dev/null
+++ b/HiddenApiTestApp/res/layout/activity_main.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true">
+
+ <LinearLayout
+ android:id="@+id/layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/strict_mode"
+ android:text="StrictMode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/strict_mode_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:text=""/>
+
+ <GridLayout
+ android:id="@+id/grid_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:columnCount="2"
+ android:rowCount="3"
+ android:orientation="horizontal" >
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill"
+ android:layout_columnWeight="1"
+ android:text="mWindow"/>
+
+ <Button
+ android:id="@+id/button2"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill"
+ android:layout_columnWeight="1"
+ android:text="mCalled"/>
+
+ <Button
+ android:id="@+id/button3"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill"
+ android:layout_columnWeight="1"
+ android:text="mDoReportFullyDrawn"/>
+
+ <Button
+ android:id="@+id/button3"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill"
+ android:layout_columnWeight="1"
+ android:text="mContext"/>
+
+ <Button
+ android:id="@+id/disable_checks_reflection"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill"
+ android:layout_columnWeight="1"
+ android:text="Disable API checks (reflection)"/>
+
+ <Button
+ android:id="@+id/disable_checks_linking"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill"
+ android:layout_columnWeight="1"
+ android:text="Disable API checks (linking)"/>
+
+ </GridLayout>
+
+
+ <TextView
+ android:id="@+id/textView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Click a button!"/>
+
+ </LinearLayout>
+</ScrollView> \ No newline at end of file
diff --git a/HiddenApiTestApp/res/layout/activity_nochecks.xml b/HiddenApiTestApp/res/layout/activity_nochecks.xml
new file mode 100644
index 0000000..c274c53
--- /dev/null
+++ b/HiddenApiTestApp/res/layout/activity_nochecks.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/textView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text=""/>
+
+</LinearLayout>
diff --git a/HiddenApiTestApp/res/mipmap-anydpi-v26/ic_launcher.xml b/HiddenApiTestApp/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6d5e5d0
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background"/>
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon> \ No newline at end of file
diff --git a/HiddenApiTestApp/res/mipmap-anydpi-v26/ic_launcher_round.xml b/HiddenApiTestApp/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6d5e5d0
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background"/>
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon> \ No newline at end of file
diff --git a/HiddenApiTestApp/res/mipmap-hdpi/ic_launcher.png b/HiddenApiTestApp/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a2f5908
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/HiddenApiTestApp/res/mipmap-hdpi/ic_launcher_round.png b/HiddenApiTestApp/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..1b52399
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher.png b/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..ff10afd
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher_round.png b/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..115a4c7
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher.png b/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..dcd3cd8
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher_round.png b/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..459ca60
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher.png b/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..8ca12fe
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher_round.png b/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..8e19b41
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher.png b/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b824ebd
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher_round.png b/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4c19a13
--- /dev/null
+++ b/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/HiddenApiTestApp/res/values/colors.xml b/HiddenApiTestApp/res/values/colors.xml
new file mode 100644
index 0000000..5a077b3
--- /dev/null
+++ b/HiddenApiTestApp/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/HiddenApiTestApp/res/values/strings.xml b/HiddenApiTestApp/res/values/strings.xml
new file mode 100644
index 0000000..1296fc4
--- /dev/null
+++ b/HiddenApiTestApp/res/values/strings.xml
@@ -0,0 +1,4 @@
+<resources>
+ <string name="app_name">TestApp</string>
+ <string name="disable_checks_activity_name">TestApp (disable API check)</string>
+</resources>
diff --git a/HiddenApiTestApp/res/values/styles.xml b/HiddenApiTestApp/res/values/styles.xml
new file mode 100644
index 0000000..f11f745
--- /dev/null
+++ b/HiddenApiTestApp/res/values/styles.xml
@@ -0,0 +1,3 @@
+<resources>
+
+</resources>