diff options
author | Mathew Inwood <mathewi@google.com> | 2020-01-06 16:39:22 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-01-06 16:39:22 +0000 |
commit | 87332125be2be864bd423fa6cc6a4b9ef604e640 (patch) | |
tree | da3aadf478563a266cc45e05c10075d4360a83e9 | |
parent | f24b408a258fd197434e5c278b1203968327bc05 (diff) | |
parent | 680846486608774b86b34e0d70571d6ab15d66cd (diff) | |
download | experimental-main.tar.gz |
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 Binary files differnew file mode 100644 index 0000000..a2f5908 --- /dev/null +++ b/HiddenApiTestApp/res/mipmap-hdpi/ic_launcher.png diff --git a/HiddenApiTestApp/res/mipmap-hdpi/ic_launcher_round.png b/HiddenApiTestApp/res/mipmap-hdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..1b52399 --- /dev/null +++ b/HiddenApiTestApp/res/mipmap-hdpi/ic_launcher_round.png diff --git a/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher.png b/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..ff10afd --- /dev/null +++ b/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher.png diff --git a/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher_round.png b/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..115a4c7 --- /dev/null +++ b/HiddenApiTestApp/res/mipmap-mdpi/ic_launcher_round.png diff --git a/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher.png b/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..dcd3cd8 --- /dev/null +++ b/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher.png diff --git a/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher_round.png b/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..459ca60 --- /dev/null +++ b/HiddenApiTestApp/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher.png b/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..8ca12fe --- /dev/null +++ b/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher.png diff --git a/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher_round.png b/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..8e19b41 --- /dev/null +++ b/HiddenApiTestApp/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher.png b/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..b824ebd --- /dev/null +++ b/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher_round.png b/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..4c19a13 --- /dev/null +++ b/HiddenApiTestApp/res/mipmap-xxxhdpi/ic_launcher_round.png 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> |