├── license.txt ├── readme.md └── src ├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── test │ │ └── accessibilitydatamonitoring │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── test │ │ │ └── accessibilitydatamonitoring │ │ │ ├── MainActivity.java │ │ │ ├── MyAccessibilityService.java │ │ │ └── UrlInfo.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── serviceconfig.xml │ └── test │ └── java │ └── com │ └── test │ └── accessibilitydatamonitoring │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2017 Apriorit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Technology to Access App Private Data on Android (No Root) 2 | 3 | ## About 4 | 5 | By default all Android apps save data in a so-called Sandbox – a storage area which other applications cannot access. This project provides a simple way to access private data of third-party applications without root permissions. 6 | 7 | WARNING: Android security policy directly states that access to private data can be illegal, which puts certain limitations on gathering such data, including the possibility of being prosecuted by law. Therefore, use this method to obtain data consciously and thoughtfully and make sure that you abide by laws and respect privacy of others. 8 | 9 | ## Implementation 10 | 11 | The main solution idea is to parse a layout tree of an active page and extract the necessary data. 12 | 13 | This solution works well starting with Android 4.3 and older (including Android N), but to get needed "clear" data you will need to take your time to study each target application. This code illustrates the approach for Firefox browser history on Android 6.0 and Skype instant messages. 14 | 15 | For detailed implementation notes and code details, please review the [related article](https://www.apriorit.com/dev-blog/429-access-app-data-on-android-no-root). 16 | 17 | ## License 18 | 19 | Licensed under the MIT license. © Apriorit. 20 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /src/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /src/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "com.test.accessibilitydatamonitoring" 8 | minSdkVersion 18 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 28 | testCompile 'junit:junit:4.12' 29 | } 30 | -------------------------------------------------------------------------------- /src/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Android\android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /src/app/src/androidTest/java/com/test/accessibilitydatamonitoring/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.test.accessibilitydatamonitoring; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.test.accessibilitydatamonitoring", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/app/src/main/java/com/test/accessibilitydatamonitoring/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.test.accessibilitydatamonitoring; 2 | 3 | import android.app.Activity; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.os.Bundle; 7 | import android.provider.Settings; 8 | import android.text.TextUtils; 9 | import android.text.method.ScrollingMovementMethod; 10 | import android.view.View; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | 14 | import java.text.SimpleDateFormat; 15 | import java.util.List; 16 | import java.util.Locale; 17 | 18 | import static com.test.accessibilitydatamonitoring.MyAccessibilityService.urls; 19 | 20 | public class MainActivity extends Activity { 21 | private TextView textView; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_main); 27 | textView = (TextView) findViewById(R.id.urls); 28 | textView.setMovementMethod(new ScrollingMovementMethod()); 29 | } 30 | 31 | public void clearUrls(View view) { 32 | urls.clear(); 33 | displayUrls(); 34 | } 35 | 36 | private void displayUrls() { 37 | textView.setText(getReadableUrlsInfo(urls)); 38 | } 39 | 40 | @Override 41 | protected void onResume() { 42 | super.onResume(); 43 | toastIfAccessibilityDisabled(); 44 | displayUrls(); 45 | } 46 | 47 | private void toastIfAccessibilityDisabled() { 48 | if (!isAccessibilityEnabled(this, MyAccessibilityService.class)) { 49 | Toast.makeText(this, "Enable accessibility in Settings -> Accessibility -> " + getString(R.string.accessibility_label), Toast.LENGTH_LONG).show(); 50 | } 51 | } 52 | 53 | public static boolean isAccessibilityEnabled(Context context, Class accessibilityService) { 54 | ComponentName expectedComponentName = new ComponentName(context, accessibilityService); 55 | 56 | String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 57 | if (enabledServicesSetting == null) 58 | return false; 59 | 60 | TextUtils.SimpleStringSplitter colonSplitter = new TextUtils.SimpleStringSplitter(':'); 61 | colonSplitter.setString(enabledServicesSetting); 62 | 63 | while (colonSplitter.hasNext()) { 64 | String componentNameString = colonSplitter.next(); 65 | ComponentName enabledService = ComponentName.unflattenFromString(componentNameString); 66 | 67 | if (enabledService != null && enabledService.equals(expectedComponentName)) 68 | return true; 69 | } 70 | 71 | return false; 72 | } 73 | 74 | private String getReadableUrlsInfo(List urls) { 75 | String result = ""; 76 | if (urls.isEmpty()) { 77 | result = "NO ANY URLS..."; 78 | } else { 79 | for (UrlInfo url : urls) { 80 | result += new SimpleDateFormat("hh:mm:ss.SSS", Locale.getDefault()).format(url.time) + " " + url.name 81 | + "\r\n" 82 | + "\r\n"; 83 | } 84 | } 85 | 86 | return result; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/app/src/main/java/com/test/accessibilitydatamonitoring/MyAccessibilityService.java: -------------------------------------------------------------------------------- 1 | package com.test.accessibilitydatamonitoring; 2 | 3 | import android.accessibilityservice.AccessibilityService; 4 | import android.util.Log; 5 | import android.util.Patterns; 6 | import android.view.accessibility.AccessibilityEvent; 7 | import android.view.accessibility.AccessibilityNodeInfo; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class MyAccessibilityService extends AccessibilityService { 13 | 14 | public static final String TAG = "MONITORING"; 15 | public static List urls = new ArrayList<>(); 16 | 17 | @Override 18 | public void onAccessibilityEvent(AccessibilityEvent event) { 19 | CharSequence eventPackageName = event.getPackageName(); 20 | if (eventPackageName == null) { 21 | return; 22 | } 23 | 24 | 25 | printEventInfo(event); 26 | printNodeTreeDepth(event.getSource()); 27 | 28 | int eventType = event.getEventType(); 29 | if (eventPackageName.equals("org.mozilla.firefox")) { 30 | if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED 31 | || eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 32 | searchFirefoxUrlBreadth(event.getSource()); 33 | } 34 | } 35 | } 36 | 37 | @Override 38 | public void onInterrupt() { 39 | 40 | } 41 | 42 | void printEventInfo(AccessibilityEvent event) { 43 | Log.d(TAG, "EventType=" + AccessibilityEvent.eventTypeToString(event.getEventType()) + ", package=" + event.getPackageName()); 44 | } 45 | 46 | private static void printNodeTreeDepth(AccessibilityNodeInfo nodeInfo) { 47 | depthFirstSearchNodeProcessing(nodeInfo, 0); 48 | } 49 | 50 | public static void depthFirstSearchNodeProcessing(AccessibilityNodeInfo nodeInfo, int depth) { 51 | if (nodeInfo == null) { 52 | return; 53 | } 54 | 55 | Log.d(TAG, depth + "__" + nodeInfo.getClassName() + ", " + nodeInfo.getViewIdResourceName() + ", " + nodeInfo.getText()); 56 | 57 | for (int i = 0; i < nodeInfo.getChildCount(); i++) { 58 | depthFirstSearchNodeProcessing(nodeInfo.getChild(i), depth + 1); 59 | } 60 | } 61 | 62 | private void searchFirefoxUrlBreadth(AccessibilityNodeInfo nodeInfo) { 63 | if (nodeInfo == null) { 64 | return; 65 | } 66 | List unvisited = new ArrayList<>(); 67 | unvisited.add(nodeInfo); 68 | 69 | while (!unvisited.isEmpty()) { 70 | AccessibilityNodeInfo node = unvisited.remove(0); 71 | 72 | if (node == null) { 73 | continue; 74 | } 75 | 76 | if (firefoxUrlFound(node)) { 77 | return; 78 | } 79 | 80 | for (int i = 0; i < node.getChildCount(); i++) { 81 | unvisited.add(node.getChild(i)); 82 | } 83 | } 84 | } 85 | 86 | private boolean firefoxUrlFound(AccessibilityNodeInfo nodeInfo) { 87 | if ("android.widget.TextView".equals(nodeInfo.getClassName()) 88 | && "org.mozilla.firefox:id/url_bar_title".equals(nodeInfo.getViewIdResourceName()) 89 | && nodeInfo.getText() != null) { 90 | saveUrl(nodeInfo.getText().toString()); 91 | return true; 92 | } 93 | return false; 94 | } 95 | 96 | private void saveUrl(String visibleUrl) { 97 | // skip if url is not valid 98 | if (!Patterns.WEB_URL.matcher(visibleUrl).matches()) { 99 | return; 100 | } 101 | 102 | // skip if already saved 103 | String lastAccessedUrl = urls.isEmpty() ? null : urls.get(urls.size() - 1).name; 104 | if (!visibleUrl.equals(lastAccessedUrl)) { 105 | urls.add(new UrlInfo(System.currentTimeMillis(), visibleUrl)); 106 | Log.d(TAG, "Firefox url added:" + visibleUrl); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/app/src/main/java/com/test/accessibilitydatamonitoring/UrlInfo.java: -------------------------------------------------------------------------------- 1 | package com.test.accessibilitydatamonitoring; 2 | 3 | class UrlInfo { 4 | long time; 5 | String name; 6 | 7 | UrlInfo(long time, String url) { 8 | this.time = time; 9 | this.name = url; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 |