Repository: google/android-classyshark Branch: master Commit: 78ddba71b0d8 Files: 172 Total size: 550.5 KB Directory structure: gitextract__6u5jl00/ ├── .gitignore ├── CONTRIB.md ├── ClassySharkAndroid/ │ ├── .gitignore │ ├── ClassySharkAndroid.iml │ ├── app/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ ├── androidTest/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── google/ │ │ │ └── classysharkandroid/ │ │ │ └── activities/ │ │ │ └── classysharkandroid/ │ │ │ └── ApplicationTest.java │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets/ │ │ │ │ ├── prettify.css │ │ │ │ ├── prettify.js │ │ │ │ ├── run_prettify.js │ │ │ │ └── sons-of-obsidian.css │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── google/ │ │ │ │ └── classysharkandroid/ │ │ │ │ ├── activities/ │ │ │ │ │ ├── ClassesListActivity.java │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── SourceViewerActivity.java │ │ │ │ ├── adapters/ │ │ │ │ │ └── StableArrayAdapter.java │ │ │ │ ├── dex/ │ │ │ │ │ └── DexLoaderBuilder.java │ │ │ │ ├── reflector/ │ │ │ │ │ ├── ClassTypeAlgorithm.java │ │ │ │ │ ├── ClassesNamesList.java │ │ │ │ │ └── Reflector.java │ │ │ │ └── utils/ │ │ │ │ ├── IOUtils.java │ │ │ │ └── UriUtils.java │ │ │ └── res/ │ │ │ ├── layout/ │ │ │ │ ├── activity_classes_list.xml │ │ │ │ ├── activity_main.xml │ │ │ │ └── activity_source_viewer.xml │ │ │ ├── menu/ │ │ │ │ ├── menu_main.xml │ │ │ │ └── menu_source_viewer.xml │ │ │ ├── values/ │ │ │ │ ├── dimens.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── values-w820dp/ │ │ │ └── dimens.xml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── classysharkandroid/ │ │ └── ExampleUnitTest.java │ ├── build.gradle │ ├── settings.gradle │ └── snap/ │ └── snapcraft.yaml ├── ClassySharkWS/ │ ├── build.gradle │ ├── gradlew │ ├── gradlew.bat │ └── src/ │ ├── META-INF/ │ │ └── MANIFEST.MF │ └── com/ │ └── google/ │ └── classyshark/ │ ├── Main.java │ ├── Shark.java │ ├── Version.java │ ├── analytics/ │ │ ├── Analytics.java │ │ ├── FocusPoint.java │ │ ├── GoogleAnalytics_v1_URLBuildingStrategy.java │ │ ├── HTTPGetMethod.java │ │ ├── JGoogleAnalyticsTracker.java │ │ ├── LoggingAdapter.java │ │ └── URLBuildingStrategy.java │ ├── cli/ │ │ └── CliMode.java │ ├── gui/ │ │ ├── GuiMode.java │ │ ├── panel/ │ │ │ ├── ArchiveDisplayer.java │ │ │ ├── ClassySharkPanel.java │ │ │ ├── FileTransferHandler.java │ │ │ ├── ViewerController.java │ │ │ ├── chart/ │ │ │ │ ├── RingChart.java │ │ │ │ └── RingChartPanel.java │ │ │ ├── displayarea/ │ │ │ │ ├── BatchDocument.java │ │ │ │ ├── DisplayArea.java │ │ │ │ ├── IDisplayArea.java │ │ │ │ └── doodles/ │ │ │ │ ├── ChristmasBG.java │ │ │ │ ├── Doodle.java │ │ │ │ ├── SanFranBG.java │ │ │ │ └── SharkBG.java │ │ │ ├── io/ │ │ │ │ ├── CurrentFolderConfig.java │ │ │ │ ├── FileChooserUtils.java │ │ │ │ └── RecentArchivesConfig.java │ │ │ ├── methodscount/ │ │ │ │ └── MethodsCountPanel.java │ │ │ ├── reducer/ │ │ │ │ └── Reducer.java │ │ │ ├── toolbar/ │ │ │ │ ├── KeyUtils.java │ │ │ │ ├── RecentArchivesButton.java │ │ │ │ ├── Toolbar.java │ │ │ │ └── ToolbarController.java │ │ │ └── tree/ │ │ │ ├── CellRenderer.java │ │ │ ├── FilesTree.java │ │ │ └── NodeInfo.java │ │ ├── settings/ │ │ │ ├── SettingsFrame.java │ │ │ └── ThemeChosenListener.java │ │ └── theme/ │ │ ├── SwingThemeApplier.java │ │ ├── Theme.java │ │ ├── ThemeManager.java │ │ ├── dark/ │ │ │ ├── DarkColorScheme.java │ │ │ ├── DarkIconScheme.java │ │ │ └── DarkTheme.java │ │ └── light/ │ │ ├── LightColorScheme.java │ │ ├── LightIconScheme.java │ │ └── LightTheme.java │ ├── silverghost/ │ │ ├── FullArchiveReader.java │ │ ├── SilverGhost.java │ │ ├── SilverGhostFacade.java │ │ ├── TokensMapper.java │ │ ├── contentreader/ │ │ │ ├── BinaryContentReader.java │ │ │ ├── ContentReader.java │ │ │ ├── aar/ │ │ │ │ └── AarReader.java │ │ │ ├── apk/ │ │ │ │ └── ApkReader.java │ │ │ ├── clazz/ │ │ │ │ ├── ClassNameVisitor.java │ │ │ │ └── ClazzReader.java │ │ │ ├── dex/ │ │ │ │ ├── DexReader.java │ │ │ │ └── DexlibLoader.java │ │ │ └── jar/ │ │ │ └── JarReader.java │ │ ├── exporter/ │ │ │ ├── Exporter.java │ │ │ ├── FlatMethodCountExporter.java │ │ │ ├── MethodCountExporter.java │ │ │ └── TreeMethodCountExporter.java │ │ ├── io/ │ │ │ └── SherlockHash.java │ │ ├── methodscounter/ │ │ │ ├── ClassInfo.java │ │ │ ├── ClassNode.java │ │ │ └── RootBuilder.java │ │ ├── plugins/ │ │ │ ├── EmptyFullArchiveReader.java │ │ │ └── IdentityMapper.java │ │ └── translator/ │ │ ├── Translator.java │ │ ├── TranslatorFactory.java │ │ ├── apk/ │ │ │ ├── ApkTranslator.java │ │ │ └── dashboard/ │ │ │ ├── ApkDashboard.java │ │ │ ├── ApkNativeMethodsVisitor.java │ │ │ ├── ClassesDexDataEntry.java │ │ │ ├── DynamicSymbolsInspector.java │ │ │ ├── JavaDependenciesInspector.java │ │ │ ├── PrivateNativeLibsInspector.java │ │ │ ├── SyntheticAccessorsInspector.java │ │ │ ├── Table.java │ │ │ └── manifest/ │ │ │ ├── AndroidManifestPlainTextReader.java │ │ │ ├── ManifestInspector.java │ │ │ └── ReceiverActionsBL.java │ │ ├── dex/ │ │ │ ├── DexInfoTranslator.java │ │ │ ├── DexMethodsDumper.java │ │ │ └── DexStringsDumper.java │ │ ├── elf/ │ │ │ ├── ElfReader.java │ │ │ └── ElfTranslator.java │ │ ├── jar/ │ │ │ └── JarInfoTranslator.java │ │ ├── java/ │ │ │ ├── JavaTranslator.java │ │ │ ├── MetaObject.java │ │ │ ├── MetaObjectFactory.java │ │ │ ├── MetaObjectWithMapper.java │ │ │ ├── StressTest.java │ │ │ ├── clazz/ │ │ │ │ ├── QualifiedTypesMap.java │ │ │ │ ├── asm/ │ │ │ │ │ ├── ClassBytesFromJarExtractor.java │ │ │ │ │ ├── ClassDetailsFiller.java │ │ │ │ │ └── MetaObjectAsmClass.java │ │ │ │ └── reflect/ │ │ │ │ ├── ClassUtils.java │ │ │ │ └── MetaObjectClass.java │ │ │ └── dex/ │ │ │ ├── DexlibAdapter.java │ │ │ ├── MetaObjectDex.java │ │ │ └── MultidexReader.java │ │ └── xml/ │ │ ├── AndroidXmlTranslator.java │ │ ├── XmlDecompressor.java │ │ └── XmlHighlighter.java │ └── updater/ │ ├── UpdateManager.java │ ├── models/ │ │ ├── Release.java │ │ └── ReleaseDownloadData.java │ ├── networking/ │ │ ├── AbstractDownloader.java │ │ ├── AbstractReleaseCallback.java │ │ ├── CliDownloader.java │ │ ├── GitHubApi.java │ │ ├── GuiDownloader.java │ │ ├── MessageRunnable.java │ │ └── NetworkManager.java │ └── utils/ │ ├── FileUtils.java │ └── NamingUtils.java ├── NOTICE ├── README.md ├── Samples/ │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ └── SampleGradle/ │ ├── README.md │ ├── build.gradle │ ├── gradlew │ ├── gradlew.bat │ └── src/ │ └── main/ │ └── java/ │ └── Main.java └── third_party/ ├── ASMDEX.LICENSE └── java-binutils.LICENSE ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .gradle/ ClassySharkWS/.gradle/ ClassySharkWS/build/ ClassySharkAndroid/.idea/ ClassySharkAndroid/out/ ClassySharkAndroid/tests/ ClassySharkAndroid/app/app.iml ClassySharkAndroid/gradle.properties ClassySharkAndroid/gradle/wrapper/gradle-wrapper.jar ClassySharkAndroid/gradle/wrapper/gradle-wrapper.properties ClassySharkAndroid/gradlew ClassySharkAndroid/gradlew.bat ClassySharkWS/ClassySharkWS.iml ClassySharkWS/.idea/ ClassySharkWS/out/ *.dex *.class *.html *.properties *.txt ClassySharkWS/*_dump ClassySharkWS/ClassyShark*.jar ClassySharkWS/pathname ================================================ FILE: CONTRIB.md ================================================ # How to become a contributor and submit your own code ## Contributor License Agreements We'd love to accept your sample apps and patches! Before we can take them, we have to jump a couple of legal hurdles. Please fill out either the individual or corporate Contributor License Agreement (CLA). * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA] (https://developers.google.com/open-source/cla/individual). * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA] (https://developers.google.com/open-source/cla/corporate). Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. ## Contributing A Patch 1. Submit an issue describing your proposed change to the repo in question. 1. The repo owner will respond to your issue promptly. 1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 1. Fork the desired repo, develop and test your code changes. 1. Ensure that your code adheres to the existing style in the sample to which you are contributing. Refer to the [Android Code Style Guide] (https://source.android.com/source/code-style.html) for the recommended coding standards for this organization. 1. Ensure that your code has an appropriate set of unit tests which all pass. 1. Submit a pull request. ================================================ FILE: ClassySharkAndroid/.gitignore ================================================ .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures ================================================ FILE: ClassySharkAndroid/ClassySharkAndroid.iml ================================================ ================================================ FILE: ClassySharkAndroid/app/.gitignore ================================================ /build ================================================ FILE: ClassySharkAndroid/app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion '25.0.0' defaultConfig { applicationId "com.google.classysharkandroid" minSdkVersion 19 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.0.0' compile 'com.google.guava:guava:18.0' } ================================================ FILE: ClassySharkAndroid/app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/bfarber/DevTools/android-sdk-macosx/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: ClassySharkAndroid/app/src/androidTest/java/com/google/classysharkandroid/activities/classysharkandroid/ApplicationTest.java ================================================ package com.google.classysharkandroid.activities.classysharkandroid; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: ClassySharkAndroid/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: ClassySharkAndroid/app/src/main/assets/prettify.css ================================================ .str{color:#EC7600}.kwd{color:#93C763}.com{color:#66747B}.typ{color:#678CB1}.lit{color:#FACD22}.pun{color:#F1F2F3}.pln{color:#F1F2F3}.tag{color:#8AC763}.atn{color:#E0E2E4}.atv{color:#EC7600}.dec{color:purple}pre.prettyprint{border:0 solid #888}ol.linenums{margin-top:0;margin-bottom:0}.prettyprint{background:#000}li.L0,li.L1,li.L2,li.L3,li.L4,li.L5,li.L6,li.L7,li.L8,li.L9{color:#555;list-style-type:decimal}li.L1,li.L3,li.L5,li.L7,li.L9{background:#111}@media print{.str{color:#060}.kwd{color:#006;font-weight:700}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:700}.lit{color:#044}.pun{color:#440}.pln{color:#000}.tag{color:#006;font-weight:700}.atn{color:#404}.atv{color:#060}} ================================================ FILE: ClassySharkAndroid/app/src/main/assets/prettify.js ================================================ !function(){var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; (function(){function S(a){function d(e){var b=e.charCodeAt(0);if(b!==92)return b;var a=e.charAt(1);return(b=r[a])?b:"0"<=a&&a<="7"?parseInt(e.substring(1),8):a==="u"||a==="x"?parseInt(e.substring(2),16):e.charCodeAt(1)}function g(e){if(e<32)return(e<16?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return e==="\\"||e==="-"||e==="]"||e==="^"?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),e=[],a= b[0]==="^",c=["["];a&&c.push("^");for(var a=a?1:0,f=b.length;a122||(l<65||h>90||e.push([Math.max(65,h)|32,Math.min(l,90)|32]),l<97||h>122||e.push([Math.max(97,h)&-33,Math.min(l,122)&-33]))}}e.sort(function(e,a){return e[0]-a[0]||a[1]-e[1]});b=[];f=[];for(a=0;ah[0]&&(h[1]+1>h[0]&&c.push("-"),c.push(g(h[1])));c.push("]");return c.join("")}function s(e){for(var a=e.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),c=a.length,d=[],f=0,h=0;f=2&&e==="["?a[f]=b(l):e!=="\\"&&(a[f]=l.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var x=0,m=!1,j=!1,k=0,c=a.length;k=5&&"lang-"===w.substring(0,5))&&!(t&&typeof t[1]==="string"))f=!1,w="src";f||(r[z]=w)}h=c;c+=z.length;if(f){f=t[1];var l=z.indexOf(f),B=l+f.length;t[2]&&(B=z.length-t[2].length,l=B-f.length);w=w.substring(5);H(j+h,z.substring(0,l),g,k);H(j+h+l,f,I(w,f),k);H(j+h+B,z.substring(B),g,k)}else k.push(j+h,w)}a.g=k}var b={},s;(function(){for(var g=a.concat(d),j=[],k={},c=0,i=g.length;c=0;)b[n.charAt(e)]=r;r=r[1];n=""+r;k.hasOwnProperty(n)||(j.push(r),k[n]=q)}j.push(/[\S\s]/);s=S(j)})();var x=d.length;return g}function v(a){var d=[],g=[];a.tripleQuotedStrings?d.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?d.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&g.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),g.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,q])):d.push(["com", /^#[^\n\r]*/,q,"#"]));a.cStyleComments&&(g.push(["com",/^\/\/[^\n\r]*/,q]),g.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));if(b=a.regexLiterals){var s=(b=b>1?"":"\n\r")?".":"[\\S\\s]";g.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+s+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+ s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/, q],["pun",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d= c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=["break,continue,do,else,for,if,return,while"],E=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[E,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],N=[E,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"], O=[N,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],E=[E,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],P=[y,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], Q=[y,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],W=[y,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],y=[y,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],R=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/, V=/\S/,X=v({keywords:[M,O,E,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",P,Q,y],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),F={};p(X,["default-code"]);p(C([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-", /^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);p(C([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/], ["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);p(C([],[["atv",/^[\S\s]+/]]),["uq.val"]);p(v({keywords:M,hashComments:!0,cStyleComments:!0,types:R}),["c","cc","cpp","cxx","cyc","m"]);p(v({keywords:"null,true,false"}),["json"]);p(v({keywords:O,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:R}), ["cs"]);p(v({keywords:N,cStyleComments:!0}),["java"]);p(v({keywords:y,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);p(v({keywords:P,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);p(v({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);p(v({keywords:Q, hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);p(v({keywords:E,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);p(v({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);p(v({keywords:W,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]); p(C([],[["str",/^[\S\s]+/]]),["regex"]);var Y=D.PR={createSimpleLexer:C,registerLangHandler:p,sourceDecorator:v,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:D.prettyPrintOne=function(a,d,g){var b=document.createElement("div");b.innerHTML="
"+a+"
";b=b.firstChild;g&&J(b,g,!0);K({h:d,j:g,c:b,i:1}); return b.innerHTML},prettyPrint:D.prettyPrint=function(a,d){function g(){for(var b=D.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;i=0;){var M=A[m],T=M.src.match(/^[^#?]*\/run_prettify\.js(\?[^#]*)?(?:#.*)?$/);if(T){z=T[1]||"";M.parentNode.removeChild(M);break}}var S=!0,D= [],N=[],K=[];z.replace(/[&?]([^&=]+)=([^&]+)/g,function(e,j,w){w=decodeURIComponent(w);j=decodeURIComponent(j);j=="autorun"?S=!/^[0fn]/i.test(w):j=="lang"?D.push(w):j=="skin"?N.push(w):j=="callback"&&K.push(w)});m=0;for(z=D.length;m122||(o<65||k>90||f.push([Math.max(65,k)|32,Math.min(o,90)|32]),o<97||k>122||f.push([Math.max(97,k)&-33,Math.min(o,122)&-33]))}}f.sort(function(f,a){return f[0]- a[0]||a[1]-f[1]});b=[];g=[];for(a=0;ak[0]&&(k[1]+1>k[0]&&c.push("-"),c.push(h(k[1])));c.push("]");return c.join("")}function e(f){for(var a=f.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),c=a.length,d=[],g=0,k=0;g=2&&f==="["?a[g]=b(o):f!=="\\"&&(a[g]=o.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var j=0,F=!1,l=!1,I=0,c=a.length;I=5&&"lang-"===y.substring(0,5))&&!(u&&typeof u[1]==="string"))g=!1,y="src";g||(m[B]=y)}k=c;c+=B.length;if(g){g=u[1];var o=B.indexOf(g),H=o+g.length;u[2]&&(H=B.length-u[2].length,o=H-g.length);y=y.substring(5);n(l+k,B.substring(0,o),h,j);n(l+k+o,g,A(y, g),j);n(l+k+H,B.substring(H),h,j)}else j.push(l+k,y)}a.g=j}var b={},e;(function(){for(var h=a.concat(d),l=[],i={},c=0,p=h.length;c=0;)b[q.charAt(f)]=m;m=m[1];q=""+m;i.hasOwnProperty(q)||(l.push(m),i[q]=r)}l.push(/[\S\s]/);e=j(l)})();var i=d.length;return h}function t(a){var d=[],h=[];a.tripleQuotedStrings?d.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/, r,"'\""]):a.multiLineStrings?d.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,r,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,r,"\"'"]);a.verbatimStrings&&h.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,r]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,r,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/, r,"#"]),h.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,r])):d.push(["com",/^#[^\n\r]*/,r,"#"]));a.cStyleComments&&(h.push(["com",/^\/\/[^\n\r]*/,r]),h.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,r]));if(b=a.regexLiterals){var e=(b=b>1?"":"\n\r")?".":"[\\S\\s]";h.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+ ("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+e+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+e+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&h.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&h.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),r]);d.push(["pln",/^\s+/,r," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");h.push(["lit",/^@[$_a-z][\w$@]*/i,r],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,r],["pln",/^[$_a-z][\w$@]*/i,r],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i, r,"0123456789"],["pln",/^\\[\S\s]?/,r],["pun",RegExp(b),r]);return C(d,h)}function z(a,d,h){function b(a){var c=a.nodeType;if(c==1&&!j.test(a.className))if("br"===a.nodeName)e(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&h){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(l.createTextNode(d),a.nextSibling),e(a),c||a.parentNode.removeChild(a)}} function e(a){function b(a,c){var d=c?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),h=a.nextSibling;f.appendChild(d);for(var e=h;e;e=h)h=e.nextSibling,f.appendChild(e)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var j=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,l=a.ownerDocument,i=l.createElement("li");a.firstChild;)i.appendChild(a.firstChild);for(var c=[i],p=0;p=0;){var b=d[h];U.hasOwnProperty(b)?V.console&&console.warn("cannot override language handler %s",b):U[b]=a}}function A(a,d){if(!a||!U.hasOwnProperty(a))a=/^\s*=o&&(b+=2);h>=H&&(t+=2)}}finally{if(g)g.style.display=k}}catch(v){V.console&&console.log(v&&v.stack||v)}}var V=window,G=["break,continue,do,else,for,if,return,while"],O=[[G,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],J=[O,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],K=[O,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"], L=[K,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],O=[O,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],M=[G,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], N=[G,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],R=[G,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],G=[G,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],Q=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/, S=/\S/,T=t({keywords:[J,L,O,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",M,N,G],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),U={};i(T,["default-code"]);i(C([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-", /^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);i(C([["pln",/^\s+/,r," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,r,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/], ["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);i(C([],[["atv",/^[\S\s]+/]]),["uq.val"]);i(t({keywords:J,hashComments:!0,cStyleComments:!0,types:Q}),["c","cc","cpp","cxx","cyc","m"]);i(t({keywords:"null,true,false"}),["json"]);i(t({keywords:L,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:Q}), ["cs"]);i(t({keywords:K,cStyleComments:!0}),["java"]);i(t({keywords:G,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);i(t({keywords:M,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);i(t({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);i(t({keywords:N, hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);i(t({keywords:O,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);i(t({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);i(t({keywords:R,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]); i(C([],[["str",/^[\S\s]+/]]),["regex"]);var X=V.PR={createSimpleLexer:C,registerLangHandler:i,sourceDecorator:t,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:function(a,d,e){var b=document.createElement("div");b.innerHTML="
"+a+"
";b=b.firstChild;e&&z(b,e,!0);D({h:d,j:e,c:b,i:1});return b.innerHTML}, prettyPrint:e=e=function(a,d){function e(){for(var b=V.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;p<(((º>"); mProgressDialog.setIndeterminate(false); mProgressDialog.setCancelable(false); mProgressDialog.show(); uriStream = UriUtils.getStreamFromUri(ClassesListActivity.this, uriFromIntent); final byte[] bytes = IOUtils.toByteArray(uriStream); new FillClassesNamesThread(bytes).start(); new StartDexLoaderThread(bytes).start(); } catch (Exception e) { e.printStackTrace(); } } private void setActionBar() { ActionBar bar = getSupportActionBar(); String title = "Content"; if(getIntent().getStringExtra(MainActivity.APP_NAME) != null) { title = getIntent().getStringExtra(MainActivity.APP_NAME); } bar.setTitle((Html.fromHtml("" + title + ""))); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } private class FillClassesNamesThread extends Thread { private final byte[] bytes; public FillClassesNamesThread(byte[] bytes) { this.bytes = bytes; } @Override public void run() { try { Thread.currentThread().setPriority(Thread.MAX_PRIORITY); File incomeFile = File.createTempFile("classes" + Thread.currentThread().getId(), ".dex", getCacheDir()); IOUtils.bytesToFile(bytes, incomeFile); File optimizedFile = File.createTempFile("opt" + Thread.currentThread().getId(), ".dex", getCacheDir()); DexFile dx = DexFile.loadDex(incomeFile.getPath(), optimizedFile.getPath(), 0); for (Enumeration classNames = dx.entries(); classNames.hasMoreElements(); ) { String className = classNames.nextElement(); classesList.add(className); } } catch (Exception e) { // ODEX, need to see how to handle e.printStackTrace(); } ClassesListActivity.this.runOnUiThread(new Runnable() { @Override public void run() { final ArrayList list = new ArrayList<>(); for (int i = 0; i < classesList.getClassNames().size(); ++i) { list.add(classesList.getClassNames().get(i)); } final StableArrayAdapter adapter = new StableArrayAdapter(ClassesListActivity.this, android.R.layout.simple_list_item_1, list); lv.setAdapter(adapter); mProgressDialog.dismiss(); if(classesList.getClassNames().isEmpty()) { Toast.makeText(ClassesListActivity.this, "Sorry don't support ODEX", Toast.LENGTH_LONG).show(); } } }); } } private class StartDexLoaderThread extends Thread { private final byte[] bytes; public StartDexLoaderThread(byte[] bytes) { this.bytes = bytes; } @Override public void run() { try { Thread.currentThread().setPriority(Thread.MAX_PRIORITY); final DexClassLoader loader = DexLoaderBuilder.fromBytes(ClassesListActivity.this, bytes); ClassesListActivity.this.runOnUiThread(new Runnable() { @Override public void run() { lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { Class loadClass; try { loadClass = loader.loadClass(classesList.getClassName(position)); Reflector reflector = new Reflector(loadClass); reflector.generateClassData(); String result = reflector.toString(); Intent i = new Intent(ClassesListActivity.this, SourceViewerActivity.class); i.putExtra(ClassesListActivity.SELECTED_CLASS_NAME, classesList.getClassName(position)); i.putExtra(ClassesListActivity.SELECTED_CLASS_DUMP, result); startActivity(i); } catch (Exception e) { e.printStackTrace(); } } }); } }); } catch (Exception e) { e.printStackTrace(); } } } } ================================================ FILE: ClassySharkAndroid/app/src/main/java/com/google/classysharkandroid/activities/MainActivity.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classysharkandroid.activities; import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.ResolveInfo; import android.net.Uri; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.webkit.MimeTypeMap; import android.widget.AdapterView; import android.widget.ListView; import com.google.classysharkandroid.R; import com.google.classysharkandroid.adapters.StableArrayAdapter; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class MainActivity extends AppCompatActivity { public static final String APP_NAME = "APP_NAME"; private ListView lv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lv = (ListView) findViewById(R.id.listView); } @Override public void onStart() { super.onStart(); final ArrayList apps = new ArrayList<>(); final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); final List pkgAppsList = getPackageManager().queryIntentActivities(mainIntent, 0); for (Object object : pkgAppsList) { ResolveInfo info = (ResolveInfo) object; File file = new File(info.activityInfo.applicationInfo.publicSourceDir); AppListNode aln = new AppListNode(); aln.name = info.activityInfo.applicationInfo.processName.toString(); aln.file = file; apps.add(aln); } Collections.sort(apps); final StableArrayAdapter adapter = new StableArrayAdapter(MainActivity.this, android.R.layout.simple_list_item_1, convert(apps)); lv.setAdapter(adapter); lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { MimeTypeMap myMime = MimeTypeMap.getSingleton(); Intent newIntent = new Intent(MainActivity.this, ClassesListActivity.class); String mimeType = myMime.getMimeTypeFromExtension("apk"); newIntent.setDataAndType(Uri.fromFile(apps.get(position).file),mimeType); newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); newIntent.putExtra(APP_NAME, apps.get(position).name); try { startActivity(newIntent); } catch (ActivityNotFoundException e) { } } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } private static List convert(ArrayList apps) { ArrayList result = new ArrayList<>(); for(AppListNode node : apps) { result.add(node.name); } return result; } private static class AppListNode implements Comparable { public String name; public File file; @Override public int compareTo(AppListNode another) { return this.name.compareTo(another.name); } } } ================================================ FILE: ClassySharkAndroid/app/src/main/java/com/google/classysharkandroid/activities/SourceViewerActivity.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classysharkandroid.activities; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.text.Html; import android.view.Menu; import android.view.MenuItem; import android.webkit.WebView; import android.webkit.WebViewClient; import com.google.classysharkandroid.R; import com.google.common.html.HtmlEscapers; public class SourceViewerActivity extends AppCompatActivity { private String sourceCodeText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_source_viewer); getWindow().getDecorView().setBackgroundColor(Color.BLACK); ActionBar bar = getSupportActionBar(); bar.setBackgroundDrawable(new ColorDrawable(0xff606060)); String result = ""; try { String name = getIntent().getStringExtra(ClassesListActivity.SELECTED_CLASS_NAME); String barName = name.substring(name.lastIndexOf(".") + 1); bar.setTitle((Html.fromHtml("" + barName + ""))); result = getIntent().getStringExtra(ClassesListActivity.SELECTED_CLASS_DUMP); } catch (Exception e) { e.printStackTrace(); } sourceCodeText = result; sourceCodeText = HtmlEscapers.htmlEscaper().escape(sourceCodeText); WebView webView = (WebView) findViewById(R.id.source_view); webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setDefaultTextEncodingName("utf-8"); webView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { } }); webView.loadDataWithBaseURL("file:///android_asset/", "
" + sourceCodeText + "
", "text/html", "UTF-8", null); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } } ================================================ FILE: ClassySharkAndroid/app/src/main/java/com/google/classysharkandroid/adapters/StableArrayAdapter.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classysharkandroid.adapters; import android.content.Context; import android.widget.ArrayAdapter; import java.util.HashMap; import java.util.List; public class StableArrayAdapter extends ArrayAdapter { HashMap mIdMap = new HashMap<>(); public StableArrayAdapter(Context context, int textViewResourceId, List objects) { super(context, textViewResourceId, objects); for (int i = 0; i < objects.size(); ++i) { mIdMap.put(objects.get(i), i); } } @Override public long getItemId(int position) { String item = getItem(position); return mIdMap.get(item); } @Override public boolean hasStableIds() { return true; } } ================================================ FILE: ClassySharkAndroid/app/src/main/java/com/google/classysharkandroid/dex/DexLoaderBuilder.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classysharkandroid.dex; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import android.content.Context; import com.google.classysharkandroid.utils.IOUtils; import dalvik.system.DexClassLoader; public class DexLoaderBuilder { private static final int BUF_SIZE = 8 * 1024; private DexLoaderBuilder() { } public static DexClassLoader fromFile(Context context, final File dexFile) throws Exception { FileInputStream fileInputStream = new FileInputStream(dexFile); byte[] bFile = IOUtils.toByteArray(fileInputStream); return fromBytes(context, bFile); } public static DexClassLoader fromBytes(Context context, final byte[] dexBytes) throws Exception { if (null == context) { throw new RuntimeException("No context provided"); } String dexFileName = "internal.dex"; final File dexInternalStoragePath = new File(context.getDir("dex", Context.MODE_PRIVATE), dexFileName); if (!dexInternalStoragePath.exists()) { prepareDex(dexBytes, dexInternalStoragePath); } final File optimizedDexOutputPath = context.getCodeCacheDir(); DexClassLoader loader = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(), optimizedDexOutputPath.getAbsolutePath(), null, context.getClassLoader().getParent()); dexInternalStoragePath.delete(); return loader; } private static boolean prepareDex(byte[] bytes, File dexInternalStoragePath) { BufferedInputStream bis = null; OutputStream dexWriter = null; try { bis = new BufferedInputStream(new ByteArrayInputStream(bytes)); dexWriter = new BufferedOutputStream(new FileOutputStream(dexInternalStoragePath)); byte[] buf = new byte[BUF_SIZE]; int len; while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) { dexWriter.write(buf, 0, len); } dexWriter.close(); bis.close(); return true; } catch (IOException e) { if (dexWriter != null) { try { dexWriter.close(); } catch (IOException ioe) { throw new RuntimeException(ioe); } } if (bis != null) { try { bis.close(); } catch (IOException ioe) { throw new RuntimeException(ioe); } } return false; } } } ================================================ FILE: ClassySharkAndroid/app/src/main/java/com/google/classysharkandroid/reflector/ClassTypeAlgorithm.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classysharkandroid.reflector; import java.util.Hashtable; public class ClassTypeAlgorithm { private ClassTypeAlgorithm() { } public static String TypeName(String nm, Hashtable ht) { String yy; String arr; if (nm.charAt(0) != '[') { int i = nm.lastIndexOf("."); if (i == -1) return nm; // It's a primitive type, ignore it. else { yy = nm.substring(i + 1); if (ht != null) ht.put(nm, yy); // note class types in the hashtable. return yy; } } arr = "[]"; if (nm.charAt(1) == '[') yy = TypeName(nm.substring(1), ht); else { switch (nm.charAt(1)) { case 'L': yy = TypeName(nm.substring(nm.indexOf("L") + 1, nm.indexOf(";")), ht); break; case 'I': yy = "int"; break; case 'V': yy = "void"; break; case 'C': yy = "char"; break; case 'D': yy = "double"; break; case 'F': yy = "float"; break; case 'J': yy = "long"; break; case 'S': yy = "short"; break; case 'Z': yy = "boolean"; break; case 'B': yy = "byte"; break; default: yy = "BOGUS:" + nm; break; } } return yy + arr; } } ================================================ FILE: ClassySharkAndroid/app/src/main/java/com/google/classysharkandroid/reflector/ClassesNamesList.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classysharkandroid.reflector; import java.util.LinkedList; import java.util.List; public class ClassesNamesList { private List list; public ClassesNamesList() { list = new LinkedList<>(); } public void add(String className) { list.add(className); } public List getClassNames() { return this.list; } public String getClassName(int position) { return this.list.get(position); } } ================================================ FILE: ClassySharkAndroid/app/src/main/java/com/google/classysharkandroid/reflector/Reflector.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classysharkandroid.reflector; import java.lang.reflect.*; import java.util.*; public class Reflector { private Class clazz; private List words; public enum TAG { MODIFIER, IDENTIFIER, DOCUMENT } public static class TaggedWord { public TaggedWord(String word, TAG tag) { this.text = word; this.tag = tag; } public String text; public TAG tag; } public Reflector(Class clazz) { this.clazz = clazz; words = new ArrayList<>(); } public String toString() { if (words == null) { return ""; } else { StringBuilder sb = new StringBuilder(); for (TaggedWord word : words) { sb.append(word.text); } return sb.toString(); } } public List getWords() { return this.words; } public void generateClassData() { long start = System.currentTimeMillis(); Constructor constructors[]; Class cc[]; Method methods[]; Field fields[]; Class currentClass = null; Class supClass; String x, y; Hashtable classRef; currentClass = clazz; /* * Step 0: If our name contains dots we're in a package so put * that out first. */ x = currentClass.getName(); if (x.lastIndexOf(".") != -1) { y = x.substring(0, x.lastIndexOf(".")); words.add(new TaggedWord("\npackage ", TAG.MODIFIER)); words.add(new TaggedWord(y, TAG.IDENTIFIER)); words.add(new TaggedWord(";", TAG.MODIFIER)); } fields = currentClass.getDeclaredFields(); constructors = currentClass.getDeclaredConstructors(); methods = currentClass.getDeclaredMethods(); classRef = generateDependencies(constructors, methods, fields); // Don't import ourselves ... classRef.remove(currentClass.getName()); fillTaggedText(constructors, methods, fields, currentClass, classRef); long finish = System.currentTimeMillis(); System.out.println("* " + (finish - start) + "ms"); } private Hashtable generateDependencies(Constructor[] constructors, Method[] methods, Field[] fields) { String x, y; Hashtable classRef = new Hashtable(); for (int i = 0; i < fields.length; i++) { x = ClassTypeAlgorithm.TypeName(fields[i].getType().getName(), classRef); } for (int i = 0; i < constructors.length; i++) { Class cx[] = constructors[i].getParameterTypes(); if (cx.length > 0) { for (int j = 0; j < cx.length; j++) { x = ClassTypeAlgorithm.TypeName(cx[j].getName(), classRef); } } } for (int i = 0; i < methods.length; i++) { x = ClassTypeAlgorithm.TypeName(methods[i].getReturnType().getName(), classRef); Class cx[] = methods[i].getParameterTypes(); if (cx.length > 0) { for (int j = 0; j < cx.length; j++) { x = ClassTypeAlgorithm.TypeName(cx[j].getName(), classRef); } } Class[] xType = methods[i].getExceptionTypes(); for (int j = 0; j < xType.length; j++) { x = ClassTypeAlgorithm.TypeName(xType[j].getName(), classRef); } } return classRef; } private void fillTaggedText(Constructor[] constructors, Method[] methods, Field[] fields, Class currentClass, Hashtable classRef) { Class supClass; String x; for (Enumeration e = classRef.keys(); e.hasMoreElements(); ) { Object importIdentifier = e.nextElement(); words.add(new TaggedWord("\nimport ", TAG.MODIFIER)); words.add(new TaggedWord(importIdentifier + ";", TAG.IDENTIFIER)); } words.add(new TaggedWord("\n\n", TAG.IDENTIFIER)); int mod = currentClass.getModifiers(); words.add(new TaggedWord(Modifier.toString(mod), TAG.MODIFIER)); if (!Modifier.isInterface(mod)) { words.add(new TaggedWord(" class", TAG.MODIFIER)); } words.add(new TaggedWord(" " + ClassTypeAlgorithm.TypeName(currentClass.getName(), null), TAG.IDENTIFIER)); supClass = currentClass.getSuperclass(); if (supClass != null) { words.add(new TaggedWord(" extends ", TAG.MODIFIER)); words.add(new TaggedWord(ClassTypeAlgorithm.TypeName(supClass.getName(), classRef), TAG.IDENTIFIER)); } words.add(new TaggedWord("\n{", TAG.IDENTIFIER)); words.add(new TaggedWord("\n" + "/*\n" + " * Field Definitions.\n" + " */", TAG.DOCUMENT)); for (int i = 0; i < fields.length; i++) { Class ctmp = fields[i].getType(); int md = fields[i].getModifiers(); words.add(new TaggedWord("\n " + Modifier.toString(md) + " ", TAG.MODIFIER)); words.add(new TaggedWord(ClassTypeAlgorithm.TypeName(fields[i].getType().getName(), null) + " ", TAG.IDENTIFIER)); words.add(new TaggedWord(fields[i].getName() + ";", TAG.DOCUMENT)); } // TODO ENUMS members // http://stackoverflow.com/questions/140537/how-to-use-java-reflection-when-the-enum-type-is-a-class words.add(new TaggedWord("\n" + "/*\n" + " * Declared Constructors.\n" + " */\n", TAG.DOCUMENT)); x = ClassTypeAlgorithm.TypeName(currentClass.getName(), null); for (int i = 0; i < constructors.length; i++) { int md = constructors[i].getModifiers(); words.add(new TaggedWord(" " + Modifier.toString(md) + " ", TAG.MODIFIER)); words.add(new TaggedWord(x, TAG.IDENTIFIER)); Class cx[] = constructors[i].getParameterTypes(); words.add(new TaggedWord("(", TAG.IDENTIFIER)); if (cx.length > 0) { for (int j = 0; j < cx.length; j++) { words.add(new TaggedWord(ClassTypeAlgorithm.TypeName(cx[j].getName(), null), TAG.IDENTIFIER)); if (j < (cx.length - 1)) { words.add(new TaggedWord(", ", TAG.IDENTIFIER)); } } } words.add(new TaggedWord(") { ... }\n", TAG.IDENTIFIER)); } for (int i = 0; i < methods.length; i++) { int md = methods[i].getModifiers(); words.add(new TaggedWord(" " + Modifier.toString(md) + " ", TAG.MODIFIER)); words.add(new TaggedWord(ClassTypeAlgorithm.TypeName(methods[i].getReturnType().getName(), null) + " ", TAG.IDENTIFIER)); words.add(new TaggedWord(methods[i].getName(), TAG.DOCUMENT)); Class cx[] = methods[i].getParameterTypes(); words.add(new TaggedWord("(", TAG.IDENTIFIER)); if (cx.length > 0) { for (int j = 0; j < cx.length; j++) { words.add(new TaggedWord(ClassTypeAlgorithm.TypeName(cx[j].getName(), classRef), TAG.IDENTIFIER)); if (j < (cx.length - 1)) { words.add(new TaggedWord(", ", TAG.IDENTIFIER)); } } } words.add(new TaggedWord(") ", TAG.IDENTIFIER)); // TODO put to dependencies & imports Class[] xType = methods[i].getExceptionTypes(); if (xType.length > 0) { words.add(new TaggedWord(" throws ", TAG.IDENTIFIER)); } for (int j = 0; j < xType.length; j++) { words.add(new TaggedWord(xType[j].getSimpleName(), TAG.IDENTIFIER)); } words.add(new TaggedWord("{ ... }\n", TAG.IDENTIFIER)); } words.add(new TaggedWord("\n} ", TAG.IDENTIFIER)); } public static void main(String[] args) { Reflector reflector = new Reflector(Integer.class); reflector.generateClassData(); System.out.print(reflector); } } ================================================ FILE: ClassySharkAndroid/app/src/main/java/com/google/classysharkandroid/utils/IOUtils.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classysharkandroid.utils; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class IOUtils { public static void bytesToFile(byte[] bytes, File result) throws IOException { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(result)); bos.write(bytes); bos.flush(); bos.close(); } public static byte[] toByteArray(InputStream input) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); copy(input, output); return output.toByteArray(); } public static int copy(InputStream input, OutputStream output) throws IOException { long count = copyLarge(input, output); if (count > Integer.MAX_VALUE) { return -1; } return (int) count; } private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; public static long copyLarge(InputStream input, OutputStream output) throws IOException { byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; long count = 0; int n = 0; while (-1 != (n = input.read(buffer))) { output.write(buffer, 0, n); count += n; } return count; } } ================================================ FILE: ClassySharkAndroid/app/src/main/java/com/google/classysharkandroid/utils/UriUtils.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classysharkandroid.utils; import android.content.Context; import android.net.Uri; import java.io.FileNotFoundException; import java.io.InputStream; public class UriUtils { public static InputStream getStreamFromUri(Context context, Uri uriFromIntent) throws FileNotFoundException { return context.getContentResolver().openInputStream(uriFromIntent); } public static boolean isAttach(Uri uriFromIntent) { return (uriFromIntent != null) && (uriFromIntent.getScheme().contains("content")); } } ================================================ FILE: ClassySharkAndroid/app/src/main/res/layout/activity_classes_list.xml ================================================ ================================================ FILE: ClassySharkAndroid/app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: ClassySharkAndroid/app/src/main/res/layout/activity_source_viewer.xml ================================================ ================================================ FILE: ClassySharkAndroid/app/src/main/res/menu/menu_main.xml ================================================ ================================================ FILE: ClassySharkAndroid/app/src/main/res/menu/menu_source_viewer.xml ================================================ ================================================ FILE: ClassySharkAndroid/app/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: ClassySharkAndroid/app/src/main/res/values/strings.xml ================================================ ClassySharkAndroid Hello world! Settings SourceViewer MainActivity ================================================ FILE: ClassySharkAndroid/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: ClassySharkAndroid/app/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: ClassySharkAndroid/app/src/test/java/com/classysharkandroid/ExampleUnitTest.java ================================================ package com.apisolutions.classysharkandroid.activities.classysharkandroid; import org.junit.Test; import static org.junit.Assert.*; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: ClassySharkAndroid/build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.0.0-beta2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: ClassySharkAndroid/settings.gradle ================================================ include ':app' ================================================ FILE: ClassySharkAndroid/snap/snapcraft.yaml ================================================ name: android-classyshark version: '8.2' summary: Binary analysis of any Android/Java based app/APK/game. description: |ClassyShark is a standalone binary inspection tool for Android developers. It can reliably browse any Android executable and show important info such as class interfaces and members, dex counts and dependencies. confinement: devmode parts: android-classyshark: after: [desktop-glib-only] plugin: gradle source: https://codeload.github.com/android-classyshark-8.2.zip build: | export JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64" gradle release -x test -x createGitTag install: | unzip DIST/android-classyshark_bin-*.zip -d $SNAPCRAFT_PART_INSTALL/ build-packages: - unzip - openjdk-8-jdk - dexlib2 - java-binutils apps: android-classyshark: command: desktop-launch $SNAP/android-classyshark-8.2/android-classyshark.sh ================================================ FILE: ClassySharkWS/build.gradle ================================================ group 'classyshark' version '1.0-SNAPSHOT' apply plugin: 'java' apply plugin: 'java-library-distribution' // task: gradle distZip sourceCompatibility = 1.8 repositories { flatDir { dirs '../third_party' } mavenCentral() } // ClassyShark doesn't follow the standard src/main/java convention // https://docs.gradle.org/current/userguide/java_plugin.html#sec:changing_java_project_layout sourceSets { main { java { srcDirs = ['src/'] } resources { srcDirs = ['src/'] } } } dependencies { // local jars compile name: 'asmdex-1.0', ext: 'jar' compile name: 'util-2.0.6', ext: 'jar' compile name: 'java-binutils', ext: 'jar' // maven compile 'org.ow2.asm:asm-all:5.2' compile group: 'org.smali', name: 'dexlib2', version: '2.2.7' compile 'org.apache.bcel:bcel:6.5.0' compile 'com.squareup.retrofit2:converter-gson:2.9.0' compile 'com.google.code.gson:gson-parent:2.9.0' compile 'com.google.guava:guava:31.1-jre' compile 'com.squareup.okhttp3:okhttp:4.10.0' compile 'com.squareup.okio:okio:3.2.0' compile 'com.squareup.retrofit2:retrofit:2.9.0' } jar { manifest { attributes( 'Main-Class': 'com.google.classyshark.Main', "Class-Path": configurations.compile.collect { "lib/$it.name" }.join(' ') ) } } // Create a single Jar with all dependencies task fatJar(type: Jar) { manifest { attributes ( 'Main-Class': 'com.google.classyshark.Main', ) } baseName = project.name + '-all' from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } with jar } ================================================ FILE: ClassySharkWS/gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: ClassySharkWS/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: ClassySharkWS/src/META-INF/MANIFEST.MF ================================================ Manifest-Version: 1.0 Main-Class: com.google.classyshark.Main ================================================ FILE: ClassySharkWS/src/com/google/classyshark/Main.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark; import com.google.classyshark.analytics.Analytics; import com.google.classyshark.cli.CliMode; import com.google.classyshark.gui.GuiMode; import java.util.Arrays; import java.util.List; /** * the driver class of the app */ public class Main { private Main() { } private static boolean isGui(List argsAsArray) { return argsAsArray.isEmpty() || argsAsArray.get(0).equalsIgnoreCase("-open"); } public static void main(final String[] args) { final List argsAsArray = Arrays.asList(args); Analytics.INSTANCE.addActivation(); if (isGui(argsAsArray)) { GuiMode.with(argsAsArray); } else { CliMode.with(argsAsArray); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/Shark.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark; import com.google.classyshark.silverghost.SilverGhostFacade; import java.io.File; import java.util.LinkedList; import java.util.List; import static com.google.classyshark.silverghost.SilverGhostFacade.getGeneratedClassString; /** * The ClassyShark API usually used by build & continues integration toolchains */ public class Shark { private File archiveFile; private Shark(File archiveFile) { this.archiveFile = archiveFile; } public static Shark with(File archiveFile) { return new Shark(archiveFile); } /** * @param className class name to generate such as "com.bumptech.glide.request.target.BaseTarget" * @return */ public String getGeneratedClass(String className) { return getGeneratedClassString(className, archiveFile); } /** * @return list of class names */ public List getAllClassNames() { return SilverGhostFacade.getAllClassNames(archiveFile); } /** * @return manifest */ public String getManifest() { return SilverGhostFacade.getManifest(archiveFile); } /** * @return all methods */ public List getAllMethods() { return SilverGhostFacade.getAllMethods(archiveFile); } /** * @return all strings from all string tables */ public List getAllStrings() { return SilverGhostFacade.getAllStrings(archiveFile); } /** * * @return */ public boolean isMultiDex() { return SilverGhostFacade.isMultiDex(archiveFile); } public boolean isCustomMultiDex() { return SilverGhostFacade.isCustomMultiDex(archiveFile); } public static void main(String[] args) { File apk = new File("/Users/bfarber/Desktop/Scenarios/3 APKs/" + "com.google.samples.apps.iosched-333.apk"); Shark shark = Shark.with(apk); System.out.println( shark.getGeneratedClass("com.bumptech.glide.request.target.BaseTarget")); System.out.println(shark.getAllClassNames()); System.out.println(shark.getManifest()); System.out.println(shark.getAllMethods()); //System.out.println(shark.getAllStrings()); System.out.println(shark.isMultiDex()); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/Version.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark; /** * This class holds the current ClassyShark version */ public class Version { public static final int MAJOR = 8; public static final int MINOR = 2; } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/analytics/Analytics.java ================================================ /* * Copyright 2017 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.analytics; import com.google.classyshark.Version; // based on https://github.com/siddii/jgoogleanalytics public enum Analytics { INSTANCE; public void addActivation() { JGoogleAnalyticsTracker tracker = new JGoogleAnalyticsTracker( "ClassyShark-Activation", Version.MAJOR + "." + Version.MINOR, "UA-91889970-1"); FocusPoint focusPoint = new FocusPoint("Activation"); tracker.trackAsynchronously(focusPoint); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/analytics/FocusPoint.java ================================================ /* * Copyright 2015 Siddique Hameed * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.analytics; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; /** * Focus point of the application. It can represent data points like application load, application module load, user actions, error events etc. * * @author : Siddique Hameed * @version : 0.1 */ public class FocusPoint { private String name; private FocusPoint parentFocusPoint; private static final String URI_SEPARATOR = "/"; private static final String TITLE_SEPARATOR = "-"; public FocusPoint(String name) { this.name = name; } public FocusPoint(String name, FocusPoint parentFocusPoint) { this(name); this.parentFocusPoint = parentFocusPoint; } public String getName() { return name; } public void setParentTrackPoint(FocusPoint parentFocusPoint) { this.parentFocusPoint = parentFocusPoint; } public FocusPoint getParentFocusPoint() { return parentFocusPoint; } public String getContentURI() { StringBuffer contentURIBuffer = new StringBuffer(); getContentURI(contentURIBuffer, this); return contentURIBuffer.toString(); } public String getContentTitle() { StringBuffer titleBuffer = new StringBuffer(); getContentTitle(titleBuffer, this); return titleBuffer.toString(); } private void getContentURI(StringBuffer contentURIBuffer, FocusPoint focusPoint) { FocusPoint parentFocuPoint = focusPoint.getParentFocusPoint(); if (parentFocuPoint != null) { getContentURI(contentURIBuffer, parentFocuPoint); } contentURIBuffer.append(URI_SEPARATOR); contentURIBuffer.append(encode(focusPoint.getName())); } private String encode(String name) { try { return URLEncoder.encode(name, "UTF-8"); } catch (UnsupportedEncodingException e) { return name; } } private void getContentTitle(StringBuffer titleBuffer, FocusPoint focusPoint) { FocusPoint parentFocusPoint = focusPoint.getParentFocusPoint(); if (parentFocusPoint != null) { getContentTitle(titleBuffer, parentFocusPoint); titleBuffer.append(TITLE_SEPARATOR); } titleBuffer.append(encode(focusPoint.getName())); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/analytics/GoogleAnalytics_v1_URLBuildingStrategy.java ================================================ /* * Copyright 2015 Siddique Hameed * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.analytics; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Date; import java.util.Random; /** * URL building logic for the earlier versions of google analytics (urchin.js) * * @author : Siddique Hameed * @version : 0.1 */ public class GoogleAnalytics_v1_URLBuildingStrategy implements URLBuildingStrategy { private FocusPoint appFocusPoint; private String googleAnalyticsTrackingCode; private String refererURL = "http://www.BoxySystems.com"; private static final String TRACKING_URL_Prefix = "http://www.google-analytics.com/__utm.gif"; private static final Random random = new Random(); private static String hostName = "localhost"; static { try { hostName = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { //ignore this } } public GoogleAnalytics_v1_URLBuildingStrategy(String appName, String googleAnalyticsTrackingCode) { this.googleAnalyticsTrackingCode = googleAnalyticsTrackingCode; this.appFocusPoint = new FocusPoint(appName); } public GoogleAnalytics_v1_URLBuildingStrategy(String appName, String appVersion, String googleAnalyticsTrackingCode) { this.googleAnalyticsTrackingCode = googleAnalyticsTrackingCode; this.appFocusPoint = new FocusPoint(appVersion, new FocusPoint(appName)); } public String buildURL(FocusPoint focusPoint) { int cookie = random.nextInt(); int randomValue = random.nextInt(2147483647) - 1; long now = new Date().getTime(); // String $urchinUrl="http://www.google-analytics.com/__utm.gif?utmwv=1&utmn='.$var_utmn.'&utmsr=-&utmsc=-&utmul=-&utmje=0&utmfl=-&utmdt=-&utmhn='.$var_utmhn.'&utmr='.$var_referer.'&utmp='.$var_utmp." + // "'&utmac='.$var_utmac.'" + // "&utmcc=__utma%3D'.$var_cookie.'.'.$var_random.'.'.$var_today.'.'.$var_today.'.'.$var_today.'.2%3B%2B__utmb%3D'.$var_cookie.'%3B%2B__utmc%3D'.$var_cookie.'%3B%2B__utmz%3D'.$var_cookie.'.'.$var_today.'.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D'.$var_cookie.'.'.$var_uservar.'%3B"; focusPoint.setParentTrackPoint(appFocusPoint); StringBuffer url = new StringBuffer(TRACKING_URL_Prefix); url.append("?utmwv=1"); //Urchin/Analytics version url.append("&utmn=" + random.nextInt()); url.append("&utmcs=UTF-8"); //document encoding url.append("&utmsr=1440x900"); //screen resolution url.append("&utmsc=32-bit"); //color depth url.append("&utmul=en-us"); //user language url.append("&utmje=1"); //java enabled url.append("&utmfl=9.0%20%20r28"); //flash url.append("&utmcr=1"); //carriage return url.append("&utmdt=" + focusPoint.getContentTitle()); //The optimum keyword density //document title url.append("&utmhn=" + hostName);//document hostname url.append("&utmr=" + refererURL); //referer URL url.append("&utmp=" + focusPoint.getContentURI());//document page URL url.append("&utmac=" + googleAnalyticsTrackingCode);//Google Analytics account url.append("&utmcc=__utma%3D'" + cookie + "." + randomValue + "." + now + "." + now + "." + now + ".2%3B%2B__utmb%3D" + cookie + "%3B%2B__utmc%3D" + cookie + "%3B%2B__utmz%3D" + cookie + "." + now + ".2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D" + cookie); return url.toString(); } public void setRefererURL(String refererURL) { this.refererURL = refererURL; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/analytics/HTTPGetMethod.java ================================================ /* * Copyright 2015 Siddique Hameed * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.analytics; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; /** * Simple class peforming HTTP Get method on the requested url * * @author : Siddique Hameed * @version : 0.1 */ public class HTTPGetMethod { private static final String GET_METHOD_NAME = "GET"; private static final String SUCCESS_MESSAGE = "JGoogleAnalytics: Tracking Successful!"; private LoggingAdapter loggingAdapter = null; public void setLoggingAdapter(LoggingAdapter loggingAdapter) { this.loggingAdapter = loggingAdapter; } private static String uaName = null; // User Agent name private static String osString = "Unknown"; HTTPGetMethod() { // Initialise the static parameters if we need to. if (uaName == null) { uaName = "Java/" + System.getProperty("java.version"); // java version info appended // os string is architecture+osname+version concatenated with _ osString = System.getProperty("os.arch"); if (osString == null || osString.length() < 1) { osString = ""; } else { osString += "; "; osString += System.getProperty("os.name") + " " + System.getProperty("os.version"); } } } public void request(String urlString) { try { URL url = new URL(urlString); HttpURLConnection urlConnection = openURLConnection(url); urlConnection.setInstanceFollowRedirects(true); urlConnection.setRequestMethod(GET_METHOD_NAME); urlConnection.setRequestProperty("User-agent", uaName + " (" + osString + ")"); urlConnection.connect(); int responseCode = getResponseCode(urlConnection); if (responseCode != HttpURLConnection.HTTP_OK) { logError("JGoogleAnalytics: Error tracking, url=" + urlString); } else { logMessage(SUCCESS_MESSAGE); } } catch (Exception e) { logError(e.getMessage()); } } protected int getResponseCode(HttpURLConnection urlConnection) throws IOException { return urlConnection.getResponseCode(); } private HttpURLConnection openURLConnection(URL url) throws IOException { return (HttpURLConnection) url.openConnection(); } private void logMessage(String message) { if (loggingAdapter != null) { loggingAdapter.logMessage(message); } } private void logError(String errorMesssage) { if (loggingAdapter != null) { loggingAdapter.logError(errorMesssage); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/analytics/JGoogleAnalyticsTracker.java ================================================ /* * Copyright 2015 Siddique Hameed * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.analytics; /** * Main class for tracking google analytics data. * * @author : Siddique Hameed * @version : 0.1 * @see : http://JGoogleAnalytics.googlecode.com */ public class JGoogleAnalyticsTracker { private URLBuildingStrategy urlBuildingStrategy = null; private HTTPGetMethod httpRequest = new HTTPGetMethod(); private LoggingAdapter loggingAdapter; /** * Simple constructor passing the application name & google analytics tracking code * * @param appName Application name (For ex: "LibraryFinder") * @param googleAnalyticsTrackingCode (For ex: "UA-2184000-1") */ public JGoogleAnalyticsTracker(String appName, String googleAnalyticsTrackingCode) { this.urlBuildingStrategy = new GoogleAnalytics_v1_URLBuildingStrategy(appName, googleAnalyticsTrackingCode); } /** * Constructor passing the application name, application version & google analytics tracking code * * @param appName Application name (For ex: "LibraryFinder") * @param appVersion Application version (For ex: "1.3.1") * @param googleAnalyticsTrackingCode (For ex: "UA-2184000-1") */ public JGoogleAnalyticsTracker(String appName, String appVersion, String googleAnalyticsTrackingCode) { this.urlBuildingStrategy = new GoogleAnalytics_v1_URLBuildingStrategy(appName, appVersion, googleAnalyticsTrackingCode); } /** * Setter injection for URLBuildingStrategy incase if you want to use a different url building logic. * * @param urlBuildingStrategy implemented instance of URLBuildingStrategy */ public void setUrlBuildingStrategy(URLBuildingStrategy urlBuildingStrategy) { this.urlBuildingStrategy = urlBuildingStrategy; } /** * Setter injection for LoggingAdpater. You can hook up log4j, System.out or any other loggers you want. * * @param loggingAdapter implemented instance of LoggingAdapter */ public void setLoggingAdapter(LoggingAdapter loggingAdapter) { this.loggingAdapter = loggingAdapter; httpRequest.setLoggingAdapter(loggingAdapter); } /** * Track the focusPoint in the application synchronously.
* Please be cognizant while using this method. Since, it would have a peformance hit on the actual application. * Use it unless it's really needed * * @param focusPoint Focus point of the application like application load, application module load, user actions, error events etc. */ public void trackSynchronously(FocusPoint focusPoint) { logMessage("JGoogleAnalytics: Tracking synchronously focusPoint=" + focusPoint.getContentTitle()); httpRequest.request(urlBuildingStrategy.buildURL(focusPoint)); } /** * Track the focusPoint in the application asynchronously.
* * @param focusPoint Focus point of the application like application load, application module load, user actions, error events etc. */ public void trackAsynchronously(FocusPoint focusPoint) { logMessage("JGoogleAnalytics: Tracking Asynchronously focusPoint=" + focusPoint.getContentTitle()); new TrackingThread(focusPoint).start(); } private void logMessage(String message) { if (loggingAdapter != null) { loggingAdapter.logMessage(message); } } private class TrackingThread extends Thread { private FocusPoint focusPoint; public TrackingThread(FocusPoint focusPoint) { this.focusPoint = focusPoint; this.setPriority(Thread.MIN_PRIORITY); } public void run() { httpRequest.request(urlBuildingStrategy.buildURL(focusPoint)); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/analytics/LoggingAdapter.java ================================================ /* * Copyright 2015 Siddique Hameed * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.analytics; /** * Interface for logging adapter. You can hook up log4j, System.out or any other loggers you want. * * @author : Siddique Hameed * @version : 0.1 */ public interface LoggingAdapter { public void logError(String errorMessage); public void logMessage(String message); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/analytics/URLBuildingStrategy.java ================================================ /* * Copyright 2015 Siddique Hameed * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.analytics; /** * Interface for the URL building strategy * * @author : Siddique Hameed * @version : 0.1 */ public interface URLBuildingStrategy { public String buildURL(FocusPoint focusPoint); public void setRefererURL(String refererURL); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/cli/CliMode.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.cli; import com.google.classyshark.updater.UpdateManager; import java.io.File; import java.util.List; import static com.google.classyshark.silverghost.SilverGhostFacade.exportArchive; import static com.google.classyshark.silverghost.SilverGhostFacade.exportClassFromApk; import static com.google.classyshark.silverghost.SilverGhostFacade.inspectApk; import static com.google.classyshark.silverghost.SilverGhostFacade.inspectPackages; /** * Command line mode */ public class CliMode { private static final String ERROR_MESSAGE = "Usage: java -jar ClassyShark.jar [-options] [args...]\n" + " (to execute a ClassyShark on binary archive jar/apk/dex/class)\n" + "where options include:\n" + " -open\t open an archive with GUI \n" + " -export\t export to file \n" + " -methodcounts\t packages with method counts \n" + " -inspect experimental prints apk analysis\n" + " -update\tupdates ClassyShark" + "\nwhere args is an optional classname\n"; private CliMode() { } public static void with(List args) { if (args.size() < 2) { System.err.println("missing command line arguments " + "\n\n\n" + ERROR_MESSAGE); return; } File archiveFile = new File(args.get(1)); if (!archiveFile.exists()) { System.err.println("File doesn't exist ==> " + archiveFile + "\n\n\n" + ERROR_MESSAGE); return; } final String operand = args.get(0).toLowerCase(); switch (operand) { case "-export": if (args.size() == 2) { exportArchive(args); } else { exportClassFromApk(args); } break; case "-inspect": inspectApk(args); break; case "-methodcounts": inspectPackages(args); break; case "-update": UpdateManager.getInstance().checkVersionConsole(); break; default: System.err.println("wrong operand ==> " + operand + "\n\n\n" + ERROR_MESSAGE); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/GuiMode.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui; import com.google.classyshark.gui.panel.ClassySharkPanel; import com.google.classyshark.gui.theme.Theme; import com.google.classyshark.gui.theme.ThemeManager; import com.google.classyshark.updater.UpdateManager; import javax.swing.JFrame; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.WindowConstants; import java.io.File; import java.util.List; /** * GUI mode */ public class GuiMode { private static Theme theme = ThemeManager.getCurrentTheme(); private GuiMode() { } public static void with(final List argsAsArray) { UpdateManager.getInstance().checkVersionGui(); javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { buildAndShowClassyShark(argsAsArray); } }); } public static Theme getTheme(){ return theme; } private static void buildAndShowClassyShark(List cmdLineArgs) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (UnsupportedLookAndFeelException | IllegalAccessException | ClassNotFoundException | InstantiationException | SecurityException ex) { ex.printStackTrace(); } JFrame frame = buildClassySharkFrame(cmdLineArgs); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); theme.applyTo(frame); } private static JFrame buildClassySharkFrame(List cmdLineArgs) { JFrame result = new JFrame("ClassyShark"); theme.applyTo(result); // no arguments if (cmdLineArgs.size() == 0) { result.getContentPane().add(new ClassySharkPanel(result)); return result; } // only archive if (cmdLineArgs.size() == 2) { result.getContentPane().add( new ClassySharkPanel(result, new File(cmdLineArgs.get(1)))); return result; } // archive and a class file if (cmdLineArgs.size() == 3) { result.getContentPane().add( new ClassySharkPanel(result, new File(cmdLineArgs.get(1)), cmdLineArgs.get(2))); return result; } return result; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/ArchiveDisplayer.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel; import java.io.File; public interface ArchiveDisplayer { void displayArchive(File file); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/ClassySharkPanel.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel; import com.google.classyshark.gui.GuiMode; import com.google.classyshark.gui.panel.chart.RingChartPanel; import com.google.classyshark.gui.panel.displayarea.DisplayArea; import com.google.classyshark.gui.panel.displayarea.IDisplayArea; import com.google.classyshark.gui.panel.io.CurrentFolderConfig; import com.google.classyshark.gui.panel.io.FileChooserUtils; import com.google.classyshark.gui.panel.io.RecentArchivesConfig; import com.google.classyshark.gui.panel.methodscount.MethodsCountPanel; import com.google.classyshark.gui.panel.toolbar.KeyUtils; import com.google.classyshark.gui.panel.toolbar.Toolbar; import com.google.classyshark.gui.panel.toolbar.ToolbarController; import com.google.classyshark.gui.panel.tree.FilesTree; import com.google.classyshark.gui.settings.SettingsFrame; import com.google.classyshark.gui.theme.Theme; import com.google.classyshark.silverghost.SilverGhost; import com.google.classyshark.silverghost.TokensMapper; import com.google.classyshark.silverghost.exporter.Exporter; import com.google.classyshark.silverghost.methodscounter.ClassNode; import com.google.classyshark.silverghost.translator.Translator; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.File; import java.util.ArrayList; import java.util.List; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.SwingWorker; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.filechooser.FileFilter; /** * App controller, general app structure MVM ==> Model - View - Mediator (this class) */ public class ClassySharkPanel extends JPanel implements ToolbarController, ViewerController, KeyListener { private static final boolean IS_CLASSNAME_FROM_MOUSE_CLICK = true; private static final boolean VIEW_TOP_CLASS = true; public static final String ANDROID_MANIFEST_XML_SEARCH = "AndroidManifest.xml - "; private JFrame parentFrame; private Toolbar toolbar; private JSplitPane jSplitPane; private MethodsCountPanel methodsCountPanel; private int dividerLocation = 0; private IDisplayArea displayArea; private FilesTree filesTree; private RingChartPanel ringChartPanel; private boolean isDataLoaded = false; private final Theme theme = GuiMode.getTheme(); private SilverGhost silverGhost = new SilverGhost(); public ClassySharkPanel(JFrame frame, File archive, String fullClassName) { this(frame); silverGhost.setBinaryArchive(archive); updateUiAfterArchiveReadAndLoadClass(fullClassName); } public ClassySharkPanel(JFrame frame, File archive) { this(frame); silverGhost.setBinaryArchive(archive); displayArchive(silverGhost.getBinaryArchive()); } public ClassySharkPanel(JFrame frame) { super(false); buildUI(); parentFrame = frame; toolbar.setText(""); theme.applyTo(this); } @Override public void onSelectedTypeClassFromMouseClick(String selectedClass) { for (String clazz : silverGhost.getImportsForCurrentClass()) { if (clazz.contains(selectedClass)) { onSelectedImportFromMouseClick(clazz); return; } } for (String clazz : silverGhost.getAllClassNames()) { if (clazz.contains(selectedClass)) { onSelectedImportFromMouseClick(clazz); return; } } } @Override public void onSelectedImportFromMouseClick(String className) { if (silverGhost.getAllClassNames().contains(className)) { onSelectedClassName(className); } } @Override public void onSelectedClassName(String className) { fillDisplayArea(className, VIEW_TOP_CLASS, IS_CLASSNAME_FROM_MOUSE_CLICK); } @Override public void openArchive() { final JFileChooser fc = new JFileChooser(); fc.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { return FileChooserUtils.acceptFile(f); } @Override public String getDescription() { return FileChooserUtils.getFileChooserDescription(); } }); fc.setCurrentDirectory(CurrentFolderConfig.INSTANCE.getCurrentDirectory()); int returnVal = fc.showOpenDialog(this); toolbar.setText(""); if (returnVal == JFileChooser.APPROVE_OPTION) { File resultFile = fc.getSelectedFile(); CurrentFolderConfig.INSTANCE.setCurrentDirectory(fc.getCurrentDirectory()); RecentArchivesConfig.INSTANCE.addArchive(resultFile.getName(), fc.getCurrentDirectory()); displayArchive(resultFile); } } @Override public void onGoBackPressed() { toolbar.setText(""); displayArea.displayClassNames(silverGhost.getAllClassNames(), ""); silverGhost.initClassNameFiltering(); } @Override public void onViewTopClassPressed() { final String textFromTypingArea = toolbar.getText(); fillDisplayArea(textFromTypingArea, VIEW_TOP_CLASS, !IS_CLASSNAME_FROM_MOUSE_CLICK); } @Override public void onChangedTextFromTypingArea(String selectedLine) { fillDisplayArea(selectedLine, !VIEW_TOP_CLASS, !IS_CLASSNAME_FROM_MOUSE_CLICK); } @Override public void onMappingsButtonPressed() { final JFileChooser fc = new JFileChooser(); fc.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { return true; } @Override public String getDescription() { return ""; } }); fc.setCurrentDirectory(CurrentFolderConfig.INSTANCE.getCurrentDirectory()); int returnVal = fc.showOpenDialog(this); toolbar.setText(""); if (returnVal == JFileChooser.APPROVE_OPTION) { File resultFile = fc.getSelectedFile(); readMappingFile(resultFile); } } @Override public void onExportButtonPressed() { SwingWorker worker = new SwingWorker() { @Override protected Void doInBackground() throws Exception { Exporter.writeCurrentClass(silverGhost.getCurrentClassName(), silverGhost.getCurrentClassContent()); Exporter.writeArchive(silverGhost.getBinaryArchive(), silverGhost.getAllClassNames()); return null; } protected void done() { } }; worker.execute(); } @Override public void onChangeLeftPaneVisibility(boolean visible) { if (visible) { jSplitPane.setDividerLocation(dividerLocation); } else { dividerLocation = jSplitPane.getDividerLocation(); } jSplitPane.getLeftComponent().setVisible(visible); jSplitPane.updateUI(); } @Override public void onSettingsButtonPressed() { SettingsFrame frame = new SettingsFrame(); } @Override public void displayArchive(File binaryArchive) { silverGhost.setBinaryArchive(binaryArchive); if (parentFrame != null) { parentFrame.setTitle(silverGhost.getBinaryArchive().getName()); } readArchiveAndFillDisplayArea(null); toolbar.activateNavigationButtons(); filesTree.setVisibleRoot(); methodsCountPanel.loadFile(silverGhost.getBinaryArchive()); } @Override public void onSelectedMethodCount(ClassNode rootNode) { ringChartPanel.setRootNode(rootNode); } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { if (!isDataLoaded) { openArchive(); return; } if (KeyUtils.isLeftArrowPressed(e)) { openArchive(); return; } final String textFromTypingArea = processKeyPressWithTypedText(e, toolbar.getText()); final boolean isViewTopClassKeyPressed = KeyUtils.isRightArrowPressed(e) || KeyUtils.isCommandKeyPressed(e); fillDisplayArea(textFromTypingArea, isViewTopClassKeyPressed, !ClassySharkPanel.IS_CLASSNAME_FROM_MOUSE_CLICK); } private static String processKeyPressWithTypedText(KeyEvent e, String text) { String result = text; if (KeyUtils.isDeletePressed(e)) { if (!text.isEmpty()) { result = text.substring(0, text.length() - 1); return result; } } if (KeyUtils.isLetterOrDigit(e)) { result += e.getKeyChar(); } return result; } @Override public void keyReleased(KeyEvent e) { } private void buildUI() { BorderLayout borderLayout = new BorderLayout(); setLayout(borderLayout); ringChartPanel = new RingChartPanel(this); toolbar = new Toolbar(this); add(toolbar, BorderLayout.NORTH); toolbar.addKeyListenerToTypingArea(this); displayArea = new DisplayArea(this); final JScrollPane rightScrollPane = new JScrollPane(displayArea.onAddComponentToPane()); theme.applyTo(rightScrollPane); filesTree = new FilesTree(this); JTabbedPane jTabbedPane = new JTabbedPane(); JScrollPane leftScrollPane = new JScrollPane(filesTree.getJTree()); theme.applyTo(leftScrollPane); jTabbedPane.addTab("Classes", leftScrollPane); methodsCountPanel = new MethodsCountPanel(this); jTabbedPane.addTab("Methods count", methodsCountPanel); theme.applyTo(jTabbedPane); jTabbedPane.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { int dividerLocation1 = jSplitPane.getDividerLocation(); JTabbedPane jTabbedPane1 = (JTabbedPane) e.getSource(); if (jTabbedPane1.getSelectedIndex() == 0) { jSplitPane.setRightComponent(rightScrollPane); } else { jSplitPane.setRightComponent(ringChartPanel); } jSplitPane.setDividerLocation(dividerLocation1); } }); jSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); jSplitPane.setDividerSize(3); jSplitPane.setPreferredSize(new Dimension(1000, 700)); jSplitPane.add(jTabbedPane, JSplitPane.LEFT); jSplitPane.add(rightScrollPane, JSplitPane.RIGHT); jSplitPane.getLeftComponent().setVisible(true); jSplitPane.setDividerLocation(300); theme.applyTo(jSplitPane); add(jSplitPane, BorderLayout.CENTER); } private void updateUiAfterArchiveReadAndLoadClass(String className) { if (parentFrame != null) { parentFrame.setTitle(silverGhost.getBinaryArchive().getName()); } readArchiveAndFillDisplayArea(className); toolbar.activateNavigationButtons(); filesTree.setVisibleRoot(); methodsCountPanel.loadFile(silverGhost.getBinaryArchive()); } private void readArchiveAndFillDisplayArea(final String className) { SwingWorker worker = new SwingWorker() { @Override protected Void doInBackground() throws Exception { silverGhost.readContents(); return null; } @Override protected void done() { if (silverGhost.isArchiveError()) { filesTree.fillArchive(new File("ERROR"), new ArrayList(), silverGhost.getComponents()); displayArea.displayError(); return; } filesTree.fillArchive(silverGhost.getBinaryArchive(), silverGhost.getAllClassNames(), silverGhost.getComponents()); if (className != null) { onSelectedClassName(className); } else { displayArea.displaySharkey(); } isDataLoaded = true; } }; worker.execute(); } private void readMappingFile(final File resultFile) { SwingWorker worker = new SwingWorker() { private TokensMapper reverseMappings; @Override protected Void doInBackground() throws Exception { reverseMappings = silverGhost.readMappingFile(resultFile); return null; } protected void done() { silverGhost.addMappings(reverseMappings); } }; worker.execute(); } private void fillDisplayArea(final String textFromTypingArea, final boolean viewTopClass, final boolean viewMouseClickedClass) { toolbar.setTypingAreaCaret(); SwingWorker worker = new SwingWorker() { private List displayedClassTokens = new ArrayList<>(); private List manifestSearchResultsTokens = new ArrayList<>() ; private List filteredClassNames = new ArrayList<>(); private String className = ""; @Override protected Void doInBackground() throws Exception { if (viewMouseClickedClass) { className = textFromTypingArea; convertToManifestIfNeeded(); silverGhost.translateArchiveElement(className); displayedClassTokens = silverGhost.getArchiveElementTokens(); } else if (viewTopClass) { className = silverGhost.getAutoCompleteClassName(); silverGhost.translateArchiveElement(className); displayedClassTokens = silverGhost.getArchiveElementTokens(); } else { className = textFromTypingArea; convertToManifestIfNeeded(); filteredClassNames = silverGhost.filter(className); manifestSearchResultsTokens = silverGhost.getManifestMatches(textFromTypingArea); checkIfOneClassAndPrepareTokens(); } return null; } @Override protected void done() { if(className.isEmpty()) return; if (isUserClickedOnSearchResult()) { if (clickedOnClass()) { toolbar.setText(className); displayArea.displayClass(displayedClassTokens, textFromTypingArea); } else { toolbar.setText("AndroidManifest.xml"); displayManifestWithSpecificLine(); } } else { if (noResults()) { displayArea.displayError(); } else if (oneResult()) { displayArea.displayClass(displayedClassTokens, ""); } else { displayArea.displaySearchResults(filteredClassNames, manifestSearchResultsTokens, textFromTypingArea); } } } private boolean isUserClickedOnSearchResult() { return viewTopClass || viewMouseClickedClass; } private boolean clickedOnClass() { return !displayedClassTokens.isEmpty(); } private void displayManifestWithSpecificLine() { silverGhost.translateArchiveElement("AndroidManifest.xml"); displayedClassTokens = silverGhost.getArchiveElementTokens(); displayArea.displayClass(displayedClassTokens, className); } private void checkIfOneClassAndPrepareTokens() { if (oneResult()) { String topClassName = filteredClassNames.get(0); silverGhost.translateArchiveElement(topClassName); displayedClassTokens = silverGhost.getArchiveElementTokens(); } } private boolean noResults() { return ((filteredClassNames.size() == 0) && (manifestSearchResultsTokens.size() == 0)); } private boolean oneResult() { return filteredClassNames.size() == 1; } private void convertToManifestIfNeeded() { if (textFromTypingArea.startsWith(ANDROID_MANIFEST_XML_SEARCH)) { className = "AndroidManifest.xml"; } } }; worker.execute(); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/FileTransferHandler.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel; import com.google.classyshark.gui.panel.io.CurrentFolderConfig; import com.google.classyshark.gui.panel.io.RecentArchivesConfig; import javax.swing.JComponent; import javax.swing.TransferHandler; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.File; import java.io.IOException; import java.util.List; import static com.google.classyshark.gui.panel.io.FileChooserUtils.isSupportedArchiveFile; public class FileTransferHandler extends TransferHandler { private final ArchiveDisplayer archiveDisplayer; public FileTransferHandler(ArchiveDisplayer archiveDisplayer) { this.archiveDisplayer = archiveDisplayer; } public int getSourceActions(JComponent c) { return COPY_OR_MOVE; } public boolean canImport(TransferSupport ts) { return ts.isDataFlavorSupported(DataFlavor.javaFileListFlavor); } public boolean importData(TransferSupport ts) { try { @SuppressWarnings("rawtypes") List data = (List) ts.getTransferable().getTransferData( DataFlavor.javaFileListFlavor); if (data.size() < 1) { return false; } for (Object item : data) { File file = (File) item; if(isSupportedArchiveFile(file)) { CurrentFolderConfig.INSTANCE.setCurrentDirectory(file.getParentFile()); RecentArchivesConfig.INSTANCE.addArchive(file.getName(), file.getParentFile()); archiveDisplayer.displayArchive(file); } } return true; } catch (UnsupportedFlavorException e) { return false; } catch (IOException e) { return false; } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/ViewerController.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel; import com.google.classyshark.silverghost.methodscounter.ClassNode; public interface ViewerController extends ArchiveDisplayer { void onSelectedClassName(String className); void onSelectedImportFromMouseClick(String classNameFromImportStatement); void onSelectedTypeClassFromMouseClick(String word); void onSelectedMethodCount(ClassNode rootNode); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/chart/RingChart.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.chart; import com.google.classyshark.gui.GuiMode; import com.google.classyshark.silverghost.methodscounter.ClassNode; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; public class RingChart { private static final int MARGIN = 20; private static final int DEFAULT_MAX_DEPTH = 2; private Color OTHERS_COLOR = Color.GRAY; private static final Color[] PALETTE = new Color[]{ new Color(0x5DA5DA), new Color(0xFAA43A), new Color(0x60BD68), new Color(0xF17CB0), new Color(0xB2912F), new Color(0xB276B2), new Color(0xDECF3F), new Color(0xF15854), new Color(0x4D4D4D)}; private static final Color[][] L2_PALLETES = new Color[][] { { new Color(0x5dc1d9), new Color(0x5db8d9), new Color(0x5dafd9), new Color(0x5da5d9), new Color(0x5d9cd9), new Color(0x5d93d9), new Color(0x5d89d9) }, { new Color(0xfa7839), new Color(0xfa8639), new Color(0xfa9539), new Color(0xfaa339), new Color(0xfab239), new Color(0xfac039), new Color(0xfacf39) }, { new Color(0x6dbd60), new Color(0x66bd60), new Color(0x60bd61), new Color(0x60bd68), new Color(0x60bd6f), new Color(0x60bd76), new Color(0x60bd7d) }, { new Color(0xf27ccc), new Color(0xf27cc3), new Color(0xf27cba), new Color(0xf27cb1), new Color(0xf27ca8), new Color(0xf27c9f), new Color(0xf27c96) }, { new Color(0xb3742e), new Color(0xb37e2e), new Color(0xb3882e), new Color(0xb3912e), new Color(0xb39b2e), new Color(0xb3a52e), new Color(0xb3af2e) }, { new Color(0xa576b3), new Color(0xa976b3), new Color(0xae76b3), new Color(0xb376b3), new Color(0xb376ae), new Color(0xb376a9), new Color(0xb376a5) }, { new Color(0xdeaa3e), new Color(0xdeb63e), new Color(0xdec23e), new Color(0xdece3e), new Color(0xdeda3e), new Color(0xd6de3e), new Color(0xcade3e) }, { new Color(0xf25573), new Color(0xf25567), new Color(0xf2555b), new Color(0xf25a55), new Color(0xf26655), new Color(0xf27255), new Color(0xf27d55) }, { new Color(0x5C5C5C), new Color(0x666666), new Color(0x707070), new Color(0x7A7A7A), new Color(0x858585), new Color(0x8F8F8F), new Color(0x999999) } }; private int maxDepth; private Map colorClassNodeMap = new HashMap<>(); private BufferedImage image; private ClassNode selectedNode; public RingChart() { this(DEFAULT_MAX_DEPTH); } public RingChart(int maxDepth) { this.maxDepth = maxDepth; } public void setSelectedNode(ClassNode selectedNode) { this.selectedNode = selectedNode; } public ClassNode getSelectedNode() { return selectedNode; } public void render(int width, int height, ClassNode rootNode, Graphics g) { image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D imageG2d = (Graphics2D)image.getGraphics(); imageG2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); imageG2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); imageG2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); imageG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); imageG2d.setComposite(AlphaComposite.Src); imageG2d.setColor(GuiMode.getTheme().getBackgroundColor()); imageG2d.fillRect(0, 0, width, height); int graphWidth = width - MARGIN * 2; int graphHeight = height - MARGIN * 2; imageG2d.translate(MARGIN, MARGIN); int size = Math.min(graphWidth, graphHeight); renderNode(graphWidth, graphHeight, size, 0, 360, rootNode, imageG2d, 1, PALETTE); imageG2d.dispose(); g.drawImage(image, 0, 0, null); g.dispose(); } public ClassNode getClassNodeAt(int x, int y) { if (image == null) { return null; } int color = image.getRGB(x,y); return colorClassNodeMap.get(color); } private void renderNode(int width, int height, int radius, int startAngle, int endAngle, ClassNode rootNode, Graphics2D g2d, int depth, Color[] pallete) { if (rootNode.getChildNodes().isEmpty()) { return; } int nodeStartAngle; int nodeEndAngle = startAngle; int angleSize = endAngle - startAngle; int r = (radius / maxDepth) * depth; int x = (width - r) / 2; int y = (height - r) / 2; int currentNode = 0; int currentColor = 0; List nodes = new ArrayList<>(rootNode.getChildNodes().values()); Collections.sort(nodes, new Comparator() { @Override public int compare(ClassNode o1, ClassNode o2) { return Integer.compare(o2.getMethodCount(), o1.getMethodCount()); } }); while (nodeEndAngle < endAngle) { ClassNode node = nodes.get(currentNode); nodeStartAngle = nodeEndAngle; String title = node.getKey(); Color color = pallete[currentColor]; nodeEndAngle = (int) ((double) node.getMethodCount() / rootNode.getMethodCount() * angleSize + nodeEndAngle); if (currentNode == nodes.size() - 1) { nodeEndAngle = endAngle; } else if (currentColor == pallete.length - 1 || 360 - nodeEndAngle < 5) { currentColor = pallete.length - 1; nodeEndAngle = endAngle; title = "Others"; color = OTHERS_COLOR; } if (selectedNode != null && node == selectedNode) { color = getHighlightColor(color); } if (color != OTHERS_COLOR) { colorClassNodeMap.put(color.getRGB(), node); } if (depth < maxDepth && currentColor != pallete.length - 1) { Color[] newpallete = L2_PALLETES[currentColor]; renderNode( width, height, radius, nodeStartAngle, nodeEndAngle, node, g2d, depth + 1, newpallete); } g2d.setColor(color); g2d.fillArc(x, y, r, r, nodeStartAngle, nodeEndAngle - nodeStartAngle); g2d.setColor(Color.BLACK); g2d.drawArc(x, y, r, r, nodeStartAngle, nodeEndAngle - nodeStartAngle); //Render Lines between angles AffineTransform saved = g2d.getTransform(); int cx = width / 2; int cy = height / 2; g2d.translate(cx, cy); double rads = Math.toRadians(nodeEndAngle); int py = (int)Math.round(Math.sin(rads) * (r / 2)) * -1; int px = (int)Math.round(Math.cos(rads) * (r / 2)); g2d.drawLine(0, 0, px, py); //Render text int r2 = (radius / maxDepth) * (depth - 1); r2 = r + (r2 - r)/2; rads = Math.toRadians(nodeStartAngle + (nodeEndAngle - nodeStartAngle) / 2); py = (int)Math.round(Math.sin(rads) * (r2 / 2))* -1; px = (int)Math.round(Math.cos(rads) * (r2 / 2)); g2d.drawString(title, px, py); g2d.setTransform(saved); currentNode++; currentColor++; } } private Color getHighlightColor(Color color) { float hsbVals[] = Color.RGBtoHSB( color.getRed(), color.getGreen(), color.getBlue(), null); return Color.getHSBColor(hsbVals[0], hsbVals[1] * 0.7f, hsbVals[2]); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/chart/RingChartPanel.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.chart; import com.google.classyshark.gui.GuiMode; import com.google.classyshark.gui.panel.FileTransferHandler; import com.google.classyshark.gui.panel.ViewerController; import com.google.classyshark.silverghost.methodscounter.ClassNode; import javax.swing.JPanel; import javax.swing.ToolTipManager; import java.awt.Graphics; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; public class RingChartPanel extends JPanel { private RingChart ringChart = new RingChart(); private ClassNode rootNode; public RingChartPanel(final ViewerController viewerController) { super(); ToolTipManager.sharedInstance().registerComponent(this); GuiMode.getTheme().applyTo(this); this.addMouseMotionListener(new MouseMotionListener() { @Override public void mouseDragged(MouseEvent e) { } @Override public void mouseMoved(MouseEvent e) { ClassNode prevSelectedNode = ringChart.getSelectedNode(); ClassNode currSelectedNode = ringChart.getClassNodeAt(e.getX(), e.getY()); if (currSelectedNode == null && prevSelectedNode == null) { return; } if (currSelectedNode == null || !currSelectedNode.equals(prevSelectedNode)) { ringChart.setSelectedNode(currSelectedNode); repaint(); } } }); this.addMouseListener(new MouseListener() { @Override public void mouseClicked(MouseEvent e) { ClassNode classNode = ringChart.getClassNodeAt(e.getX(), e.getY()); if (classNode == null) { return; } if (classNode.getChildNodes() != null && !classNode.getChildNodes().isEmpty()) { viewerController.onSelectedMethodCount(classNode); } } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } }); setTransferHandler(new FileTransferHandler(viewerController)); } @Override public String getToolTipText(MouseEvent e) { int x = e.getX(); int y = e.getY(); ClassNode classNode = ringChart.getClassNodeAt(x, y); if (classNode == null) return null; return classNode.getKey() + ": " + classNode.getMethodCount(); } public void setRootNode(ClassNode rootNode) { this.rootNode = rootNode; repaint(); } @Override public void paint(Graphics g) { if (rootNode != null) { ringChart.render(getWidth(), getHeight(), rootNode, g); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/displayarea/BatchDocument.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.displayarea; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.Element; import java.util.ArrayList; class BatchDocument extends DefaultStyledDocument { private static final char[] EOL_ARRAY = { '\n' }; private ArrayList batch = null; public BatchDocument() { batch = new ArrayList(); } public void appendBatchStringNoLineFeed(String str, AttributeSet a) { a = a.copyAttributes(); char[] chars = str.toCharArray(); batch.add(new ElementSpec( a, ElementSpec.ContentType, chars, 0, str.length())); } public void appendBatchLineFeed(AttributeSet a) { batch.add(new ElementSpec( a, ElementSpec.ContentType, EOL_ARRAY, 0, 1)); Element paragraph = getParagraphElement(0); AttributeSet pattern = paragraph.getAttributes(); batch.add(new ElementSpec(null, ElementSpec.EndTagType)); batch.add(new ElementSpec(pattern, ElementSpec.StartTagType)); } public void processBatchUpdates(int offs) throws BadLocationException { ElementSpec[] inserts = new ElementSpec[batch.size()]; batch.toArray(inserts); super.insert(offs, inserts); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/displayarea/DisplayArea.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.displayarea; import com.google.classyshark.gui.GuiMode; import com.google.classyshark.gui.panel.FileTransferHandler; import com.google.classyshark.gui.panel.ViewerController; import com.google.classyshark.gui.panel.displayarea.doodles.Doodle; import com.google.classyshark.gui.theme.Theme; import com.google.classyshark.silverghost.translator.Translator; import com.google.classyshark.silverghost.translator.java.JavaTranslator; import java.awt.Color; import java.awt.Component; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.List; import java.util.StringTokenizer; import javax.swing.JFrame; import javax.swing.JTextPane; import javax.swing.WindowConstants; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.Document; import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.Utilities; /** * the area to display lists of classes and individual class */ public class DisplayArea implements IDisplayArea { private enum DisplayDataState { SHARKEY, CLASSES_LIST, INSIDE_CLASS, ERROR } private final JTextPane jTextPane; private Style style; private final Theme theme = GuiMode.getTheme(); private DisplayDataState displayDataState; public DisplayArea(final ViewerController viewerController) { jTextPane = new JTextPane(); theme.applyTo(jTextPane); jTextPane.setDragEnabled(true); jTextPane.setTransferHandler(new FileTransferHandler(viewerController)); jTextPane.setEditable(false); jTextPane.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (displayDataState == DisplayDataState.SHARKEY) { return; } if (e.getButton() != MouseEvent.BUTTON1) { return; } if (e.getClickCount() != 2) { return; } int offset = jTextPane.viewToModel(e.getPoint()); try { int rowStart = Utilities.getRowStart(jTextPane, offset); int rowEnd = Utilities.getRowEnd(jTextPane, offset); String selectedLine = jTextPane.getText().substring(rowStart, rowEnd); System.out.println(selectedLine); if (displayDataState == DisplayDataState.CLASSES_LIST || selectedLine.endsWith(".dex")) { viewerController.onSelectedClassName(selectedLine); } else if (displayDataState == DisplayDataState.INSIDE_CLASS) { if (selectedLine.contains("import")) { viewerController.onSelectedImportFromMouseClick( getClassNameFromImportStatement(selectedLine)); } else { rowStart = Utilities.getWordStart(jTextPane, offset); rowEnd = Utilities.getWordEnd(jTextPane, offset); String word = jTextPane.getText().substring(rowStart, rowEnd); viewerController.onSelectedTypeClassFromMouseClick(word); } } } catch (BadLocationException e1) { e1.printStackTrace(); } } public String getClassNameFromImportStatement(String selectedLine) { final String IMPORT = "import "; int start = selectedLine.indexOf(IMPORT) + IMPORT.length(); String result = selectedLine.trim().substring(start, selectedLine.indexOf(";")); return result; } }); jTextPane.addKeyListener(new KeyListener() { @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { Toolkit toolkit = Toolkit.getDefaultToolkit(); int ctrlModifier = toolkit.getMenuShortcutKeyMask(); //check if the modifier: ctrl for linux, windows or command for mac is pressed // with the'c' key if (e.getKeyChar() == 'c' && (e.getModifiers() & ctrlModifier) == ctrlModifier) { String copyText = jTextPane.getSelectedText(); //if there is no selection, copy the entire text if (copyText == null) { copyText = jTextPane.getText(); } //Add the text to the clipboard toolkit.getSystemClipboard().setContents(new StringSelection(copyText), null); } } @Override public void keyReleased(KeyEvent e) { } }); displaySharkey(); } @Override public Component onAddComponentToPane() { return this.jTextPane; } @Override public void displaySearchResults(List filteredClassNames, List displayedManifestSearchResultsTokens, String textFromTypingArea) { displayDataState = DisplayDataState.CLASSES_LIST; StyleConstants.setFontSize(style, 20); StyleConstants.setForeground(style, theme.getIdentifiersColor()); clearText(); Document doc = new DefaultStyledDocument(); jTextPane.setDocument(doc); StyleConstants.setFontSize(style, 20); StyleConstants.setBackground(style, theme.getBackgroundColor()); fillTokensToDoc(displayedManifestSearchResultsTokens, doc, true); StyleConstants.setFontSize(style, 20); StyleConstants.setForeground(style, theme.getIdentifiersColor()); StyleConstants.setBackground(style, theme.getBackgroundColor()); int displayedClassLimit = 50; if(filteredClassNames.size() < displayedClassLimit) { displayedClassLimit = filteredClassNames.size(); } for (int i = 0; i < displayedClassLimit; i++) { try { doc.insertString(doc.getLength(), filteredClassNames.get(i) + "\n", style); } catch (BadLocationException e) { e.printStackTrace(); } } jTextPane.setDocument(doc); jTextPane.setCaretPosition(1); } @Override public void displayClassNames(List classNamesToShow, String inputText) { StyleConstants.setFontSize(style, 20); StyleConstants.setForeground(style, theme.getIdentifiersColor()); StyleConstants.setBackground(style, theme.getBackgroundColor()); if (classNamesToShow.size() > 50) { displayAllClassesNames(classNamesToShow); return; } displayDataState = DisplayDataState.CLASSES_LIST; clearText(); int matchIndex; String beforeMatch = ""; String match; String afterMatch = ""; Document doc = jTextPane.getDocument(); for (String className : classNamesToShow) { matchIndex = className.indexOf(inputText); if (matchIndex > -1) { beforeMatch = className.substring(0, matchIndex); match = className.substring(matchIndex, matchIndex + inputText.length()); afterMatch = className.substring(matchIndex + inputText.length(), className.length()); } else { // we are here by camel match // i.e. 2-3 letters that fits // to class name match = className; } try { doc.insertString(doc.getLength(), beforeMatch, style); StyleConstants.setBackground(style, theme.getSelectionBgColor()); doc.insertString(doc.getLength(), match, style); StyleConstants.setBackground(style, theme.getBackgroundColor()); doc.insertString(doc.getLength(), afterMatch + "\n", style); } catch (BadLocationException e) { e.printStackTrace(); } } jTextPane.setDocument(doc); } private void displayAllClassesNames(List classNames) { long start = System.currentTimeMillis(); displayDataState = DisplayDataState.CLASSES_LIST; StyleConstants.setFontSize(style, 20); StyleConstants.setForeground(style, theme.getIdentifiersColor()); clearText(); BatchDocument blank = new BatchDocument(); jTextPane.setDocument(blank); for (String className : classNames) { blank.appendBatchStringNoLineFeed(className, style); blank.appendBatchLineFeed(style); } try { blank.processBatchUpdates(0); } catch (BadLocationException e) { e.printStackTrace(); } jTextPane.setDocument(blank); System.out.println("UI update " + (System.currentTimeMillis() - start) + " ms"); } @Override public void displayClass(String classString) { displayDataState = DisplayDataState.INSIDE_CLASS; try { String currentText = jTextPane.getDocument().getText(0, jTextPane.getDocument().getLength()); if (currentText.equals(getOneColorFormattedOutput(classString))) { return; } } catch (BadLocationException e) { e.printStackTrace(); } clearText(); StyleConstants.setFontSize(style, 20); Document doc = new DefaultStyledDocument(); try { doc.insertString(doc.getLength(), getOneColorFormattedOutput(classString), style); } catch (BadLocationException e) { e.printStackTrace(); } jTextPane.setDocument(doc); jTextPane.setCaretPosition(1); } // TODO add here logic fo highlighter // TODO by adding flag to Translator.ELEMENT @Override public void displayClass(List elements, String key) { displayDataState = DisplayDataState.INSIDE_CLASS; clearText(); StyleConstants.setFontSize(style, 20); StyleConstants.setBackground(style, theme.getBackgroundColor()); Document doc = new DefaultStyledDocument(); fillTokensToDoc(elements, doc, false); StyleConstants.setForeground(style, theme.getIdentifiersColor()); jTextPane.setDocument(doc); int i = calcScrollingPosition(key); jTextPane.setCaretPosition(i); } private void fillTokensToDoc(List from, Document to, boolean newLine) { try { for (Translator.ELEMENT e : from) { switch (e.tag) { case MODIFIER: StyleConstants.setForeground(style, theme.getKeyWordsColor()); break; case DOCUMENT: StyleConstants.setForeground(style, theme.getDefaultColor()); break; case IDENTIFIER: StyleConstants.setForeground(style, theme.getIdentifiersColor()); break; case ANNOTATION: StyleConstants.setForeground(style, theme.getAnnotationsColor()); break; case XML_TAG: StyleConstants.setForeground(style, theme.getIdentifiersColor()); break; case XML_ATTR_NAME: StyleConstants.setForeground(style, theme.getKeyWordsColor()); break; case XML_ATTR_VALUE: StyleConstants.setForeground(style, theme.getDefaultColor()); break; case SELECTION: StyleConstants.setForeground(style, theme.getSelectionBgColor()); break; default: StyleConstants.setForeground(style, Color.LIGHT_GRAY); } String text = e.text; if(newLine) { text += "\n"; } to.insertString(to.getLength(), text, style); } } catch (BadLocationException e) { e.printStackTrace(); } } private int calcScrollingPosition(String textToFind) { int pos = 0; boolean found = false; textToFind = textToFind.trim(); if (textToFind != null && textToFind.length() > 0) { Document document = jTextPane.getDocument(); int findLength = textToFind.length(); try { // Rest the search position if we're at the end of the document if (pos + findLength > document.getLength()) { pos = 0; } // While we haven't reached the end... "<=" Correction while (pos + findLength <= document.getLength()) { String match = document.getText(pos, findLength).toLowerCase(); if (match.equalsIgnoreCase(textToFind)) { found = true; break; } pos++; } } catch (Exception exp) { exp.printStackTrace(); } } if(found) { return pos; } else { return 1; } } @Override public void displaySharkey() { displayDataState = DisplayDataState.SHARKEY; clearText(); style = jTextPane.addStyle("STYLE", null); Document doc = jTextPane.getStyledDocument(); try { StyleConstants.setForeground(style, theme.getIdentifiersColor()); StyleConstants.setFontSize(style, 15); StyleConstants.setFontFamily(style, "Monospaced"); doc.insertString(doc.getLength(), Doodle.get(), style); } catch (BadLocationException e) { e.printStackTrace(); } jTextPane.setDocument(doc); } @Override public void displayError() { displayDataState = DisplayDataState.ERROR; clearText(); style = jTextPane.addStyle("STYLE", null); Document doc = jTextPane.getStyledDocument(); try { StyleConstants.setForeground(style, theme.getDefaultColor()); StyleConstants.setFontSize(style, 15); StyleConstants.setFontFamily(style, "Monospaced"); doc.insertString(doc.getLength(), "\n\n\n\t\t\t There was a problem loading the class ", style); doc.insertString(doc.getLength(), Doodle.get(), style); } catch (BadLocationException e) { e.printStackTrace(); } jTextPane.setDocument(doc); } private void clearText() { jTextPane.setText(null); } private static String getOneColorFormattedOutput(String data) { return data + "\n"; } public static void main(String[] args) { DisplayArea da = new DisplayArea(null); Translator emitter = new JavaTranslator(StringTokenizer.class); emitter.apply(); da.displayClass(emitter.getElementsList(), ""); JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.getContentPane().add(da.onAddComponentToPane()); frame.pack(); frame.setVisible(true); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/displayarea/IDisplayArea.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.displayarea; import com.google.classyshark.silverghost.translator.Translator; import java.awt.Component; import java.util.List; public interface IDisplayArea { Component onAddComponentToPane(); void displayClassNames(List classNamesToShow, String inputText); void displayClass(List displayedClassTokens, String classString); void displayClass(String classString); void displaySharkey(); void displayError(); void displaySearchResults(List filteredClassNames, List displayedManifestSearchResultsTokens, String textFromTypingArea); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/displayarea/doodles/ChristmasBG.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.displayarea.doodles; /** * the welcome ascii art image */ class ChristmasBG { public static final String SHARKEY = "\t * ,\n" + "\t _/^\\_\n" + "\t < >\n" + "\t * /.-.\\ *\n" + "\t * `/&\\` *\n" + "\t ,@.*;@,\n" + "\t /_o.I %_\\ *\n" + "\t * (`'--:o(_@;\n" + "\t /`;--.,__ `') *\n" + "\t ;@`o % O,*`'`&\\ \n" + "\t * (`'--)_@ ;o %'()\\ *\n" + "\t /`;--._`''--._O'@;\n" + "\t /&*,()~o`;-.,_ `\"\"`)\n" + "\t * /`,@ ;+& () o*`;-';\\\n" + "\t (`\"\"--.,_0 +% @' &()\\\n" + "\t /-.,_ ``''--....-'`) *\n" + "\t * /@%;o`:;'--,.__ __.'\\\n" + "\t ;*,&(); @ % &^;~`\"`o;@(); *\n" + "\t /(); o^~; & ().o@*&`;&%O\\\n" + "\t `\"=\"==\"\"==,,,.,=\"==\"===\"`\n" + "\t __.----.(\\-''#####---...___...-----._\n" + "\t '` \\)_`\"\"\"\"\"` \n" + "\n http://www.angelfire.com/ca/mathcool/christmas.html\n" + "\n\n\n\tClassyShark ver. 4.3 Christmas Edition"; } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/displayarea/doodles/Doodle.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.displayarea.doodles; public class Doodle { public static String get() { return SharkBG.SHARKEY; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/displayarea/doodles/SanFranBG.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.displayarea.doodles; public class SanFranBG { public static final String SHARKEY = "\n\n\n\n" + "\n\n\n\n" + "\t ^^\n" + "\t ^^ .. ..\n" + "\t [] []\n" + "\t .:[]:_ ^^ ,:[]:.\n" + "\t .: :[]: :-. ,-: :[]: :.\n" + "\t .: : :[]: : :`._ ,.': : :[]: : :.\n" + "\t .: : : :[]: : : : :-._ _,-: : : : :[]: : : :.\n" + "\t_..: : : : :[]: : : : : : :-._________.-: : : : : : :[]: : : : :-._\n" + "\t_:_:_:_:_:_:[]:_:_:_:_:_:_:_:_:_:_:_:_:_:_:_:_:_:_:_:[]:_:_:_:_:_:_\n" + "\t!!!!!!!!!!!![]!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!![]!!!!!!!!!!!!!\n" + "\t^^^^^^^^^^^^[]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^[]^^^^^^^^^^^^^\n" + "\t [] []\n" + "\t [] []\n" + "\t [] []\n" + "\t ~~^-~^_~^~/ \\~^-~^~_~^-~_^~-^~_^~~-^~_~^~-~_~-^~_^/ \\~^-~_~^-~~-\n" + "\t~ _~~- ~^-^~-^~~- ^~_^-^~~_ -~^_ -~_-~~^- _~~_~-^_ ~^-^~~-_^-~ ~^\n" + "\t ~ ^- _~~_- ~~ _ ~ ^~ - ~~^ _ - ^~- ~ _ ~~^ - ~_ - ~^_~\n" + "\t ~- ^_ ~^ - ^~ _ - ~^~ _ _~^~- _ ~~^ - _ ~ - _ ~~^ -\n" + "\tjgs ~^ -_ ~^^ -_ ~ _ - _ ~^~- _~ -_ ~- _ ~^ _ - ~ ^-\n" + "\t ~^~ - _ ^ - ~~~ _ - _ ~-^ ~ __- ~_ - ~ ~^_-\n" + "\t ~ ~- ^~ - ~^ - ~ ^~ - ~~ ^~ - ~" + "\n\n\t http://www.ascii-code.com/ascii-art/buildings-and-places/bridges.php" + "\n\n\n\tClassyShark ver. 6.0 powered by SilverGhost"; } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/displayarea/doodles/SharkBG.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.displayarea.doodles; import com.google.classyshark.Version; /** * the welcome ascii art image */ class SharkBG { public static final String SHARKEY = "\n\n\n\n" + "\n\n\n\n" + " _,, ,dW\n" + " ,iSMP JIl;\n" + " sPT1Y' JWS:'\n" + " sIl:l1 fWIl? \n" + " dIi:Il; fW1\" \n" + " dIi:l:I' fWI: \n" + " dIli:l:I; fWI: \n" + " .dIli:I:S:S . fWIl` \n" + " ,sWSSIIIiISIIS w,_ .sMW ,MWIl; \n" + " _.,sWWW*\"'*\" , SWW' MWWMm mu,,._ .iSYISb, ,MM*SI!: \n" + " _,s YMMWW'',sd,'MM WMMi \"*MW* WWWMWMb MMS WWP`,MW' S1!` \n" + " _,os,'MMi YW' m,'WW; WWb`SWM Im,, SIS ISW SISIP* WSi II!. \n" + " .osSMWMW,'WSi ',MMP SSb WSW ISII`SYYi III !Il lIi,ui:,*1:li:l1! \n" + " ,sSMMWWWSSSS,'SWbdWW* *YSbiSS:'IlI 7llI il1: l! 'l:+'+l; `''+1i:1i \n" + " ,sYSMWMWY**\"\"\"'` 'WWSSIIiu,'**Y11';IIIb ?!li ?l:i, ` `'l!: \n" + " sPITMWMW'`.M.wdWWb,'YIi `YT\" ,u!1\",ISIWWm,'+?+ `'+Ili `'l:,\n" + " YIi1lTYfPSkyLinedI!i`I!\" .,:!1\"',iSWWMMMMMmm, \n" + " \"T1l1lI**\"'`.2006? ',o?*'`` ```\"\"**YSWMMMWMm, \n" + " \"*:iil1I!I!\"` ' ``\"*YMMWWM, \n" + " ii! '*YMWM, \n" + " I' \"YM\n" + "\n\n\n\thttp://www.retrojunkie.com/asciiart/animals/sharks.htm" + "\n\n\n\tClassyShark ver." + Version.MAJOR + "." + Version.MINOR + " powered by SilverGhost"; } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/io/CurrentFolderConfig.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.io; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.util.Properties; /** * Class that manages ClassyShark config */ public enum CurrentFolderConfig { INSTANCE; private static final String CLASSYSHARK_PROPERTIES = "classyshark.properties"; private static final String CURRENT_FOLDER = "CURRENT_FOLDER"; public void setCurrentDirectory(File path) { String curDir = path.getAbsolutePath(); try { File configFile = new File(CLASSYSHARK_PROPERTIES); if (!configFile.exists()) { configFile.createNewFile(); } Properties props = new Properties(); props.setProperty(CURRENT_FOLDER, curDir); FileWriter writer = new FileWriter(configFile); props.store(writer, CURRENT_FOLDER + "wrote"); writer.close(); } catch (Exception ex) { } } public File getCurrentDirectory() { try { File configFile = new File(CLASSYSHARK_PROPERTIES); if (!configFile.exists()) { configFile.createNewFile(); } FileReader reader = new FileReader(configFile); Properties props = new Properties(); props.load(reader); String result = props.getProperty(CURRENT_FOLDER); reader.close(); return new File(result); } catch (Exception ex) { } return new File(System.getProperty("user.home")); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/io/FileChooserUtils.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.io; import java.io.File; public class FileChooserUtils { private FileChooserUtils(){} public static boolean acceptFile(File f) { if (f.isDirectory()) { return true; } else { return isSupportedArchiveFile(f); } } public static String getFileChooserDescription() { return "dex, jar, apk, class, aar"; } public static boolean isSupportedArchiveFile(File f) { String filename = f.getName().toLowerCase(); return filename.endsWith(".dex") || filename.endsWith(".jar") || filename.endsWith(".zip") || filename.endsWith(".apk") || filename.endsWith(".class") || filename.endsWith(".aar"); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/io/RecentArchivesConfig.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.io; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Properties; /** * Class that manages recent archives config */ public enum RecentArchivesConfig { INSTANCE; private static final String CLASSYSHARK_RECENTS = "classyshark_recents.properties"; private static final String UPDATE_ARCHIVE = "CURRENT_FOLDER"; public void addArchive(String name, File currentDirectory) { try { File configFile = new File(CLASSYSHARK_RECENTS); Properties props = new Properties(); if (!configFile.exists()) { configFile.createNewFile(); } else { configFile = new File(CLASSYSHARK_RECENTS); FileReader reader = new FileReader(configFile); props.load(reader); } props.setProperty(name, currentDirectory.getAbsolutePath()); FileWriter writer = new FileWriter(configFile); props.store(writer, UPDATE_ARCHIVE + "wrote"); writer.close(); } catch (Exception ex) { } } public void clear() { try { File configFile = new File(CLASSYSHARK_RECENTS); Properties props = new Properties(); if (!configFile.exists()) { configFile.createNewFile(); } FileWriter writer = new FileWriter(configFile); props.store(writer, UPDATE_ARCHIVE + "wrote"); writer.close(); } catch (Exception ex) { } } public List getRecentArchiveNames() { List result = new ArrayList<>(); try { File configFile = new File(CLASSYSHARK_RECENTS); if (!configFile.exists()) { configFile.createNewFile(); } FileReader reader = new FileReader(configFile); Properties props = new Properties(); props.load(reader); Enumeration e = props.keys(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); result.add(key); } Collections.sort(result); reader.close(); } catch (Exception ex) { } return result; } public String getFilePath(String name) { String result; try { File configFile = new File(CLASSYSHARK_RECENTS); if (!configFile.exists()) { configFile.createNewFile(); } FileReader reader = new FileReader(configFile); Properties props = new Properties(); props.load(reader); result = props.getProperty(name); reader.close(); } catch (Exception ex) { result = ""; } return result; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/methodscount/MethodsCountPanel.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.methodscount; import com.google.classyshark.gui.GuiMode; import com.google.classyshark.gui.panel.FileTransferHandler; import com.google.classyshark.gui.panel.ViewerController; import com.google.classyshark.gui.panel.tree.CellRenderer; import com.google.classyshark.gui.theme.Theme; import com.google.classyshark.silverghost.methodscounter.ClassNode; import com.google.classyshark.silverghost.methodscounter.RootBuilder; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingWorker; import javax.swing.border.EmptyBorder; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import java.awt.BorderLayout; import java.awt.Font; import java.awt.HeadlessException; import java.io.File; public class MethodsCountPanel extends JPanel { private DefaultTreeModel treeModel; private JTree jTree; private ViewerController viewerController; private final Theme theme = GuiMode.getTheme(); public MethodsCountPanel(ViewerController viewerController, File file) throws HeadlessException { this(viewerController); loadFile(file); theme.applyTo(this); } public MethodsCountPanel(ViewerController viewerController) throws HeadlessException { this.viewerController = viewerController; setup(); theme.applyTo(this); } public void loadFile(File file) { new NodeWorker(file).execute(); } private void setup() { this.setLayout(new BorderLayout()); treeModel = new DefaultTreeModel(new DefaultMutableTreeNode(null)); jTree = new JTree(treeModel); jTree.setRootVisible(false); jTree.setCellRenderer(new CellRenderer()); theme.applyTo(jTree); DefaultTreeCellRenderer cellRenderer = (DefaultTreeCellRenderer) jTree.getCellRenderer(); cellRenderer.setFont(new Font("Monospaced", Font.PLAIN, 20)); jTree.setCellRenderer(cellRenderer); jTree.addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { Object selection = jTree.getLastSelectedPathComponent(); DefaultMutableTreeNode defaultMutableTreeNode = (DefaultMutableTreeNode)selection; ClassNode node = (ClassNode) defaultMutableTreeNode.getUserObject(); viewerController.onSelectedMethodCount(node); } }); JScrollPane jScrollPane = new JScrollPane(jTree); this.setBorder(new EmptyBorder(0,0,0,0)); this.add(jScrollPane, BorderLayout.CENTER); theme.applyTo(jScrollPane); jTree.setDragEnabled(true); jTree.setTransferHandler(new FileTransferHandler(viewerController)); } private void addNodes(ClassNode parent, DefaultMutableTreeNode jTreeParent) { for (ClassNode n: parent.getChildNodes().values()) { DefaultMutableTreeNode newJTreeNode = new DefaultMutableTreeNode(n); jTreeParent.add(newJTreeNode); addNodes(n, newJTreeNode); } } private DefaultMutableTreeNode createDefaultMutableTreeNode(ClassNode rootNode) { DefaultMutableTreeNode jTreeRootNode = new DefaultMutableTreeNode(rootNode); addNodes(rootNode, jTreeRootNode); return jTreeRootNode; } class NodeWorker extends SwingWorker { private File file; public NodeWorker(File file) { this.file = file; } @Override protected ClassNode doInBackground() throws Exception { RootBuilder analyzer = new RootBuilder(); return analyzer.fillClassesWithMethods(file); } @Override protected void done() { try { TreeNode root = createDefaultMutableTreeNode(get()); treeModel.setRoot(root); jTree.setRootVisible(true); viewerController.onSelectedMethodCount( (ClassNode)((DefaultMutableTreeNode)root).getUserObject()); } catch (Exception ex) { } } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/reducer/Reducer.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.reducer; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Is a function : (key, list of classes) --> list of classes, reduced by key */ public class Reducer { private List allClassNames; private List reducedClassNames; public Reducer(List allClassNames) { this.allClassNames = allClassNames; reducedClassNames = new ArrayList<>(); } public List reduce(String key) { List result; if (key.isEmpty()) { result = allClassNames; reducedClassNames.clear(); return result; } else { reducedClassNames = fuzzyReduceClassNames(key, allClassNames); result = reducedClassNames; return result; } } public String getAutocompleteClassName() { if (!reducedClassNames.isEmpty()) { return reducedClassNames.get(0); } return allClassNames.get(0); } public List getAllClassNames() { return Collections.unmodifiableList(allClassNames); } private static List fuzzyReduceClassNames(String key, List list) { List result = new ArrayList<>(); int foundEntryIndex; String camelSearchKey; for (String entry : list) { camelSearchKey = entry; camelSearchKey = camelSearchKey.replaceAll("[^A-Z]", ""); foundEntryIndex = entry.indexOf(key); if ((camelSearchKey.equalsIgnoreCase(key)) || (foundEntryIndex > -1)) { result.add(entry); } } return result; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/toolbar/KeyUtils.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.toolbar; import java.awt.event.KeyEvent; /** * Utility methods for handling input keys */ public class KeyUtils { private KeyUtils() { } public static boolean isDeletePressed(KeyEvent e) { return (e.getKeyCode() == 8); } public static boolean isLeftArrowPressed(KeyEvent e) { return (e.getKeyCode() == 37); } public static boolean isRightArrowPressed(KeyEvent e) { return (e.getKeyCode() == 39); } public static boolean isCommandKeyPressed(KeyEvent e) { return (e.getKeyCode() == 157); } public static boolean isLetterOrDigit(KeyEvent e) { char eventChar = e.getKeyChar(); return Character.isLetterOrDigit(eventChar); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/toolbar/RecentArchivesButton.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.toolbar; import com.google.classyshark.gui.GuiMode; import com.google.classyshark.gui.panel.io.RecentArchivesConfig; import com.google.classyshark.gui.theme.Theme; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; /** * recent files button */ public class RecentArchivesButton extends JButton { private JPopupMenu popup; private ToolbarController panel; private final Theme theme = GuiMode.getTheme(); public RecentArchivesButton() { super(); setIcon(GuiMode.getTheme().getRecentIcon()); setToolTipText("History"); popup = new JPopupMenu(); theme.applyTo(popup); popup.setLayout(new BoxLayout(popup, BoxLayout.Y_AXIS)); popup.addPopupMenuListener(new PopupPrintListener()); buildPopup(); setBorderPainted(false); setFocusPainted(true); addMouseListener(new MousePopupListener()); } public void setPanel(ToolbarController panel) { this.panel = panel; } private void buildPopup() { popup.removeAll(); JMenuItem item; for (String archiveName : RecentArchivesConfig.INSTANCE.getRecentArchiveNames()) { item = new JMenuItem(archiveName); theme.applyTo(item); popup.add(item); item.setHorizontalTextPosition(JMenuItem.RIGHT); item.addActionListener(new RecentFilesListener(archiveName)); } popup.addSeparator(); final JMenuItem clearRecentArchivesItem = new JMenuItem("Clear"); theme.applyTo(clearRecentArchivesItem); popup.add(clearRecentArchivesItem); clearRecentArchivesItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { RecentArchivesConfig.INSTANCE.clear(); popup.removeAll(); popup.updateUI(); popup.add(clearRecentArchivesItem); } }); } private class RecentFilesListener implements ActionListener { private String archiveName; public RecentFilesListener(String archiveName) { this.archiveName = archiveName; } @Override public void actionPerformed(ActionEvent e) { panel.displayArchive( new File(RecentArchivesConfig.INSTANCE.getFilePath(archiveName), archiveName)); buildPopup(); } } private class MousePopupListener extends MouseAdapter { public void mousePressed(MouseEvent e) { checkPopup(e); } public void mouseClicked(MouseEvent e) { checkPopup(e); } public void mouseReleased(MouseEvent e) { checkPopup(e); } private void checkPopup(MouseEvent e) { popup.show(RecentArchivesButton.this, e.getX() - (int) popup.getPreferredSize().getWidth(), e.getY()); } } private class PopupPrintListener implements PopupMenuListener { public void popupMenuWillBecomeVisible(PopupMenuEvent e) { buildPopup(); } public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } public void popupMenuCanceled(PopupMenuEvent e) { } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/toolbar/Toolbar.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.toolbar; import com.google.classyshark.gui.GuiMode; import com.google.classyshark.gui.theme.Theme; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.WindowConstants; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; /** * toolbar = buttons + command line */ public class Toolbar extends JToolBar { private final JTextField typingArea; private final ToolbarController toolbarController; private final Theme theme = GuiMode.getTheme(); private JButton openBtn; private JButton viewBtn; private JButton backBtn; private JButton exportButton; private JButton recentArchivesBtn; private JButton mappingBtn; private JToggleButton leftPanelToggleBtn; public Toolbar(final ToolbarController toolbarController) { super(); this.toolbarController = toolbarController; theme.applyTo(this); typingArea = buildTypingArea(); openBtn = buildOpenButton(); backBtn = buildBackButton(); viewBtn = buildViewButton(); mappingBtn = buildMappingsButton(); exportButton = buildExportButton(); recentArchivesBtn = buildRecentArchivesButton(); leftPanelToggleBtn = buildLeftPanelToggleButton(); add(leftPanelToggleBtn); add(openBtn); add(backBtn); add(viewBtn); add(typingArea); add(mappingBtn); add(exportButton); add(recentArchivesBtn); add(buildSettingsButton()); setFloatable(false); setTypingAreaCaret(); setBorder(BorderFactory.createEmptyBorder()); } public void addKeyListenerToTypingArea(KeyListener kl) { typingArea.addKeyListener(kl); } public void setTypingAreaCaret() { int len = typingArea.getDocument().getLength(); typingArea.setCaretPosition(len); typingArea.setCaretColor(theme.getIdentifiersColor()); } public String getText() { return typingArea.getText(); } public void setText(String text) { typingArea.setText(text); } public void activateNavigationButtons() { viewBtn.setEnabled(true); backBtn.setEnabled(true); exportButton.setEnabled(true); } private JTextField buildTypingArea() { final JTextField result = new JTextField(50); result.setEnabled(true); theme.applyTo(result); result.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { if (result.getSelectedText() != null) { String textToDelete = typingArea.getSelectedText(); String selectedLine = result.getText().substring(0, result.getText().lastIndexOf(textToDelete)); result.setText(selectedLine); toolbarController.onChangedTextFromTypingArea(result.getText()); } } }); return result; } private JButton buildOpenButton() { JButton result = new JButton(theme.getOpenIcon()); result.setToolTipText("Open file"); result.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { toolbarController.openArchive(); } }); result.setBorderPainted(false); result.setFocusPainted(true); return result; } private JButton buildBackButton() { JButton result = new JButton(theme.getBackIcon()); result.setToolTipText("Back"); result.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { toolbarController.onGoBackPressed(); } }); result.setBorderPainted(false); result.setFocusPainted(true); result.setEnabled(false); return result; } private JButton buildViewButton() { JButton result = new JButton(theme.getForwardIcon()); result.setToolTipText("Next"); result.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { toolbarController.onViewTopClassPressed(); } }); result.setBorderPainted(false); result.setFocusPainted(true); result.setEnabled(false); return result; } private JButton buildExportButton() { JButton result = new JButton(theme.getExportIcon()); result.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { toolbarController.onExportButtonPressed(); } }); result.setToolTipText("Export"); result.setBorderPainted(false); result.setEnabled(false); return result; } private JButton buildMappingsButton() { JButton result = new JButton(theme.getMappingIcon()); result.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { toolbarController.onMappingsButtonPressed(); } }); result.setToolTipText("Import Proguard mapping file"); result.setBorderPainted(false); result.setEnabled(true); return result; } private JButton buildRecentArchivesButton() { RecentArchivesButton result = new RecentArchivesButton(); result.setPanel(toolbarController); return result; } private JButton buildSettingsButton() { JButton button = new JButton(theme.getSettingsIcon()); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { toolbarController.onSettingsButtonPressed(); } }); button.setToolTipText("Settings"); button.setBorderPainted(false); return button; } private JToggleButton buildLeftPanelToggleButton() { final ImageIcon toggleIcon = theme.getToggleIcon(); final JToggleButton jToggleButton = new JToggleButton(toggleIcon, true); jToggleButton.setToolTipText("Show/hide navigation tree"); jToggleButton.setBorderPainted(false); jToggleButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { toolbarController.onChangeLeftPaneVisibility(jToggleButton.isSelected()); } }); return jToggleButton; } public static void main(String[] args) { JFrame frame = new JFrame("ClassyShark"); Toolbar toolbar = new Toolbar(null); toolbar.addKeyListenerToTypingArea(null); frame.getContentPane().add(toolbar); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/toolbar/ToolbarController.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.toolbar; import com.google.classyshark.gui.panel.ArchiveDisplayer; public interface ToolbarController extends ArchiveDisplayer { void onChangedTextFromTypingArea(String text); void openArchive(); void onGoBackPressed(); void onViewTopClassPressed(); void onMappingsButtonPressed(); void onExportButtonPressed(); void onChangeLeftPaneVisibility(boolean selected); void onSettingsButtonPressed(); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/tree/CellRenderer.java ================================================ package com.google.classyshark.gui.panel.tree; import com.google.classyshark.gui.GuiMode; import javax.swing.JTree; import javax.swing.tree.DefaultTreeCellRenderer; import java.awt.Color; import java.awt.Component; public class CellRenderer extends DefaultTreeCellRenderer{ @Override public Color getBackgroundNonSelectionColor() { return (null); } @Override public Color getBackgroundSelectionColor() { return GuiMode.getTheme().getSelectionBgColor(); } @Override public Color getBackground() { return (null); } @Override public Color getTextNonSelectionColor() { return GuiMode.getTheme().getDefaultColor(); } @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { final Component component = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); this.setText(value.toString()); return component; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/tree/FilesTree.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.tree; import com.google.classyshark.gui.GuiMode; import com.google.classyshark.gui.panel.FileTransferHandler; import com.google.classyshark.gui.panel.ViewerController; import com.google.classyshark.gui.panel.reducer.Reducer; import com.google.classyshark.silverghost.contentreader.ContentReader; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreeSelectionModel; import java.awt.Component; import java.awt.Font; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class FilesTree { private final ViewerController viewerController; private DefaultTreeModel treeModel = null; private JTree jTree = null; public FilesTree(ViewerController viewerPanel) { this.viewerController = viewerPanel; treeModel = new DefaultTreeModel(new DefaultMutableTreeNode()); jTree = new JTree(treeModel); jTree.setDragEnabled(true); jTree.setTransferHandler(new FileTransferHandler(viewerController)); jTree.setCellRenderer(new CellRenderer()); configureJTree(jTree); GuiMode.getTheme().applyTo(jTree); } public void fillArchive(File loadedFile, List displayedClassNames, List allComponets) { if (!loadedFile.getName().contains(".")) { TreeNode rootNode = createEmptyJTreeModelClass(); treeModel.setRoot(rootNode); return; } TreeNode rootNode = createTreeModel(loadedFile, displayedClassNames, allComponets); treeModel.setRoot(rootNode); } private TreeNode createTreeModel(File loadedFile, List displayedClassNames, List allComponets) { TreeNode rootNode; if (loadedFile.getName().endsWith("dex") || loadedFile.getName().endsWith("apk") || loadedFile.getName().endsWith("aar")) { rootNode = createJTreeModelAndroid(loadedFile.getName(), displayedClassNames, allComponets ); } else { rootNode = createJTreeModelClass(loadedFile.getName(), displayedClassNames, allComponets); } return rootNode; } private TreeNode createJTreeModelAndroid(String fileName, List displayedClassNames, List allComponents) { DefaultMutableTreeNode root = new DefaultMutableTreeNode(fileName); DefaultMutableTreeNode classes = new DefaultMutableTreeNode("classes"); DefaultMutableTreeNode res = new DefaultMutableTreeNode("res"); DefaultMutableTreeNode currentClassesDex = classes; List noPkgNodes = new ArrayList<>(); String lastPackage = null; String lastResDir = null; DefaultMutableTreeNode packageNode = null; DefaultMutableTreeNode dirNode = null; for (int i = 0; i < displayedClassNames.size(); i++) { String resName = displayedClassNames.get(i); if (resName.endsWith(".xml")) { if (resName.lastIndexOf(File.separator) > 0) { String dir = resName.substring(0, resName.lastIndexOf(File.separator)); if (lastResDir == null || !dir.equals(lastResDir)) { lastResDir = dir; dirNode = new DefaultMutableTreeNode(dir); res.add(dirNode); } dirNode.add(new DefaultMutableTreeNode(resName)); } else { root.add(new DefaultMutableTreeNode(resName)); } } else if (resName.endsWith(".dex")) { currentClassesDex = new DefaultMutableTreeNode(resName); classes.add(currentClassesDex); } else { if (resName.lastIndexOf('.') >= 0) { String pkg = resName.substring(0, resName.lastIndexOf('.')); if (!pkg.equals(lastPackage)) { lastPackage = pkg; packageNode = new DefaultMutableTreeNode(pkg); currentClassesDex.add(packageNode); } packageNode.add(new DefaultMutableTreeNode(new NodeInfo(resName))); } else { noPkgNodes.add(new DefaultMutableTreeNode(new NodeInfo(resName))); } } } // hack for manually stripped APKs with one flat package if (packageNode != null && currentClassesDex.isLeaf()) { currentClassesDex.add(packageNode); } for (DefaultMutableTreeNode node : noPkgNodes) { currentClassesDex.add(node); } root.add(classes); root.add(res); fillComponents(root, allComponents); return root; } private void fillComponents(DefaultMutableTreeNode root, List allComponents) { if (!allComponents.isEmpty()) { DefaultMutableTreeNode libs = new DefaultMutableTreeNode("libs"); Collections.sort(allComponents, new Comparator() { @Override public int compare(ContentReader.Component o1, ContentReader.Component o2) { return o1.name.compareTo(o2.name); } }); for (ContentReader.Component comp : allComponents) { if (comp.component.equals(ContentReader.ARCHIVE_COMPONENT.NATIVE_LIBRARY)) { libs.add(new DefaultMutableTreeNode(comp.name)); } } root.add(libs); } } private TreeNode createJTreeModelClass(String fileName, List displayedClassNames, List allComponets) { DefaultMutableTreeNode root = new DefaultMutableTreeNode(fileName); DefaultMutableTreeNode classes = new DefaultMutableTreeNode("classes"); String lastPackage = null; DefaultMutableTreeNode packageNode = null; for (int i = 0; i < displayedClassNames.size(); i++) { String fullClassFileName = displayedClassNames.get(i); String pkg = fullClassFileName; if (pkg.lastIndexOf('.') > 0) { pkg = pkg.substring(0, pkg.lastIndexOf('.')); } if (lastPackage == null || !pkg.equals(lastPackage)) { lastPackage = pkg; packageNode = new DefaultMutableTreeNode(pkg); classes.add(packageNode); } packageNode.add(new DefaultMutableTreeNode(new NodeInfo(fullClassFileName))); } root.add(classes); fillComponents(root, allComponets); return root; } private TreeNode createEmptyJTreeModelClass() { return new DefaultMutableTreeNode("error loading archive"); } public Component getJTree() { return jTree; } private void configureJTree(final JTree jTree) { jTree.setRootVisible(false); DefaultTreeCellRenderer cellRenderer = (DefaultTreeCellRenderer) jTree.getCellRenderer(); cellRenderer.setFont(new Font("Monospaced", Font.PLAIN, 20)); jTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); jTree.addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { Object selection = jTree.getLastSelectedPathComponent(); if (selection == null) return; DefaultMutableTreeNode defaultMutableTreeNode = (DefaultMutableTreeNode) selection; if (selection.toString().endsWith(".dex")) { FilesTree.this.viewerController.onSelectedClassName( (String) defaultMutableTreeNode.getUserObject()); return; } if (selection.toString().endsWith(".jar")) { FilesTree.this.viewerController.onSelectedClassName( (String) defaultMutableTreeNode.getUserObject()); return; } if (selection.toString().endsWith(".apk")) { FilesTree.this.viewerController.onSelectedClassName( (String) defaultMutableTreeNode.getUserObject()); return; } if (selection.toString().endsWith(".so")) { FilesTree.this.viewerController.onSelectedClassName( (String) defaultMutableTreeNode.getUserObject()); return; } if (!defaultMutableTreeNode.isLeaf()) return; if (FilesTree.this.viewerController != null) { if (defaultMutableTreeNode.getUserObject() instanceof String) { FilesTree.this.viewerController.onSelectedClassName( (String) defaultMutableTreeNode.getUserObject()); } else { FilesTree.this.viewerController.onSelectedClassName( ((NodeInfo) defaultMutableTreeNode.getUserObject()).fullname); } } } }); } public void setVisibleRoot() { jTree.setRootVisible(true); } public static void main(String[] args) { File test = new File(System.getProperty("user.home") + "/Desktop/ClassyShark/Scenarios/7 Jayce/data.jar"); FilesTree filesTree = new FilesTree(null); ContentReader loader = new ContentReader(test); loader.load(); Reducer reducer = new Reducer(loader.getAllClassNames()); reducer.reduce(""); filesTree.fillArchive(test, reducer.getAllClassNames(), loader.getAllComponents()); for (String s : reducer.getAllClassNames()) { System.out.println(NodeInfo.extractClassName(s)); } JFrame frame = new JFrame("Test"); JScrollPane scrolledTree = new JScrollPane(filesTree.getJTree()); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(scrolledTree); frame.pack(); frame.setVisible(true); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/panel/tree/NodeInfo.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.panel.tree; import java.util.regex.Matcher; import java.util.regex.Pattern; public class NodeInfo { public String fullname; public NodeInfo(String fullClassName) { this.fullname = fullClassName; } public String toString() { return extractClassName(fullname); } public static String extractClassName (String fullname) { Pattern p = Pattern.compile(".*?([^.]+)$"); Matcher m = p.matcher(fullname); return (m.find()) ? m.group(1) : ""; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/settings/SettingsFrame.java ================================================ package com.google.classyshark.gui.settings; import com.google.classyshark.gui.GuiMode; import com.google.classyshark.gui.theme.Theme; import com.google.classyshark.gui.theme.ThemeManager; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import java.awt.BorderLayout; import java.awt.HeadlessException; public class SettingsFrame extends JFrame{ private final Theme theme = GuiMode.getTheme(); private final String[] themes = {"Light", "Dark"}; public SettingsFrame() throws HeadlessException { super("Settings"); initUI(); JPanel panel = buildThemeUI(); getContentPane().add(panel); } private JPanel buildThemeUI() { JPanel panel = buildOutPanel(); JLabel label = buildThemeLabel(); panel.add(label, BorderLayout.NORTH); JComboBox comboBox = buildComboBox(); panel.add(comboBox, BorderLayout.CENTER); return panel; } private JComboBox buildComboBox() { JComboBox comboBox = new JComboBox(ThemeManager.getThemes()); comboBox.setSelectedIndex(ThemeManager.getThemeIndexFrom(theme)); comboBox.addActionListener(new ThemeChosenListener(this, comboBox)); return comboBox; } private JLabel buildThemeLabel() { JLabel label = new JLabel("Theme:"); label.setToolTipText("It will be applied the next time ClassyShark is started"); theme.applyTo(label); return label; } private JPanel buildOutPanel() { JPanel panel = new JPanel(new BorderLayout(8,8)); theme.applyTo(panel); return panel; } private void initUI() { setVisible(true); setSize(200, 80); setLocationRelativeTo(null); setResizable(false); setLayout(new BorderLayout()); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/settings/ThemeChosenListener.java ================================================ package com.google.classyshark.gui.settings; import com.google.classyshark.gui.theme.Theme; import com.google.classyshark.gui.theme.ThemeManager; import javax.swing.JComboBox; import javax.swing.JFrame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class ThemeChosenListener implements ActionListener { private final JFrame root; private final JComboBox comboBox; public ThemeChosenListener(JFrame root, JComboBox comboBox) { this.root = root; this.comboBox = comboBox; } @Override public void actionPerformed(ActionEvent e) { final Theme theme = ThemeManager.getThemeFrom(comboBox.getSelectedIndex()); ThemeManager.saveCurrentTheme(theme); root.setVisible(false); root.dispose(); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/theme/SwingThemeApplier.java ================================================ package com.google.classyshark.gui.theme; interface SwingThemeApplier { void applyTo(T component); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/theme/Theme.java ================================================ package com.google.classyshark.gui.theme; import javax.swing.ImageIcon; import java.awt.Color; import java.awt.Component; /** * This class is the one defining the different parameters needed in order to obtain a proper theme across the whole app */ public interface Theme extends SwingThemeApplier { ImageIcon getToggleIcon(); ImageIcon getRecentIcon(); ImageIcon getBackIcon(); ImageIcon getForwardIcon(); ImageIcon getOpenIcon(); ImageIcon getExportIcon(); ImageIcon getMappingIcon(); ImageIcon getSettingsIcon(); Color getDefaultColor(); Color getKeyWordsColor(); Color getIdentifiersColor(); Color getAnnotationsColor(); Color getSelectionBgColor(); Color getNamesColor(); Color getBackgroundColor(); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/theme/ThemeManager.java ================================================ package com.google.classyshark.gui.theme; import com.google.classyshark.gui.theme.dark.DarkTheme; import com.google.classyshark.gui.theme.light.LightTheme; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Properties; public class ThemeManager { private static final String PROP_FILE = "classyshark_ui.properties"; private static final String THEME_KEY = "Theme"; private static final String[] themes = {"Light", "Dark"}; private static final int LIGHT = 0; private static final int DARK = 1; public static void saveCurrentTheme(Theme theme) { try { Properties properties = new Properties(); properties.setProperty(THEME_KEY, theme.getClass().getName()); FileWriter writer = new FileWriter(getPropertyFile()); properties.store(writer, "Theme stored"); writer.close(); } catch (IOException e) { } } private static File getPropertyFile() throws IOException { File configFile = new File(PROP_FILE); if (!configFile.exists()) { configFile.createNewFile(); } return configFile; } public static Theme getCurrentTheme() { try { FileReader reader = new FileReader(getPropertyFile()); Properties properties = new Properties(); properties.load(reader); final String theme = properties.getProperty(THEME_KEY); Class c = (Class) Class.forName(theme); return c.newInstance(); } catch (Exception e) { return new DarkTheme(); } } public static String[] getThemes() { return themes; } public static int getThemeIndexFrom(Theme theme) { if (theme instanceof DarkTheme) { return DARK; } else { return LIGHT; } } public static Theme getThemeFrom(final int index) { switch (index) { case LIGHT: return new LightTheme(); case DARK: default: return new DarkTheme(); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/theme/dark/DarkColorScheme.java ================================================ package com.google.classyshark.gui.theme.dark; import java.awt.Color; class DarkColorScheme { private DarkColorScheme() {} static final Color BACKGROUND = new Color(32,32,32); static final Color BACKGROUND_LIGHT = new Color(46, 48,50); static final Color IDENTIFIERS = new Color(0xFF, 0xFF, 0x80); static final Color DEFAULT = new Color(0xd8, 0xd8, 0xd8); static final Color KEYWORDS = new Color(133, 153, 0); static final Color ANNOTATIONS = new Color(108, 113, 196); static final Color SELECTION_BG = new Color(7, 56, 66); static final Color NAMES = new Color(88, 110, 117); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/theme/dark/DarkIconScheme.java ================================================ package com.google.classyshark.gui.theme.dark; public class DarkIconScheme { private static final String ROOT_PATH = "/resources/"; private static final String EXTENSION = ".png"; static final String TOGGLE_ICON_PATH = ROOT_PATH + "ic_menu" + EXTENSION; static final String RECENT_ICON_PATH = ROOT_PATH + "ic_history" + EXTENSION; static final String BACK_ICON_PATH = ROOT_PATH + "ic_back" + EXTENSION; static final String NEXT_ICON_PATH = ROOT_PATH + "ic_next" + EXTENSION; static final String OPEN_ICON_PATH = ROOT_PATH + "ic_open" + EXTENSION; static final String EXPORT_ICON_PATH = ROOT_PATH + "ic_export" + EXTENSION; static final String MAPPING_ICON_PATH = ROOT_PATH + "ic_mappings" + EXTENSION; static final String SETTINGS_ICON_PATH = ROOT_PATH + "ic_settings" + EXTENSION; } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/theme/dark/DarkTheme.java ================================================ package com.google.classyshark.gui.theme.dark; import com.google.classyshark.gui.theme.Theme; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.UIManager; import java.awt.Color; import java.awt.Component; import static com.google.classyshark.gui.theme.dark.DarkColorScheme.ANNOTATIONS; import static com.google.classyshark.gui.theme.dark.DarkColorScheme.BACKGROUND; import static com.google.classyshark.gui.theme.dark.DarkColorScheme.BACKGROUND_LIGHT; import static com.google.classyshark.gui.theme.dark.DarkColorScheme.DEFAULT; import static com.google.classyshark.gui.theme.dark.DarkColorScheme.IDENTIFIERS; import static com.google.classyshark.gui.theme.dark.DarkColorScheme.KEYWORDS; import static com.google.classyshark.gui.theme.dark.DarkColorScheme.NAMES; import static com.google.classyshark.gui.theme.dark.DarkColorScheme.SELECTION_BG; import static com.google.classyshark.gui.theme.dark.DarkIconScheme.BACK_ICON_PATH; import static com.google.classyshark.gui.theme.dark.DarkIconScheme.EXPORT_ICON_PATH; import static com.google.classyshark.gui.theme.dark.DarkIconScheme.MAPPING_ICON_PATH; import static com.google.classyshark.gui.theme.dark.DarkIconScheme.NEXT_ICON_PATH; import static com.google.classyshark.gui.theme.dark.DarkIconScheme.OPEN_ICON_PATH; import static com.google.classyshark.gui.theme.dark.DarkIconScheme.RECENT_ICON_PATH; import static com.google.classyshark.gui.theme.dark.DarkIconScheme.SETTINGS_ICON_PATH; import static com.google.classyshark.gui.theme.dark.DarkIconScheme.TOGGLE_ICON_PATH; public class DarkTheme implements Theme{ private final ImageIcon toggleIcon; private final ImageIcon recentIcon; private final ImageIcon backIcon; private final ImageIcon forwardIcon; private final ImageIcon openIcon; private final ImageIcon exportIcon; private final ImageIcon mappingsIcon; private final ImageIcon settingsIcon; public DarkTheme() { toggleIcon = new ImageIcon(getClass().getResource(TOGGLE_ICON_PATH)); recentIcon = new ImageIcon(getClass().getResource(RECENT_ICON_PATH)); backIcon = new ImageIcon(getClass().getResource(BACK_ICON_PATH)); forwardIcon = new ImageIcon(getClass().getResource(NEXT_ICON_PATH)); openIcon = new ImageIcon(getClass().getResource(OPEN_ICON_PATH)); exportIcon = new ImageIcon(getClass().getResource(EXPORT_ICON_PATH)); mappingsIcon = new ImageIcon(getClass().getResource(MAPPING_ICON_PATH)); settingsIcon = new ImageIcon(getClass().getResource(SETTINGS_ICON_PATH)); // TODO was default need to change names UIManager.put("MenuItem.foreground", IDENTIFIERS); } @Override public ImageIcon getToggleIcon() { return toggleIcon; } @Override public ImageIcon getRecentIcon() { return recentIcon; } @Override public ImageIcon getBackIcon() { return backIcon; } @Override public ImageIcon getForwardIcon() { return forwardIcon; } @Override public ImageIcon getOpenIcon() { return openIcon; } @Override public ImageIcon getExportIcon() { return exportIcon; } @Override public ImageIcon getMappingIcon() { return mappingsIcon; } @Override public ImageIcon getSettingsIcon() { return settingsIcon; } @Override public Color getDefaultColor() { return DEFAULT; } @Override public Color getKeyWordsColor() { return KEYWORDS; } @Override public Color getIdentifiersColor() { return IDENTIFIERS; } @Override public Color getAnnotationsColor() { return ANNOTATIONS; } @Override public Color getSelectionBgColor() { return SELECTION_BG; } @Override public Color getNamesColor() { return NAMES; } @Override public Color getBackgroundColor() { return BACKGROUND; } @Override public void applyTo(Component component) { if (shallBeLighter(component)) { component.setBackground(BACKGROUND_LIGHT); } else if (component instanceof JLabel) { component.setForeground(IDENTIFIERS); }else { component.setBackground(BACKGROUND); } if(component instanceof JTextField) { component.setForeground(IDENTIFIERS); } } private boolean shallBeLighter(Component component) { return component instanceof JTree || component instanceof JTextField || component instanceof JMenuItem || component instanceof JPopupMenu; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/theme/light/LightColorScheme.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.gui.theme.light; import java.awt.Color; /** * Application color scheme */ class LightColorScheme { private LightColorScheme(){} static final Color DEFAULT = new Color(101, 123, 131); static final Color KEYWORDS = new Color(181, 137, 0); static final Color IDENTIFIERS = new Color(133, 153, 0); static final Color ANNOTATIONS = new Color(108, 113, 196); static final Color SELECTION_BG = new Color(7, 56, 66); static final Color NAMES = new Color(147, 161, 161); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/theme/light/LightIconScheme.java ================================================ package com.google.classyshark.gui.theme.light; class LightIconScheme { private static final String ROOT_PATH = "/resources/"; private static final String EXTENSION = ".png"; static final String TOGGLE_ICON_PATH = ROOT_PATH + "ic_menu" + EXTENSION; static final String RECENT_ICON_PATH = ROOT_PATH + "ic_history" + EXTENSION; static final String BACK_ICON_PATH = ROOT_PATH + "ic_back" + EXTENSION; static final String NEXT_ICON_PATH = ROOT_PATH + "ic_next" + EXTENSION; static final String OPEN_ICON_PATH = ROOT_PATH + "ic_open" + EXTENSION; static final String EXPORT_ICON_PATH = ROOT_PATH + "ic_export" + EXTENSION; static final String MAPPING_ICON_PATH = ROOT_PATH + "ic_mappings" + EXTENSION; static final String SETTINGS_ICON_PATH = ROOT_PATH + "ic_settings" + EXTENSION; } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/gui/theme/light/LightTheme.java ================================================ package com.google.classyshark.gui.theme.light; import com.google.classyshark.gui.theme.Theme; import javax.swing.ImageIcon; import java.awt.Color; import java.awt.Component; import static com.google.classyshark.gui.theme.light.LightColorScheme.ANNOTATIONS; import static com.google.classyshark.gui.theme.light.LightColorScheme.DEFAULT; import static com.google.classyshark.gui.theme.light.LightColorScheme.IDENTIFIERS; import static com.google.classyshark.gui.theme.light.LightColorScheme.KEYWORDS; import static com.google.classyshark.gui.theme.light.LightColorScheme.NAMES; import static com.google.classyshark.gui.theme.light.LightColorScheme.SELECTION_BG; import static com.google.classyshark.gui.theme.light.LightIconScheme.BACK_ICON_PATH; import static com.google.classyshark.gui.theme.light.LightIconScheme.EXPORT_ICON_PATH; import static com.google.classyshark.gui.theme.light.LightIconScheme.MAPPING_ICON_PATH; import static com.google.classyshark.gui.theme.light.LightIconScheme.NEXT_ICON_PATH; import static com.google.classyshark.gui.theme.light.LightIconScheme.OPEN_ICON_PATH; import static com.google.classyshark.gui.theme.light.LightIconScheme.RECENT_ICON_PATH; import static com.google.classyshark.gui.theme.light.LightIconScheme.SETTINGS_ICON_PATH; import static com.google.classyshark.gui.theme.light.LightIconScheme.TOGGLE_ICON_PATH; public class LightTheme implements Theme { private final ImageIcon toggleIcon; private final ImageIcon recentIcon; private final ImageIcon backIcon; private final ImageIcon forwardIcon; private final ImageIcon openIcon; private final ImageIcon exportIcon; private final ImageIcon mappingsIcon; private final ImageIcon settingsIcon; public LightTheme() { toggleIcon = new ImageIcon(getClass().getResource(TOGGLE_ICON_PATH)); recentIcon = new ImageIcon(getClass().getResource(RECENT_ICON_PATH)); backIcon = new ImageIcon(getClass().getResource(BACK_ICON_PATH)); forwardIcon = new ImageIcon(getClass().getResource(NEXT_ICON_PATH)); openIcon = new ImageIcon(getClass().getResource(OPEN_ICON_PATH)); exportIcon = new ImageIcon(getClass().getResource(EXPORT_ICON_PATH)); mappingsIcon = new ImageIcon(getClass().getResource(MAPPING_ICON_PATH)); settingsIcon = new ImageIcon(getClass().getResource(SETTINGS_ICON_PATH)); } @Override public ImageIcon getToggleIcon() { return toggleIcon; } @Override public ImageIcon getRecentIcon() { return recentIcon; } @Override public ImageIcon getBackIcon() { return backIcon; } @Override public ImageIcon getForwardIcon() { return forwardIcon; } @Override public ImageIcon getOpenIcon() { return openIcon; } @Override public ImageIcon getExportIcon() { return exportIcon; } @Override public ImageIcon getMappingIcon() { return mappingsIcon; } @Override public ImageIcon getSettingsIcon() { return settingsIcon; } @Override public Color getDefaultColor() { return DEFAULT; } @Override public Color getKeyWordsColor() { return KEYWORDS; } @Override public Color getIdentifiersColor() { return IDENTIFIERS; } @Override public Color getAnnotationsColor() { return ANNOTATIONS; } @Override public Color getSelectionBgColor() { return SELECTION_BG; } @Override public Color getNamesColor() { return NAMES; } @Override public Color getBackgroundColor() { return Color.WHITE; } @Override public void applyTo(Component component) { /** * Do nothing as we don't want to override system defaults for the light theme */ } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/FullArchiveReader.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost; import com.google.classyshark.silverghost.translator.Translator; import java.io.File; public interface FullArchiveReader { void readAsyncArchive(File file); Translator buildTranslator(String className, File archiveFile); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/SilverGhost.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost; import com.google.classyshark.gui.panel.reducer.Reducer; import com.google.classyshark.silverghost.contentreader.ContentReader; import com.google.classyshark.silverghost.plugins.EmptyFullArchiveReader; import com.google.classyshark.silverghost.plugins.IdentityMapper; import com.google.classyshark.silverghost.translator.Translator; import com.google.classyshark.silverghost.translator.TranslatorFactory; import java.io.File; import java.util.LinkedList; import java.util.List; import static com.google.classyshark.gui.panel.ClassySharkPanel.ANDROID_MANIFEST_XML_SEARCH; /** * Main API class with respect to threading (never call readXXX method from UI thread) */ public class SilverGhost { private File binaryArchive; private Reducer reducer; private Translator translator; private ContentReader contentReader; private String manifestStr = ""; private static TokensMapper tokensMapper; private static FullArchiveReader fullArchiveReader; static { tokensMapper = new IdentityMapper(); fullArchiveReader = new EmptyFullArchiveReader(); } public SilverGhost() { } public void setBinaryArchive(File binArchive) { this.binaryArchive = binArchive; // TODO think of initialyzing data members as they hold prev file state tokensMapper = new IdentityMapper(); } // 1. READ CONTENTS public void readContents() { contentReader = new ContentReader(getBinaryArchive()); long start = System.currentTimeMillis(); contentReader.load(); reducer = new Reducer(contentReader.getAllClassNames()); System.out.println("Archive Reading " + (System.currentTimeMillis() - start) + " ms "); if (binaryArchive.getName().endsWith(".apk")) { Translator translator = TranslatorFactory.createTranslator("AndroidManifest.xml", binaryArchive); translator.apply(); manifestStr = translator.toString(); } fullArchiveReader.readAsyncArchive(binaryArchive); } public File getBinaryArchive() { return this.binaryArchive; } public List getComponents() { return contentReader.getAllComponents(); } public List getAllClassNames() { return contentReader.getAllClassNames(); } public String getAutoCompleteClassName() { return reducer.getAutocompleteClassName(); } public void initClassNameFiltering() { reducer.reduce(""); } public List filter(String text) { return reducer.reduce(text); } public boolean isArchiveError() { boolean noJavaClasses = contentReader.getAllClassNames().isEmpty(); boolean noAndroidClasses = contentReader.getAllClassNames().size() == 1 && contentReader.getAllClassNames().contains("AndroidManifest.xml"); return noJavaClasses || noAndroidClasses; } // 2. READ MAPPINGS FILE public TokensMapper readMappingFile(File mappingFile) { tokensMapper.readMappings(mappingFile); return tokensMapper; } public void addMappings(TokensMapper tokensMapper) { this.tokensMapper = tokensMapper; } // 3. BINARY ARCHIVE ELEMENT public void translateArchiveElement(String elementName) { // TODO handle case when reducer is null or whatever translator = TranslatorFactory.createTranslator( elementName, getBinaryArchive(), reducer.getAllClassNames(), fullArchiveReader); translator.addMapper(tokensMapper); translator.apply(); } public List getArchiveElementTokens() { return translator.getElementsList(); } public List getImportsForCurrentClass() { return translator.getDependencies(); } public String getCurrentClassName() { return translator.getClassName(); } public String getCurrentClassContent() { return translator.toString(); } public List getManifestMatches(String textFromTypingArea) { LinkedList result = new LinkedList<>(); if (manifestStr.isEmpty()) { return result; } result.add(new Translator.ELEMENT(ANDROID_MANIFEST_XML_SEARCH, Translator.TAG.IDENTIFIER)); result.add(new Translator.ELEMENT("\n::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::", Translator.TAG.SELECTION)); String[] manifestStrArray = manifestStr.split("[\\r\\n]+"); if (textFromTypingArea.length() > 2) { for (int i = 0; i < manifestStrArray.length; i++) { if (manifestStrArray[i].contains(textFromTypingArea) || manifestStrArray[i].equalsIgnoreCase(textFromTypingArea)) { result.add(new Translator.ELEMENT("\n", Translator.TAG.ANNOTATION)); if (i > 2) { int j = i; result.add(new Translator.ELEMENT(manifestStrArray[j - 1], Translator.TAG.ANNOTATION)); result.add(new Translator.ELEMENT( manifestStrArray[j - 2], Translator.TAG.ANNOTATION)); } result.add(new Translator.ELEMENT( manifestStrArray[i], Translator.TAG.IDENTIFIER)); if (i < manifestStrArray.length - 4) { int j = i; result.add(new Translator.ELEMENT(manifestStrArray[j + 1], Translator.TAG.ANNOTATION)); result.add(new Translator.ELEMENT(manifestStrArray[j + 2], Translator.TAG.ANNOTATION)); } result.add(new Translator.ELEMENT("", Translator.TAG.ANNOTATION)); result.add(new Translator.ELEMENT("::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::", Translator.TAG.SELECTION)); } } result.add(new Translator.ELEMENT("", Translator.TAG.ANNOTATION)); } return result; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/SilverGhostFacade.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost; import com.google.classyshark.silverghost.contentreader.ContentReader; import com.google.classyshark.silverghost.exporter.Exporter; import com.google.classyshark.silverghost.exporter.FlatMethodCountExporter; import com.google.classyshark.silverghost.exporter.MethodCountExporter; import com.google.classyshark.silverghost.exporter.TreeMethodCountExporter; import com.google.classyshark.silverghost.methodscounter.ClassNode; import com.google.classyshark.silverghost.methodscounter.RootBuilder; import com.google.classyshark.silverghost.translator.Translator; import com.google.classyshark.silverghost.translator.TranslatorFactory; import com.google.classyshark.silverghost.translator.apk.ApkTranslator; import com.google.classyshark.silverghost.translator.dex.DexMethodsDumper; import com.google.classyshark.silverghost.translator.dex.DexStringsDumper; import java.io.File; import java.io.PrintWriter; import java.util.LinkedList; import java.util.List; /** * Basic API class with small independent scenarios */ public class SilverGhostFacade { private SilverGhostFacade() { } public static List getAllClassNames(File archiveFile) { ContentReader loader = new ContentReader(archiveFile); loader.load(); return loader.getAllClassNames(); } public static void exportClassFromApk(List args) { File apk = new File(args.get(1)); String className = args.get(2); Translator translator = TranslatorFactory.createTranslator(className, apk, getAllClassNames(apk)); try { translator.apply(); } catch (NullPointerException npe) { System.err.println("Class doesn't exist in the writeArchive"); return; } try { Exporter.writeCurrentClass(translator.getClassName(), translator.toString()); } catch (Exception e) { System.err.println("Internal error - couldn't write file"); } } public static String getGeneratedClassString(String className, File archiveFile) { String result; Translator translator = TranslatorFactory.createTranslator(className, archiveFile, getAllClassNames(archiveFile)); try { translator.apply(); result = translator.toString(); } catch (NullPointerException npe) { System.out.println("Class doesn't exist in the writeArchive"); return ""; } return result; } public static void inspectApk(List args) { if (!new File(args.get(1)).getName().endsWith(".apk")) { System.err.println("Not an apk file ==> " + "java -jar ClassyShark.jar " + "-inspect APK_FILE"); return; } Translator translator = new ApkTranslator(new File(args.get(1))); translator.apply(); System.out.print(translator); } public static String getManifest(File archiveFile) { if (!archiveFile.getName().endsWith(".apk")) { return ""; } Translator translator = TranslatorFactory.createTranslator("AndroidManifest.xml", archiveFile); translator.apply(); return translator.toString(); } public static List getAllMethods(File archiveFile) { if (!archiveFile.getName().endsWith(".apk")) { return new LinkedList<>(); } return DexMethodsDumper.dumpMethods(archiveFile); } public static void inspectPackages(List args) { String fileName = args.get(1); File file = new File(fileName); if (!file.exists()) { System.err.printf("File '%s' does not exist", fileName); return; } RootBuilder rootBuilder = new RootBuilder(); MethodCountExporter methodCountExporter = new TreeMethodCountExporter(new PrintWriter(System.out)); if (args.size() > 2) { for (int i = 2; i < args.size(); i++) { if (args.get(i).equals("-flat")) { methodCountExporter = new FlatMethodCountExporter(new PrintWriter(System.out)); } } } ClassNode rootNode = rootBuilder.fillClassesWithMethods(fileName); methodCountExporter.exportMethodCounts(rootNode); } public static List getAllStrings(File archiveFile) { if (!archiveFile.getName().endsWith(".apk")) { return new LinkedList<>(); } return DexStringsDumper.dumpStrings(archiveFile); } public static void exportArchive(List args) { File apk = new File(args.get(1)); try { Exporter.writeArchive(apk, getAllClassNames(apk)); } catch (Exception e) { System.err.println("Internal error - couldn't write file"); } } /** * returns true if the apk is multidex * * @param archiveFile * @return */ public static boolean isMultiDex(File archiveFile) { ContentReader contentReader = new ContentReader(archiveFile); contentReader.load(); List allClassNames = contentReader.getAllClassNames(); int numDexes = 0; for (String classEntry : allClassNames) { if (classEntry.endsWith(".dex")) { numDexes++; // 2 dexes or more + optimization if (numDexes == 2) { return true; } } } return false; } /** * returns true if the apk is custom dex loading multidex * * @param archiveFile * @return */ public static boolean isCustomMultiDex(File archiveFile) { if (!isMultiDex(archiveFile)) { return false; } ContentReader contentReader = new ContentReader(archiveFile); contentReader.load(); List allClassNames = contentReader.getAllClassNames(); List allDexNames = new LinkedList<>(); for (String classEntry : allClassNames) { if (classEntry.endsWith(".dex")) { allDexNames.add(classEntry); } } if(allClassNames.contains("classes1.dex")) { return true; } for (String classEntry : allDexNames) { if (!classEntry.startsWith("classes")) { return true; } } return false; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/TokensMapper.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost; import java.io.File; import java.util.Map; public interface TokensMapper { TokensMapper readMappings(File file); Map getReverseClasses(); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/contentreader/BinaryContentReader.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.contentreader; import java.util.List; /** * */ public interface BinaryContentReader { void read(); List getClassNames(); List getComponents(); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/contentreader/ContentReader.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.contentreader; import com.google.classyshark.silverghost.contentreader.aar.AarReader; import com.google.classyshark.silverghost.contentreader.apk.ApkReader; import com.google.classyshark.silverghost.contentreader.clazz.ClazzReader; import com.google.classyshark.silverghost.contentreader.dex.DexReader; import com.google.classyshark.silverghost.contentreader.jar.JarReader; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Is a function : (binary file) --> {contents: classnames, components} */ public class ContentReader { /** * components that are part of jar & apk */ public enum ARCHIVE_COMPONENT { ANDROID_MANIFEST, NATIVE_LIBRARY } public static class Component { public Component(String name, ARCHIVE_COMPONENT component) { this.name = name; this.component = component; } public String name; public ARCHIVE_COMPONENT component; } private BinaryContentReader formatReader; private List allClassNames; public ContentReader(File binaryArchive) { allClassNames = new ArrayList<>(); final String archiveName = binaryArchive.getName().toLowerCase(); if (archiveName.endsWith(".jar")) { formatReader = new JarReader(binaryArchive); } else if (archiveName.endsWith(".dex")) { formatReader = new DexReader(binaryArchive); } else if (archiveName.endsWith(".apk")) { formatReader = new ApkReader(binaryArchive); } else if (archiveName.endsWith(".aar")) { formatReader = new AarReader(binaryArchive); } else { formatReader = new ClazzReader(binaryArchive); } } public void load() { try { if (allClassNames.isEmpty()) { formatReader.read(); allClassNames = formatReader.getClassNames(); } } catch (Exception e) { allClassNames = new ArrayList<>(); } } public List getAllClassNames() { // TODO add wrong state exception if read wasn't called return Collections.unmodifiableList(allClassNames); } public List getAllComponents() { return formatReader.getComponents(); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/contentreader/aar/AarReader.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.contentreader.aar; import com.google.classyshark.silverghost.contentreader.BinaryContentReader; import com.google.classyshark.silverghost.contentreader.ContentReader; import com.google.classyshark.silverghost.contentreader.jar.JarReader; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class AarReader implements BinaryContentReader { private File binaryArchive; private List allClassNames = new ArrayList<>(); private List components = new ArrayList<>(); public AarReader(File binaryArchive) { this.binaryArchive = binaryArchive; } @Override public void read() { try { File file = File.createTempFile("classes", "jar"); file.deleteOnExit(); OutputStream out = new FileOutputStream(file); FileInputStream fin = new FileInputStream(binaryArchive); BufferedInputStream bin = new BufferedInputStream(fin); ZipInputStream zin = new ZipInputStream(bin); ZipEntry ze; while ((ze = zin.getNextEntry()) != null) { if (ze.getName().endsWith(".jar")) { byte[] buffer = new byte[8192]; int len; while ((len = zin.read(buffer)) != -1) { out.write(buffer, 0, len); } allClassNames.addAll( JarReader.readClassNamesFromJar(file, this.components)); } if (ze.getName().equals("AndroidManifest.xml")) { allClassNames.add("AndroidManifest.xml"); } } out.close(); zin.closeEntry(); zin.close(); } catch (Exception e) { } } @Override public List getClassNames() { return this.allClassNames; } @Override public List getComponents() { return this.components; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/contentreader/apk/ApkReader.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.contentreader.apk; import com.google.classyshark.silverghost.contentreader.BinaryContentReader; import com.google.classyshark.silverghost.contentreader.ContentReader; import java.io.File; import java.util.ArrayList; import java.util.List; import static com.google.classyshark.silverghost.translator.java.dex.MultidexReader.readClassNamesFromMultidex; public class ApkReader implements BinaryContentReader { private File binaryArchive; private List allClassNames = new ArrayList<>(); private List components = new ArrayList<>(); public ApkReader(File binaryArchive) { this.binaryArchive = binaryArchive; } @Override public void read() { readClassNamesFromMultidex(binaryArchive, allClassNames, components); // TODO add isPrivate for manifest // allClassNames.add(6, "AndroidManifest.xml"); } @Override public List getClassNames() { return allClassNames; } @Override public List getComponents() { // TODO add manifest here return components; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/contentreader/clazz/ClassNameVisitor.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.contentreader.clazz; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; public class ClassNameVisitor extends ClassVisitor { private String name; public ClassNameVisitor() { super(Opcodes.ASM5); } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.name = name.replaceAll("/", "\\."); } public String getName() { return name; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/contentreader/clazz/ClazzReader.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.contentreader.clazz; import com.google.classyshark.silverghost.contentreader.BinaryContentReader; import com.google.classyshark.silverghost.contentreader.ContentReader; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import org.objectweb.asm.ClassReader; public class ClazzReader implements BinaryContentReader { private File binaryArchive; private List allClassNames = new ArrayList<>(); public ClazzReader(File binaryArchive) { this.binaryArchive = binaryArchive; } @Override public void read() { try { Path path = Paths.get(binaryArchive.getAbsolutePath()); byte[] bytes = Files.readAllBytes(path); ClassNameVisitor classNameVisitor = new ClassNameVisitor(); ClassReader cr = new ClassReader(bytes); cr.accept(classNameVisitor, 0); String className = classNameVisitor.getName(); allClassNames.add(className); } catch (IOException e) { e.printStackTrace(); } } @Override public List getClassNames() { return allClassNames; } @Override public List getComponents() { return new ArrayList<>(); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/contentreader/dex/DexReader.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.contentreader.dex; import com.google.classyshark.silverghost.contentreader.BinaryContentReader; import com.google.classyshark.silverghost.contentreader.ContentReader; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.DexFile; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class DexReader implements BinaryContentReader { private File binaryArchive; private List allClassNames = new ArrayList<>(); public DexReader(File binaryArchive) { this.binaryArchive = binaryArchive; } @Override public void read() { try { allClassNames = readClassNamesFromDex(binaryArchive); } catch (Exception e) { e.printStackTrace(); } Collections.sort(allClassNames); } @Override public List getClassNames() { return allClassNames; } @Override public List getComponents() { return new ArrayList<>(); } public static List readClassNamesFromDex(File binaryArchiveFile) throws Exception { DexFile dexFile = DexlibLoader.loadDexFile(binaryArchiveFile); List result = new ArrayList<>(); for (ClassDef classDef : dexFile.getClasses()) { result.add(classDef.getType().replaceAll("/", "."). substring(1, classDef.getType().length() - 1)); } Collections.sort(result); return result; } public static void main(String[] args) { DexReader dxr = new DexReader(new File("//Users//bfarber//Desktop//classes.dex")); dxr.read(); System.out.println(dxr.getClassNames()); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/contentreader/dex/DexlibLoader.java ================================================ package com.google.classyshark.silverghost.contentreader.dex; import java.io.File; import org.jf.dexlib2.DexFileFactory; import org.jf.dexlib2.Opcodes; import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.iface.DexFile; public class DexlibLoader { public static DexFile loadDexFile(File binaryArchiveFile) throws Exception { DexBackedDexFile newDexFile = DexFileFactory.loadDexFile(binaryArchiveFile, Opcodes.forApi(19)); return newDexFile; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/contentreader/jar/JarReader.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.contentreader.jar; import com.google.classyshark.silverghost.contentreader.BinaryContentReader; import com.google.classyshark.silverghost.contentreader.ContentReader; import java.io.File; import java.io.FileInputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; public class JarReader implements BinaryContentReader { protected final File binaryArchive; protected List allClassNames = new ArrayList<>(); private List components = new ArrayList<>(); public JarReader(File binaryArchive) { this.binaryArchive = binaryArchive; } @Override public void read() { try { allClassNames = readClassNamesFromJar(binaryArchive, components); } catch (Exception e) { e.printStackTrace(); } Collections.sort(allClassNames); } @Override public List getClassNames() { return allClassNames; } @Override public List getComponents() { return this.components; } public static List readClassNamesFromJar(File jarFile, List components) throws Exception { List classes = new ArrayList<>(); JarInputStream jarFileStream = new JarInputStream(new FileInputStream( jarFile)); JarEntry jarEntry; String formattedClassName; String jarEntryName; while (true) { jarEntry = jarFileStream.getNextJarEntry(); if (jarEntry == null) { break; } jarEntryName = jarEntry.getName(); if (jarEntryName.endsWith(".class")) { formattedClassName = jarEntryName.replaceAll("/", "\\."); formattedClassName = formattedClassName.substring(0, formattedClassName.lastIndexOf('.')); classes.add(formattedClassName); } // native libs in jar // TODO add checking if (jarEntry.getName().startsWith("resources") && jarEntry.getName().startsWith(".so")) { components.add( new ContentReader.Component(jarEntry.getName(), ContentReader.ARCHIVE_COMPONENT.NATIVE_LIBRARY)); } } if (classes.isEmpty()) { throw new Exception(); } return classes; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/exporter/Exporter.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.exporter; import com.google.classyshark.silverghost.contentreader.ContentReader; import com.google.classyshark.silverghost.methodscounter.ClassNode; import com.google.classyshark.silverghost.methodscounter.RootBuilder; import com.google.classyshark.silverghost.translator.Translator; import com.google.classyshark.silverghost.translator.TranslatorFactory; import com.google.classyshark.silverghost.translator.dex.DexMethodsDumper; import com.google.classyshark.silverghost.translator.dex.DexStringsDumper; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.List; /** * exports the data to text files */ public class Exporter { private Exporter() { } public static void writeArchive(File archive, List allClasses) throws Exception { Exporter.writeManifest(archive); Exporter.writeClassNames(allClasses); Exporter.writeMethods(archive); Exporter.writeStringTables(archive); Exporter.writeMethodCounts(archive); } public static void writeCurrentClass(String className, String content) throws IOException { writeString(className + "_dump", content); } public static void writeMethodCounts(File archive) { File outputFile = new File("method_counts.txt"); System.out.println(outputFile.toString()); try (PrintWriter pw = new PrintWriter(outputFile)){ RootBuilder rootBuilder = new RootBuilder(); ClassNode classNode = rootBuilder.fillClassesWithMethods(archive); MethodCountExporter methodCountExporter = new TreeMethodCountExporter(pw); methodCountExporter.exportMethodCounts(classNode); } catch (IOException ex) { ex.printStackTrace(System.err); } } public static void writeManifest(File apk) throws IOException { if (apk.getName().endsWith(".apk")) { Translator translator = TranslatorFactory.createTranslator("AndroidManifest.xml", apk); translator.apply(); writeString(translator.getClassName() + "_dump", translator.toString()); } } public static void writeClassNames(List allClassNames) throws IOException { writeListStrings(new File("all_classes.txt"), allClassNames); } public static void writeMethods(File archiveFile) { if (!archiveFile.getName().endsWith(".apk")) { return; } List allMethods = DexMethodsDumper.dumpMethods(archiveFile); writeListStrings(new File("all_methods.txt"), allMethods); } public static void writeStringTables(File archiveFile) { if (!archiveFile.getName().endsWith(".apk")) { return; } List allStrings = DexStringsDumper.dumpStrings(archiveFile); writeListStringsChannel(new File("all_strings.txt"), allStrings); } private static void writeString(String to, String what) throws IOException { FileWriter writer; writer = new FileWriter(to); writer.write(what); writer.close(); } private static void writeListStringsChannel(File to, List allStrings) { try { byte[] buffer = (" " + " \n").getBytes(); FileChannel rwChannel = new RandomAccessFile(to, "rw").getChannel(); ByteBuffer wrBuf = rwChannel.map(FileChannel.MapMode.READ_WRITE, 0, allStrings.size() * buffer.length); for (int i = 0; i < allStrings.size(); i++) { wrBuf.put(allStrings.get(i).getBytes()); } rwChannel.close(); } catch (IOException ioe) { } } private static void writeListStrings(File to, List allStrings) { try { FileWriter writer = new FileWriter(to); for (String str : allStrings) { writer.write("\n" + str); } writer.close(); } catch (IOException ioe) { } } public static void main(String[] args) throws Exception { String allAndroid = System.getProperty("user.home") + "/Desktop/Scenarios/2 Samples/android.jar"; ContentReader loader = new ContentReader(new File(allAndroid)); loader.load(); writeClassNames(loader.getAllClassNames()); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/exporter/FlatMethodCountExporter.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.exporter; import com.google.classyshark.silverghost.methodscounter.ClassNode; import java.io.PrintWriter; import java.util.Iterator; public class FlatMethodCountExporter implements MethodCountExporter { private PrintWriter pw; public FlatMethodCountExporter(PrintWriter pw) { this.pw = pw; } public void exportMethodCounts(ClassNode rootNode) { printNode(rootNode, new String[]{}); pw.flush(); } private void printNode(ClassNode classNode, String[] path) { for (String p: path) { pw.print(p); pw.print('.'); } pw.println(classNode.getKey() + " - " + classNode.getMethodCount()); Iterator it = classNode.getChildNodes().values().iterator(); String[] newPath = new String[path.length + 1]; System.arraycopy(path, 0, newPath, 0, path.length); newPath[newPath.length - 1] = classNode.getKey(); while (it.hasNext()) { ClassNode child = it.next(); printNode(child, newPath); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/exporter/MethodCountExporter.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.exporter; import com.google.classyshark.silverghost.methodscounter.ClassNode; public interface MethodCountExporter { void exportMethodCounts(ClassNode rootNode); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/exporter/TreeMethodCountExporter.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.exporter; import com.google.classyshark.silverghost.methodscounter.ClassNode; import java.io.PrintWriter; import java.util.Iterator; public class TreeMethodCountExporter implements MethodCountExporter { private PrintWriter pw; public TreeMethodCountExporter(PrintWriter pw) { this.pw = pw; } public void exportMethodCounts(ClassNode rootNode) { printNode(rootNode, new boolean[]{true}); pw.flush(); } private void printNode(ClassNode classNode, boolean[] isFinalLevel) { renderTreeStructure(isFinalLevel); pw.println(classNode.getKey() + " - " + classNode.getMethodCount()); Iterator it = classNode.getChildNodes().values().iterator(); boolean[] isFinalLevel2 = new boolean[isFinalLevel.length + 1]; System.arraycopy(isFinalLevel, 0, isFinalLevel2, 0, isFinalLevel.length); while (it.hasNext()) { ClassNode child = it.next(); isFinalLevel2[isFinalLevel2.length - 1] = !it.hasNext(); printNode(child, isFinalLevel2); } } private void renderTreeStructure(boolean[] levels) { if (levels.length == 1) { return; } for (int i = 1; i < levels.length; i++) { if (i == levels.length - 1) { if (levels[i]) { pw.print(" \u255A"); } else { pw.print(" \u2560"); } } else { if (levels[i]) { pw.print(" "); //Add another space to compensate the equals sign if (i > 0) { pw.print(" "); } } else { pw.print(" \u2551"); } } } pw.print('\u2550'); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/io/SherlockHash.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.io; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Map; import java.util.TreeMap; import java.util.zip.ZipInputStream; public enum SherlockHash { INSTANCE; private Map mapPerBinary = new TreeMap<>(); private static class BinaryPack { private final long timeStamp; private Map map = new TreeMap<>(); public BinaryPack(long timeStamp) { this.timeStamp = timeStamp; } } public File getFileFromZipStream(File binaryFile, ZipInputStream zipInputStream, String fName, String ext) throws IOException { BinaryPack pack = mapPerBinary.get(binaryFile.getCanonicalPath()); if (pack == null || pack.timeStamp != binaryFile.lastModified()) { pack = new BinaryPack(binaryFile.lastModified()); mapPerBinary.put(binaryFile.getCanonicalPath(), pack); } String innerFileName = fName + ext; File file = pack.map.get(innerFileName); if (file != null) { return file; } file = File.createTempFile(fName, ext); file.deleteOnExit(); try (FileOutputStream fos = new FileOutputStream(file)) { byte[] bytes = new byte[1024 * 4]; int length; while ((length = zipInputStream.read(bytes)) >= 0) { fos.write(bytes, 0, length); } pack.map.put(innerFileName, file); } return file; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/methodscounter/ClassInfo.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.methodscounter; public class ClassInfo { private String packageName; private int methodCount; public ClassInfo(String packageName, int methodCount) { this.packageName = packageName; this.methodCount = methodCount; } public String getPackageName() { return packageName; } public int getMethodCount() { return methodCount; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/methodscounter/ClassNode.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.methodscounter; import java.util.HashMap; import java.util.Map; public class ClassNode { private Map childNodes = new HashMap<>(); private String key; private int methodCount = 0; public ClassNode(String key) { this.key = key; } public ClassNode() { } public Map getChildNodes() { return childNodes; } public String getKey() { return key; } public int getMethodCount() { return methodCount; } public void add(ClassInfo classInfo) { String[] packages = classInfo.getPackageName().split("\\."); int pos = 0; add(pos, packages, classInfo); } private void add(int currentPost, String[] packages, ClassInfo classInfo) { methodCount = methodCount + classInfo.getMethodCount(); if (currentPost >= packages.length) { return; } ClassNode child = childNodes.get(packages[currentPost]); if (child == null) { child = new ClassNode(); child.key = packages[currentPost]; childNodes.put(packages[currentPost], child); } child.add(currentPost + 1, packages, classInfo); } @Override public String toString() { return key + ": "+ methodCount; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/methodscounter/RootBuilder.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.methodscounter; import com.google.classyshark.silverghost.contentreader.dex.DexlibLoader; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Enumeration; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.JavaClass; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.Method; /** * */ public class RootBuilder { public ClassNode fillClassesWithMethods(File file) { if (file.getName().endsWith("aar")) { return fillFromAar(file); } else if (file.getName().endsWith("jar")) { return fillFromJar(file); } else if (file.getName().endsWith("dex")) { return fillFromDex(file); } return fillFromApk(file); } public ClassNode fillClassesWithMethods(String fileName) { return fillClassesWithMethods(new File(fileName)); } private ClassNode fillFromAar(File file) { try { File tempFile = File.createTempFile("classes", "jar"); tempFile.deleteOnExit(); FileInputStream fin = new FileInputStream(file); BufferedInputStream bin = new BufferedInputStream(fin); ZipInputStream zin = new ZipInputStream(bin); OutputStream out = new FileOutputStream(tempFile); ZipEntry ze; while ((ze = zin.getNextEntry()) != null) { if (ze.getName().endsWith(".jar")) { byte[] buffer = new byte[8192]; int len; while ((len = zin.read(buffer)) != -1) { out.write(buffer, 0, len); } out.close(); zin.closeEntry(); zin.close(); return fillFromJar(tempFile); } } } catch (Exception e) { } return new ClassNode(""); } private ClassNode fillFromJar(File file) { ClassNode rootNode = new ClassNode(file.getName()); try { JarFile theJar = new JarFile(file); Enumeration en = theJar.entries(); while (en.hasMoreElements()) { JarEntry entry = en.nextElement(); if (entry.getName().endsWith(".class")) { ClassParser cp = new ClassParser( theJar.getInputStream(entry), entry.getName()); JavaClass jc = cp.parse(); ClassInfo classInfo = new ClassInfo(jc.getClassName(), jc.getMethods().length); rootNode.add(classInfo); } } } catch (IOException e) { System.err.println("Error reading file: " + file + ". " + e.getMessage()); e.printStackTrace(System.err); } return rootNode; } private ClassNode fillFromJayce(File file) { ClassNode rootNode = new ClassNode(file.getName()); rootNode.add(new ClassInfo("not ready", 1)); // TODO threading need to wait till the content reader finishes /* JayceReader jr = new JayceReader(file); jr.read(); for(String clazz: jr.getClassNames()) { ClassInfo classInfo = new ClassInfo(clazz, jr.getCache().get(clazz).getMethods().size()); rootNode.add(classInfo); }*/ return rootNode; } private void fillFromDex(File file, ClassNode rootNode) { try { DexFile dxFile = DexlibLoader.loadDexFile(file); Set classSet = dxFile.getClasses(); for (ClassDef o : classSet) { int methodCount = 0; for (Method method : o.getMethods()) { methodCount++; } String translatedClassName = o.getType().replaceAll("\\/", "\\.").substring(1, o.getType().length() - 1); ClassInfo classInfo = new ClassInfo(translatedClassName, methodCount); rootNode.add(classInfo); } } catch (Exception ex) { System.err.println("Error parsing Dexfile: " + file.getName() + ": " + ex.getMessage()); ex.printStackTrace(System.err); } } private ClassNode fillFromDex(File file) { ClassNode rootNode = new ClassNode(file.getName()); fillFromDex(file, rootNode); return rootNode; } private ClassNode fillFromApk(File file) { ClassNode rootNode = new ClassNode(file.getName()); try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(file))) { ZipEntry zipEntry; byte[] buffer = new byte[1024]; while ((zipEntry = zipInputStream.getNextEntry()) != null) { if (!zipEntry.getName().endsWith(".dex")) { continue; } System.out.println("Parsing " + zipEntry.getName()); File tempFile = File.createTempFile("classyshark", "dex"); tempFile.deleteOnExit(); try (FileOutputStream fout = new FileOutputStream(tempFile)) { int read; while ((read = zipInputStream.read(buffer)) > 0) { fout.write(buffer, 0, read); } } fillFromDex(tempFile, rootNode); } } catch (IOException ex) { System.err.println("Error reading file: " + file + ". " + ex.getMessage()); ex.printStackTrace(System.err); } return rootNode; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/plugins/EmptyFullArchiveReader.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.plugins; import com.google.classyshark.silverghost.FullArchiveReader; import com.google.classyshark.silverghost.TokensMapper; import com.google.classyshark.silverghost.translator.Translator; import java.io.File; import java.util.LinkedList; import java.util.List; public class EmptyFullArchiveReader implements FullArchiveReader{ @Override public void readAsyncArchive(File file) { } @Override public Translator buildTranslator(String className, File archiveFile) { return new Translator() { @Override public String getClassName() { return "Empty"; } @Override public void addMapper(TokensMapper reverseMappings) { } @Override public void apply() { } @Override public List getElementsList() { return new LinkedList<>(); } @Override public List getDependencies() { return new LinkedList<>(); } }; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/plugins/IdentityMapper.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.plugins; import com.google.classyshark.silverghost.TokensMapper; import java.io.File; import java.util.Map; import java.util.TreeMap; public class IdentityMapper implements TokensMapper { private Map identityMap = new TreeMap<>(); public IdentityMapper() { } @Override public TokensMapper readMappings(File file) { return this; } @Override public Map getReverseClasses() { return this.identityMap; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/Translator.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator; import com.google.classyshark.silverghost.TokensMapper; import java.util.List; /** * Is a function : (binary data, name) --> list of elements , * with human readable semantics */ public interface Translator { /** * TAG representing type */ enum TAG { MODIFIER, IDENTIFIER, ANNOTATION, DOCUMENT, XML_TAG, XML_ATTR_NAME, XML_ATTR_VALUE, XML_CDATA, XML_COMMENT, XML_DEFAULT, SELECTION } /** * element = word with tag */ class ELEMENT { public ELEMENT(String word, TAG tag) { this.text = word; this.tag = tag; } public final String text; public final TAG tag; } String getClassName(); void addMapper(TokensMapper reverseMappings); void apply(); List getElementsList(); List getDependencies(); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/TranslatorFactory.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator; import com.google.classyshark.silverghost.FullArchiveReader; import com.google.classyshark.silverghost.plugins.EmptyFullArchiveReader; import com.google.classyshark.silverghost.translator.apk.ApkTranslator; import com.google.classyshark.silverghost.translator.dex.DexInfoTranslator; import com.google.classyshark.silverghost.translator.elf.ElfTranslator; import com.google.classyshark.silverghost.translator.jar.JarInfoTranslator; import com.google.classyshark.silverghost.translator.java.JavaTranslator; import com.google.classyshark.silverghost.translator.xml.AndroidXmlTranslator; import java.io.File; import java.util.LinkedList; import java.util.List; /** * Creates translators based on class names and archives */ public class TranslatorFactory { public static Translator createTranslator(String className, File archiveFile) { return createTranslator(className, archiveFile, new LinkedList(), null); } public static Translator createTranslator(String className, File archiveFile, List allClassNames) { return createTranslator(className, archiveFile, allClassNames, null); } public static Translator createTranslator(String className, File archiveFile, List allClassNames, FullArchiveReader fullArchiveReader) { if (className.endsWith(".xml")) { return new AndroidXmlTranslator(className, archiveFile); } if (className.endsWith(".dex")) { return new DexInfoTranslator(className, archiveFile); } if (className.endsWith(".jar")) { // TODO: does it make any sense to check for jayce subvariant? // size does not make much sense as jayce jar may include other files beyond the code return new JarInfoTranslator(archiveFile, allClassNames); } if (className.endsWith(".apk")) { return new ApkTranslator(archiveFile); } if (className.endsWith(".so")) { return new ElfTranslator(className, archiveFile); } if (fullArchiveReader != null && !(fullArchiveReader instanceof EmptyFullArchiveReader)) { return fullArchiveReader.buildTranslator(className, archiveFile); } return new JavaTranslator(className, archiveFile); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/ApkTranslator.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk; import com.google.classyshark.silverghost.TokensMapper; import com.google.classyshark.silverghost.translator.Translator; import com.google.classyshark.silverghost.translator.apk.dashboard.ApkDashboard; import java.io.File; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * Translator for the apk */ public class ApkTranslator implements Translator { private File apkFile; private ApkDashboard apkDashboard; private List elements = new ArrayList<>(); public ApkTranslator(File apkFile) { // TODO add checks for file that is not an APK this.apkFile = apkFile; } @Override public String getClassName() { return ""; } @Override public void addMapper(TokensMapper reverseMappings) { } @Override public void apply() { apkDashboard = new ApkDashboard(apkFile); apkDashboard.inspect(); ELEMENT element = new ELEMENT("\n ~ APK DASHBOARD ~\n\n", TAG.IDENTIFIER); elements.add(element); element = new ELEMENT(apkDashboard.toString(), TAG.DOCUMENT); elements.add(element); } @Override public List getElementsList() { return elements; } @Override public List getDependencies() { return new LinkedList<>(); } public String toString() { StringBuilder sb = new StringBuilder(); for (ELEMENT element : elements) { sb.append(element.text); } return sb.toString(); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/ApkDashboard.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard; import com.google.classyshark.silverghost.contentreader.dex.DexlibLoader; import com.google.classyshark.silverghost.translator.apk.dashboard.manifest.ManifestInspector; import com.google.classyshark.silverghost.translator.java.dex.MultidexReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.iface.DexFile; import org.ow2.asmdex.ApplicationReader; import org.ow2.asmdex.ApplicationVisitor; import org.ow2.asmdex.Opcodes; public class ApkDashboard { public ArrayList classesDexEntries = new ArrayList<>(); public ArrayList customClassesDexEntries = new ArrayList<>(); public List nativeLibs = new ArrayList<>(); public ArrayList nativeDependencies = new ArrayList<>(); public List nativeErrors = new ArrayList<>(); public List allClasses = new ArrayList<>(); public File apkFile; public static final String[] NEW_LINE = {" ", " "}; public ApkDashboard(File apkFile) { this.apkFile = apkFile; } public void inspect() { // TODO add exception for not calling inspect MultidexReader.fillApkDashboard(apkFile, this); } public List getAllDexEntries() { ArrayList result = new ArrayList<>(); result.addAll(classesDexEntries); result.addAll(customClassesDexEntries); return result; } public List getFullPathNativeLibNamesSorted() { Collections.sort(nativeLibs); return nativeLibs; } public List getNativeLibNamesSorted() { Set uniqueDependencies = new LinkedHashSet<>(nativeDependencies); LinkedList sortedNativeDependencies = new LinkedList<>(uniqueDependencies); Collections.sort(sortedNativeDependencies); List nativeLibNames = extractLibNamesFromFullPaths(getFullPathNativeLibNamesSorted()); return nativeLibNames; } public String getPrivateLibErrorTag(String nativeLib) { boolean isPrivate = PrivateNativeLibsInspector.isPrivate(nativeLib, getNativeLibNamesSorted()); if (isPrivate) { return " -- private api!"; } else { return ""; } } public static Set getClassesWithNativeMethodsPerDexIndex(int dexIndex, File classesDex) { ClassesDexDataEntry dexInspectionsData = ApkDashboard.fillAnalysisPerClassesDexIndex(dexIndex, classesDex); return dexInspectionsData.classesWithNativeMethods; } public List getJavaDependenciesErrors() { JavaDependenciesInspector ddi = new JavaDependenciesInspector(allClasses); List result = ddi.getInspections(); return result; } public static ClassesDexDataEntry fillAnalysisPerClassesDexIndex(int dexIndex, File classesDex) { ClassesDexDataEntry dexData = new ClassesDexDataEntry(dexIndex); try { InputStream is = new FileInputStream(classesDex); ApplicationVisitor av = new ApkNativeMethodsVisitor(dexData); ApplicationReader ar = new ApplicationReader(Opcodes.ASM4, is); ar.accept(av, 0); } catch (Exception e) { e.printStackTrace(); } try { DexFile dxFile = DexlibLoader.loadDexFile(classesDex); DexBackedDexFile dataPack = (DexBackedDexFile) dxFile; dexData.allMethods = dataPack.getMethodCount(); //dexData.syntheticAccessors = // new SyntheticAccessorsInspector(dxFile).getSyntheticAccessors(); } catch (Exception e) { e.printStackTrace(); System.out.println("here " + e); } return dexData; } private static List extractLibNamesFromFullPaths(List nativeLibs) { // libs/x86/lib.so\n ==> libs.so LinkedList result = new LinkedList<>(); for (String nativeLib : nativeLibs) { String simpleNativeLibName = nativeLib; if (nativeLib.contains("/")) { simpleNativeLibName = simpleNativeLibName.substring(simpleNativeLibName.lastIndexOf("/") + 1, simpleNativeLibName.length() - 1); } result.add(simpleNativeLibName); } return result; } public List getManifestRecommendations() { ManifestInspector mi = new ManifestInspector(apkFile); List result = mi.getInspections(); return result; } public String toString() { String[] columnHeaders = {"Recommendation", "Description"}; List rows = new LinkedList<>(); rows.add(NEW_LINE); for (ClassesDexDataEntry dexEntry : getAllDexEntries()) { addRow(rows, dexEntry.getName(), String.valueOf(dexEntry.allMethods) + " methods"); } rows.add(NEW_LINE); for (String javaDepError : getJavaDependenciesErrors()) { addRow(rows, "Java ", javaDepError); } rows.add(NEW_LINE); for (String systemBroadcast : getManifestRecommendations()) { addRow(rows, "System Broadcast ", systemBroadcast); } rows.add(NEW_LINE); for (String nativeLib : getNativeLibNamesSorted()) { if (!getPrivateLibErrorTag(nativeLib).isEmpty()) { addRow(rows, "Native Error ", nativeLib + " " + getPrivateLibErrorTag(nativeLib)); } } String[][] array = new String[rows.size()][]; for (int i = 0; i < rows.size(); i++) { array[i] = rows.get(i); } return Table.getTable(columnHeaders, array).toString(); } private void addRow(List data, String param1, String param2) { List row = new LinkedList<>(); row.add(param1); row.add(param2); data.add(row.toArray(new String[0])); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/ApkNativeMethodsVisitor.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard; import java.lang.reflect.Modifier; import org.ow2.asmdex.ApplicationVisitor; import org.ow2.asmdex.ClassVisitor; import org.ow2.asmdex.MethodVisitor; import org.ow2.asmdex.Opcodes; public class ApkNativeMethodsVisitor extends ApplicationVisitor { private ClassesDexDataEntry dexData; public ApkNativeMethodsVisitor(ClassesDexDataEntry dexData) { super(Opcodes.ASM4); this.dexData = dexData; } public ClassVisitor visitClass(int access, String name, String[] signature, String superName, String[] interfaces) { final String mName = name; return new ClassVisitor(Opcodes.ASM4) { private String className = mName.replaceAll("\\/", "\\.").substring(1, mName.length() - 1); @Override public void visit(int version, int access, String name, String[] signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String[] signature, String[] exceptions) { if (Modifier.isNative(access)) { dexData.nativeMethodsCount++; dexData.classesWithNativeMethods.add(this.className); } return super.visitMethod(access, name, desc, signature, exceptions); } }; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/ClassesDexDataEntry.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard; import java.util.List; import java.util.Set; import java.util.TreeSet; public class ClassesDexDataEntry implements Comparable { public int index; public int nativeMethodsCount = 0; public Set classesWithNativeMethods = new TreeSet<>(); public int allMethods = 0; public List syntheticAccessors; public ClassesDexDataEntry(int index) { this.index = index; } @Override public int compareTo(Object o) { if (!(o instanceof ClassesDexDataEntry)) { return -1; } return -1 * Integer.valueOf(index).compareTo(((ClassesDexDataEntry) o).index); } public String getName() { if (index == 0) { return "classes.dex"; } if (index < 10) { return "classes" + index + ".dex"; } return "custom - classes.dex"; } public String toString() { return "\nclasses" + index + ".dex" + "\nnative methods: " + nativeMethodsCount + "\nclasses with native methods" + classesWithNativeMethods; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/DynamicSymbolsInspector.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard; import java.io.IOException; import nl.lxtreme.binutils.elf.Elf; public class DynamicSymbolsInspector { private Elf elf; private String errors = ""; private boolean areErrors = false; public DynamicSymbolsInspector(Elf elf) { this.elf = elf; inspect(); } public boolean areErrors() { return areErrors; } public String getErrors() { return this.errors; } private void inspect() { try { if (!elf.isSoname()) { errors += " missing SONAME "; areErrors = true; } if (elf.isTextRel()) { errors += " text relocations found "; areErrors = true; } } catch (IOException e) { e.printStackTrace(); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/JavaDependenciesInspector.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard; import java.util.ArrayList; import java.util.List; public class JavaDependenciesInspector { private final List allClasses; private int imageLoading = 0; private boolean hasGlide; private boolean hasPicasso; private boolean hasFresco; private int asyncHttp = 0; private boolean hasOkHttp; private boolean hasVolley; private boolean hasLoopj; private int jsonParsing = 0; private boolean hasJackson; private boolean hasGson; private boolean hasMoshi; // other dependencies private boolean hasGuava; private boolean hasDeprecatedHttp; private boolean hasActionBarSherlock; private boolean hasPullToRefresh; private boolean hasViewPagerIndicator; public JavaDependenciesInspector(List allClasses) { this.allClasses = allClasses; } public List getInspections() { List result = new ArrayList<>(); for (String cName : allClasses) { updateLogic(cName); } if (imageLoading > 1) { String line = "Duplicate image loading libraries - "; if (hasPicasso) line += " picasso "; if (hasGlide) line += "glide "; if (hasFresco) line += "fresco "; result.add(line); } if (asyncHttp > 1) { String line = "Duplicate async http libraries - "; if (hasOkHttp) line += "okhttp "; if (hasVolley) line += "volley "; if (hasLoopj) line += "loopj "; result.add(line); } if (jsonParsing > 1) { String line = "Duplicate json parsing - "; if (hasJackson) line += "jackson "; if (hasGson) line += "gson "; if (hasMoshi) line += "moshi "; result.add(line); } if (hasGuava) { result.add("Guava (server side library)usage"); } if (hasDeprecatedHttp) { result.add("Apache Http is deprecated"); } if (hasActionBarSherlock) { result.add("ActionBar Sherlock is deprecated"); } if (hasPullToRefresh) { result.add("PullToRefresh is deprecated"); } if (hasViewPagerIndicator) { result.add("ViewPagerIndicator is deprecated - use support library"); } return result; } private void updateLogic(String cName) { if (cName.contains("glide") && !hasGlide) { hasGlide = true; imageLoading++; } else if (cName.contains("picasso") && !hasPicasso) { hasPicasso = true; imageLoading++; } else if (cName.contains("fresco") && !hasFresco) { hasFresco = true; imageLoading++; } else if (cName.contains("okhttp") && !hasOkHttp) { hasOkHttp = true; asyncHttp++; } else if (cName.contains("volley") && !hasVolley) { hasVolley = true; asyncHttp++; } else if (cName.contains("loopj") && !hasLoopj) { hasLoopj = true; asyncHttp++; } else if (cName.contains("fasterxml.jackson") && !hasJackson) { hasJackson = true; jsonParsing++; } else if (cName.contains("google.code.gson") && !hasGson) { hasGson = true; jsonParsing++; } else if (cName.contains("squareup.moshi") && !hasMoshi) { hasMoshi = true; jsonParsing++; } else if (cName.contains("google.common") && !hasGuava) { hasGuava = true; } else if (cName.contains("apache.http") && !hasDeprecatedHttp) { hasDeprecatedHttp = true; } else if (cName.contains("'com.actionbarsherlock") && !hasActionBarSherlock) { hasActionBarSherlock = true; } else if (cName.contains("chrisbanes.pulltorefresh") && !hasPullToRefresh) { hasPullToRefresh = true; } else if (cName.contains("com.viewpagerindicator") && !hasViewPagerIndicator) { hasViewPagerIndicator = true; } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/PrivateNativeLibsInspector.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard; import java.util.Arrays; import java.util.LinkedList; import java.util.List; public class PrivateNativeLibsInspector { private static final String[] apiLibs = { "libz.so", "libz.a", "libvulkan.so", "libstdc++.so", "libstdc++.a", "libmediandk.so", "libm.so", "libm.a", "liblog.so", "libjnigraphics.so", "libdl.so", "libc.so", "libc.a", "libandroid.so", "libOpenSLES.so", "libOpenMAXAL.so", "libGLESv3.so", "libGLESv2.so", "libGLESv1_CM.so", "libEGL.so", "crtend_so.o", "crtend_android.o", "crtbegin_static.o", "crtbegin_so.o", "crtbegin_dynamic.o", "lsOutput.log" }; private static List APIS_LIB_LIST = new LinkedList<>(Arrays.asList(apiLibs)); public static boolean isPrivate(String nativeLib, List nativeLibNames) { if (!APIS_LIB_LIST.contains(nativeLib) && !nativeLibNames.contains(nativeLib)) { return true; } return false; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/SyntheticAccessorsInspector.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard; import com.google.classyshark.silverghost.translator.java.dex.DexlibAdapter; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.Method; public class SyntheticAccessorsInspector { private final DexFile dxFile; public SyntheticAccessorsInspector(DexFile dxFile) { this.dxFile = dxFile; } public List getSyntheticAccessors() { LinkedList result = new LinkedList<>(); Set allClasses = dxFile.getClasses(); for (ClassDef classDef : allClasses) { Iterator allMethodsIter = classDef.getMethods().iterator(); while (allMethodsIter.hasNext()) { Method element = allMethodsIter.next(); String name = element.getName(); String nClassName = classDef.getType(); if (name.contains("access$")) { String cleanClassName = DexlibAdapter.getClassStringFromDex(nClassName); if (!result.contains(cleanClassName)) { result.add(cleanClassName); } } } } return result; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/Table.java ================================================ /** * Copyright (C) 2014 ned.twigg@diffplug.com * Copyright (C) 2011 K Venkata Sudhakar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; /** A couple of static methods for creating tables. */ public class Table { /** Returns a formatted table string. */ public static String getTable(Collection objects, List> columns) { String[][] data = new String[objects.size()][]; Iterator iter = objects.iterator(); int i = 0; while (i < objects.size()) { T object = iter.next(); data[i] = new String[columns.size()]; for (int j = 0; j < columns.size(); ++j) { data[i][j] = columns.get(j).getter.apply(object); } ++i; } Column[] rawColumns = columns.stream() .map(new Function, Object>() { @Override public Object apply(Column.Data c) { return c.column; } }) .collect(Collectors.toList()) .toArray(new Column[columns.size()]); return getTable(rawColumns, data); } /** Returns a formatted table string. */ public static String getTable(String[] header, String[][] data) { Column[] headerCol = Arrays.asList(header).stream() .map(new Function() { @Override public Column apply(String s) { return new Column(s); } }) .collect(Collectors.toList()) .toArray(new Column[header.length]); return getTable(headerCol, data); } /** Returns a formatted table string. */ public static String getTable(Column[] headerObjs, String[][] data) { if (data == null || data.length == 0) { throw new IllegalArgumentException("Please provide valid data : " + data); } /** * Table String buffer */ StringBuilder tableBuf = new StringBuilder(); /** * Get maximum number of columns across all rows */ String[] header = getHeaders(headerObjs); int colCount = getMaxColumns(header, data); /** * Get max length of data in each column */ List colMaxLenList = getMaxColLengths(colCount, header, data); /** * Check for the existence of header */ if (header != null && header.length > 0) { /** * 1. Row line */ tableBuf.append(getRowLineBuf(colCount, colMaxLenList, data)); /** * 2. Header line */ tableBuf.append(getRowDataBuf(colCount, colMaxLenList, header, headerObjs, true)); } /** * 3. Data Row lines */ tableBuf.append(getRowLineBuf(colCount, colMaxLenList, data)); String[] rowData = null; //Build row data buffer by iterating through all rows for (int i = 0 ; i < data.length ; i++) { //Build cell data in each row rowData = new String [colCount]; for (int j = 0 ; j < colCount ; j++) { if (j < data[i].length) { rowData[j] = data[i][j]; } else { rowData[j] = ""; } } tableBuf.append(getRowDataBuf(colCount, colMaxLenList, rowData, headerObjs, false)); } /** * 4. Row line */ tableBuf.append(getRowLineBuf(colCount, colMaxLenList, data)); return tableBuf.toString(); } private static String getRowDataBuf(int colCount, List colMaxLenList, String[] row, Column[] headerObjs, boolean isHeader) { StringBuilder rowBuilder = new StringBuilder(); String formattedData = null; Column.Align align; for (int i = 0 ; i < colCount ; i ++) { align = isHeader ? Column.Align.HEADER_DEFAULT : Column.Align.DATA_DEFAULT; if (headerObjs != null && i < headerObjs.length) { if (isHeader) { align = headerObjs[i].headerAlign; } else { align = headerObjs[i].dataAlign; } } formattedData = i < row.length ? row[i] : ""; //format = "| %" + colFormat.get(i) + "s "; formattedData = "| " + getFormattedData(colMaxLenList.get(i), formattedData, align) + " "; if (i+1 == colCount) { formattedData += "|"; } rowBuilder.append(formattedData); } return rowBuilder.append("\n").toString(); } private static String getFormattedData(int maxLength, String data, Column.Align align) { if (data.length() > maxLength) { return data; } boolean toggle = true; while (data.length() < maxLength) { if (align == Column.Align.LEFT) { data = data + " "; } else if (align == Column.Align.RIGHT) { data = " " + data; } else if (align == Column.Align.CENTER) { if (toggle) { data = " " + data; toggle = false; } else { data = data + " "; toggle = true; } } } return data; } /** * Each string item rendering requires the border and a space on both sides. * * 12 3 12 3 12 34 * +----- +-------- +------+ * abc venkat last * * @param colCount * @param colMaxLenList * @param data * @return */ private static String getRowLineBuf(int colCount, List colMaxLenList, String[][] data) { StringBuilder rowBuilder = new StringBuilder(); int colWidth = 0 ; for (int i = 0 ; i < colCount ; i ++) { colWidth = colMaxLenList.get(i) + 3; for (int j = 0; j < colWidth ; j ++) { if (j==0) { rowBuilder.append("+"); } else if ((i+1 == colCount && j+1 == colWidth)) {//for last column close the border rowBuilder.append("-+"); } else { rowBuilder.append("-"); } } } return rowBuilder.append("\n").toString(); } private static int getMaxItemLength(List colData) { int maxLength = 0; for (int i = 0 ; i < colData.size() ; i ++) { maxLength = Math.max(colData.get(i).length(), maxLength); } return maxLength; } private static int getMaxColumns(String[] header, String[][] data) { int maxColumns = 0; for (int i = 0; i < data.length; i++) { maxColumns = Math.max(data[i].length, maxColumns); } maxColumns = Math.max(header.length, maxColumns); return maxColumns; } private static List getMaxColLengths(int colCount, String[] header, String[][] data) { List colMaxLenList = new ArrayList(colCount); List colData = null; int maxLength; for (int i = 0 ; i < colCount ; i ++) { colData = new ArrayList(); if (header != null && i < header.length) { colData.add(header[i]); } for (int j = 0 ; j < data.length; j ++) { if (i < data[j].length) { colData.add(data[j][i]); } else { colData.add(""); } } maxLength = getMaxItemLength(colData); colMaxLenList.add(maxLength); } return colMaxLenList; } private static String[] getHeaders(Column[] headerObjs) { String[] header = new String[0]; if (headerObjs != null && headerObjs.length > 0) { header = new String[headerObjs.length]; for (int i = 0 ; i < headerObjs.length ; i ++) { header[i] = headerObjs[i].header; } } return header; } /** Represents a column's title and alignment. */ public static class Column { /** Represents a horizontal alignment. */ public enum Align { LEFT, CENTER, RIGHT; public static final Align HEADER_DEFAULT = LEFT; public static final Align DATA_DEFAULT = LEFT; } public final String header; public final Align headerAlign; public final Align dataAlign; /** A Column with a name. */ public Column(String headerName) { this(headerName, Align.HEADER_DEFAULT, Align.DATA_DEFAULT); } /** A Column with a name and alignment. */ public Column(String header, Align headerAlign, Align dataAlign) { this.header = header; this.headerAlign = headerAlign; this.dataAlign = dataAlign; } /** An object which can extract data with which to populate its column. */ public Data with(Function getter) { return new Data(this, getter); } /** Represents a Data-driven column. */ public static class Data { public final Column column; public final Function getter; private Data(Column column, Function getter) { this.column = column; this.getter = getter; } } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/manifest/AndroidManifestPlainTextReader.java ================================================ /* * Copyright 2017 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard.manifest; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; public class AndroidManifestPlainTextReader { private Document doc; public AndroidManifestPlainTextReader(File xmlFile) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder; try { builder = factory.newDocumentBuilder(); doc = builder.parse(xmlFile); } catch (ParserConfigurationException | SAXException | IOException e) { e.printStackTrace(); } } public AndroidManifestPlainTextReader(String xmlString) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder; try { builder = factory.newDocumentBuilder(); InputStream stream = new ByteArrayInputStream(xmlString.getBytes(StandardCharsets.UTF_8)); doc = builder.parse(stream); } catch (ParserConfigurationException | SAXException | IOException e) { e.printStackTrace(); } } public Map getActionsWithReceivers() { Map list = new TreeMap<>(); try { XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); XPathExpression expr = xpath.compile("/manifest/application/receiver/intent-filter/action"); NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); for (int i = 0; i < nodes.getLength(); i++) { Node currentNode = nodes.item(i); Node actionNameAttribute = currentNode.getAttributes().getNamedItem("name"); String receiverName = currentNode.getParentNode().getParentNode().getAttributes().getNamedItem("name").getTextContent(); list.put(actionNameAttribute.getTextContent(), receiverName); } } catch (XPathExpressionException e) { e.printStackTrace(); } return list; } public List getServices() { List list = new ArrayList<>(); try { XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); XPathExpression expr = xpath.compile("/manifest/application/service"); NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); for (int i = 0; i < nodes.getLength(); i++) { Node serviceNode = nodes.item(i); Node actionName = serviceNode.getAttributes().getNamedItem("name"); list.add(actionName.getTextContent()); } } catch (XPathExpressionException e) { e.printStackTrace(); } return list; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/manifest/ManifestInspector.java ================================================ /* * Copyright 2017 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard.manifest; import com.google.classyshark.silverghost.SilverGhostFacade; import java.io.File; import java.util.LinkedList; import java.util.List; import java.util.Map; public class ManifestInspector { private final File apkFile; public ManifestInspector(File apkFile) { this.apkFile = apkFile; } public List getInspections() { String manifestStr = SilverGhostFacade.getManifest(apkFile); AndroidManifestPlainTextReader amptr = new AndroidManifestPlainTextReader(manifestStr); List result = new LinkedList<>(); // receivers with system actions Map actions = amptr.getActionsWithReceivers(); ReceiverActionsBL rabl = new ReceiverActionsBL(actions); result.addAll(rabl.getBGActionsList()); return result; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/apk/dashboard/manifest/ReceiverActionsBL.java ================================================ /* * Copyright 2017 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.apk.dashboard.manifest; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; public class ReceiverActionsBL { // https://developer.android.com/preview/features/background-broadcasts.html static List approvedActions = Arrays.asList( "android.intent.action.LOCKED_BOOT_COMPLETED", "android.intent.action.BOOT_COMPLETED", "android.intent.action.USER_INITIALIZE", "android.intent.action.USER_ADDED", "android.intent.action.USER_REMOVED", "android.intent.action.TIMEZONE_CHANGED", "android.intent.action.TIME_SET", "android.intent.action.LOCALE_CHANGED", "android.hardware.usb.action.USB_ACCESSORY_ATTACHED", "android.hardware.usb.action.USB_ACCESSORY_DETACHED", "android.hardware.usb.action.USB_DEVICE_ATTACHED", "android.hardware.usb.action.USB_DEVICE_DETACHED", "android.accounts.LOGIN_ACCOUNTS_CHANGED", "android.intent.action.DEVICE_STORAGE_LOW", "android.intent.action.DEVICE_STORAGE_OK", "android.intent.action.PACKAGE_DATA_CLEARED", "android.intent.action.PACKAGE_FULLY_REMOVED", "android.intent.action.NEW_OUTGOING_CALL", "android.intent.action.HEADSET_PLUG", "android.intent.action.EVENT_REMINDER", "android.hardware.usb.action.USB_ACCESSORY_ATTACHED", "android.hardware.usb.action.USB_ACCESSORY_DETACHED", "android.hardware.usb.action.USB_DEVICE_ATTACHED", "android.hardware.usb.action.USB_DEVICE_DETACHED", "android.app.action.DEVICE_OWNER_CHANGED", "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED", "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" ); private final Map bgActionsToReceivers; public ReceiverActionsBL(Map actionsToReceivers) { this.bgActionsToReceivers = filterBGActions(actionsToReceivers); } public List getBGActionsList() { List result = new LinkedList<>(); for (Map.Entry entry : bgActionsToReceivers.entrySet()) { result.add(entry.getKey() + " ==> " + entry.getValue()); } return result; } private Map filterBGActions(Map actions) { TreeMap result = new TreeMap<>(); for (Map.Entry entry : actions.entrySet()) { if (!approvedActions.contains(entry.getKey())) { if (entry.getKey().startsWith("com.google.") || entry.getKey().startsWith("android.")) { result.put(entry.getKey(), entry.getValue()); } } } return result; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/dex/DexInfoTranslator.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.dex; import com.google.classyshark.silverghost.TokensMapper; import com.google.classyshark.silverghost.contentreader.dex.DexlibLoader; import com.google.classyshark.silverghost.translator.Translator; import com.google.classyshark.silverghost.translator.jar.JarInfoTranslator; import java.io.File; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.iface.DexFile; import static com.google.classyshark.silverghost.translator.apk.dashboard.ApkDashboard.getClassesWithNativeMethodsPerDexIndex; import static com.google.classyshark.silverghost.translator.java.dex.MultidexReader.extractClassesDex; /** * Translator for the classes.dex entry */ public class DexInfoTranslator implements Translator { private File apkFile; private String dexFileName; private int index; private List elements = new ArrayList<>(); public DexInfoTranslator(String dexFileName, File apkFile) { this.apkFile = apkFile; this.dexFileName = dexFileName; } @Override public String getClassName() { return dexFileName; } @Override public void addMapper(TokensMapper reverseMappings) { } @Override public void apply() { try { elements.clear(); File classesDex = extractClassesDex(dexFileName, apkFile, this); DexFile dxFile = DexlibLoader.loadDexFile(classesDex); DexBackedDexFile dataPack = (DexBackedDexFile) dxFile; ELEMENT element = new ELEMENT("\nclasses: " + dataPack.getClassCount(), TAG.MODIFIER); elements.add(element); element = new ELEMENT("\nstrings: " + dataPack.getStringCount(), TAG.DOCUMENT); elements.add(element); element = new ELEMENT("\ntypes: " + dataPack.getTypeCount(), TAG.DOCUMENT); elements.add(element); element = new ELEMENT("\nprotos: " + dataPack.getProtoCount(), TAG.DOCUMENT); elements.add(element); element = new ELEMENT("\nfields: " + dataPack.getFieldCount(), TAG.DOCUMENT); elements.add(element); element = new ELEMENT("\nmethods: " + dataPack.getMethodCount(), TAG.IDENTIFIER); elements.add(element); element = new ELEMENT("\n\nFile size: " + JarInfoTranslator.readableFileSize(classesDex.length()), TAG.DOCUMENT); elements.add(element); element = new ELEMENT("\n\nClasses with Native Calls\n", TAG.MODIFIER); elements.add(element); Set classesWithNativeMethods = getClassesWithNativeMethodsPerDexIndex(index, classesDex); for (String classWithNativeMethods : classesWithNativeMethods) { element = new ELEMENT(classWithNativeMethods + "\n", TAG.DOCUMENT); elements.add(element); } } catch (Exception e) { e.printStackTrace(); } } @Override public List getElementsList() { return elements; } @Override public List getDependencies() { return new LinkedList<>(); } public void setIndex(int index) { this.index = index; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/dex/DexMethodsDumper.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.dex; import com.google.classyshark.silverghost.translator.java.dex.DexlibAdapter; import org.objectweb.asm.Type; import org.ow2.asmdex.ApplicationReader; import org.ow2.asmdex.ApplicationVisitor; import org.ow2.asmdex.ClassVisitor; import org.ow2.asmdex.MethodVisitor; import org.ow2.asmdex.Opcodes; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * */ public class DexMethodsDumper { public static List dumpMethods(File archiveFile) { ArrayList result = new ArrayList<>(); try { ZipInputStream zipFile = new ZipInputStream(new FileInputStream( archiveFile)); ZipEntry zipEntry; while (true) { zipEntry = zipFile.getNextEntry(); if (zipEntry == null) { break; } if (zipEntry.getName().endsWith(".dex")) { int dexIndex = Character.getNumericValue(zipEntry.getName().charAt(zipEntry.getName().length() - 5)); String fName = "DUMPER_METHODS_classes"; // classes.dex <=> classes2.dex valid names if(dexIndex != 28) { fName += dexIndex; } else { dexIndex = 0; } File file = File.createTempFile(fName, "dex"); file.deleteOnExit(); FileOutputStream fos = new FileOutputStream(file); byte[] bytes = new byte[1024]; int length; while ((length = zipFile.read(bytes)) >= 0) { fos.write(bytes, 0, length); } fos.close(); List methodsList = fillAnalysis(dexIndex, file); result.addAll(methodsList); } else { } } zipFile.close(); } catch (Exception e) { e.printStackTrace(); } return result; } private static List fillAnalysis(int dexIndex, File file) throws IOException { ArrayList result = new ArrayList(); InputStream is = new FileInputStream(file); ApplicationVisitor av = new ApkInspectVisitor(result); ApplicationReader ar = new ApplicationReader(Opcodes.ASM4, is); ar.accept(av, 0); return result; } private static class ApkInspectVisitor extends ApplicationVisitor { private List methodsList; public ApkInspectVisitor(List methodsList) { super(Opcodes.ASM4); this.methodsList = methodsList; } static String getDecName(String dexType) { if (dexType.startsWith("[")) { return getDecName(dexType.substring(1)) + "[]"; } if (dexType.startsWith("L")) { String name = dexType.substring(1, dexType.length() - 1); return name.replace('/', '.'); } if(DexlibAdapter.primitiveTypes.containsKey(dexType)) { return DexlibAdapter.primitiveTypes.get(dexType); } else { return "void"; } } static String popType(String desc) { return desc.substring(nextTypePosition(desc, 0)); } static String popReturn(String desc) { return desc.substring(0, desc.indexOf(popType(desc))); } static int nextTypePosition(String desc, int pos) { while (desc.charAt(pos) == '[') pos++; if (desc.charAt(pos) == 'L') pos = desc.indexOf(';', pos); pos++; return pos; } public ClassVisitor visitClass(int access, String name, String[] signature, String superName, String[] interfaces) { return new ClassVisitor(Opcodes.ASM4) { @Override public void visit(int version, int access, String name, String[] signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String[] signature, String[] exceptions) { // class format (XYZ)R // dex format RXYZ StringBuilder builder = new StringBuilder(); builder.append(Modifier.toString(access)); builder.append(" " + ApkInspectVisitor.getDecName(popReturn(desc))); builder.append(" " + name); // using java class convert + types from ASM Type[] parameterTypes = Type.getArgumentTypes("(" + popType(desc) + ")"); builder.append("("); String prefix = ""; for (Type pType : parameterTypes) { builder.append(prefix); prefix = ","; builder.append(ApkInspectVisitor.getDecName(pType.toString())); } builder.append(")"); methodsList.add(builder.toString()); return super.visitMethod(access, name, desc, signature, exceptions); } }; } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/dex/DexStringsDumper.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.dex; import com.google.classyshark.silverghost.contentreader.dex.DexlibLoader; import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.iface.DexFile; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class DexStringsDumper { public static List dumpStrings(File apkFile) { File file; ZipInputStream zipFile; List allStrings = new ArrayList<>(); try { zipFile = new ZipInputStream(new FileInputStream(apkFile)); ZipEntry zipEntry; int i = 0; while (true) { zipEntry = zipFile.getNextEntry(); if (zipEntry == null) { break; } if (zipEntry.getName().endsWith(".dex")) { file = File.createTempFile("classes" + i, "dex"); file.deleteOnExit(); i++; FileOutputStream fos = new FileOutputStream(file); byte[] bytes = new byte[1024]; int length; while ((length = zipFile.read(bytes)) >= 0) { fos.write(bytes, 0, length); } fos.close(); DexFile dxFile = DexlibLoader.loadDexFile(file); DexBackedDexFile dataPack = (DexBackedDexFile) dxFile; int stringCount = dataPack.getStringCount(); allStrings.add(new String("classes" + i + ".dex\n")); for (int strIndex = 0; strIndex < stringCount; strIndex++) { allStrings.add(dataPack.getString(strIndex) + "\n"); } file.delete(); } } zipFile.close(); } catch (Exception e) { e.printStackTrace(); } return allStrings; } public static void main(String[] args) throws Exception { String apkFile = System.getProperty("user.home") + "/Desktop/Scenarios/4 APKs/com.google.samples.apps.iosched-333.apk"; List allStrings = dumpStrings(new File(apkFile)); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/elf/ElfReader.java ================================================ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.elf; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * A poor man's implementation of the readelf command. This program is * designed to parse ELF (Executable and Linkable Format) files. */ public class ElfReader { /** * The magic values for the ELF identification. */ private static final byte[] ELF_IDENT = { (byte) 0x7F, (byte) 'E', (byte) 'L', (byte) 'F', }; /** * Size of the e_ident[] structure in the ELF header. */ private static final int EI_NIDENT = 16; /** * Offset from end of ident structure in half-word sizes. */ private static final int OFFSET_TYPE = 0; /** * Machine type. */ private static final int OFFSET_MACHINE = 1; /** * ELF version. */ private static final int OFFSET_VERSION = 2; /** * The offset to which the system transfers control. e.g., the first thing * executed. */ private static final int OFFSET_ENTRY = 4; /** * Program header offset in bytes. */ private static final int OFFSET_PHOFF = 6; /** * Segment header offset in bytes. */ private static final int OFFSET_SHOFF = 8; /** * Processor-specific flags for binary. */ private static final int OFFSET_FLAGS = 10; /** * ELF header size in bytes. */ private static final int OFFSET_EHSIZE = 12; /** * All program headers entry size in bytes. */ private static final int OFFSET_PHENTSIZE = 13; /** * Number of program headers in ELF. */ private static final int OFFSET_PHNUM = 14; /** * All segment headers entry size in bytes. */ private static final int OFFSET_SHENTSIZE = 15; /** * Number of segment headers in ELF. */ private static final int OFFSET_SHNUM = 16; /** * The section header index that refers to string table. */ private static final int OFFSET_SHSTRNDX = 17; /** * Program header offset for type of this program header. */ private static final int PHOFF_TYPE = 0; /** * Program header offset for absolute offset in file. */ private static final int PHOFF_OFFSET = 2; /** * Program header offset for virtual address. */ private static final int PHOFF_VADDR = 4; /** * Program header offset for physical address. */ private static final int PHOFF_PADDR = 6; /** * Program header offset for file size in bytes. */ private static final int PHOFF_FILESZ = 8; /** * Program header offset for memory size in bytes. */ private static final int PHOFF_MEMSZ = 10; /** * Program header offset for flags. */ private static final int PHOFF_FLAGS = 12; /** * Program header offset for required alignment. 0 or 1 means no alignment * necessary. */ private static final int PHOFF_ALIGN = 14; /** * Index into string pool for segment name. */ private static final long SHOFF_NAME = 0; /** * Segment header offset for type (half-words) */ private static final long SHOFF_TYPE = 2; /** * Segment header offset for offset (meta!) (half-words) */ private static final long SHOFF_OFFSET = 8; /** * Segment header offset for size (half-words) */ private static final long SHOFF_SIZE = 10; /** * Data is presented in LSB format. */ private static final int ELFDATA2LSB = 1; /** * Date is presented in MSB format. */ private static final int ELFDATA2MSB = 2; private static final int ELFCLASS32 = 1; private static final int ELFCLASS64 = 2; private static final long PT_LOAD = 1; /** * Section Type: Symbol Table */ private static final int SHT_SYMTAB = 2; /** * Section Type: String Table */ private static final int SHT_STRTAB = 3; /** * Section Type: Dynamic **/ private static final int SHT_DYNAMIC = 6; /** * Section Type: Dynamic Symbol Table */ private static final int SHT_DYNSYM = 11; /** * Symbol Table Entry: Name offset */ private static final int SYMTAB_NAME = 0; /** * Symbol Table Entry: SymTab Info */ private static final int SYMTAB_ST_INFO = 6; /** * Symbol Table Entry size (half-words) */ private static final int SYMTAB_ENTRY_HALFWORD_SIZE = 7; /** * Symbol Table Entry size (extra in bytes) to cover "st_info" and * "st_other" */ private static final int SYMTAB_ENTRY_BYTE_EXTRA_SIZE = 2; public static class Symbol { public static final int STB_LOCAL = 0; public static final int STB_GLOBAL = 1; public static final int STB_WEAK = 2; public static final int STB_LOPROC = 13; public static final int STB_HIPROC = 15; public final String name; public final int bind; public final int type; Symbol(String name, int st_info) { this.name = name; this.bind = (st_info >> 4) & 0x0F; this.type = st_info & 0x0F; } } ; private final RandomAccessFile mFile; private final byte[] mBuffer = new byte[512]; private int mClass; private int mEndian; private boolean mIsDynamic; private boolean mIsPIE; private int mType; private int mWordSize; private int mHalfWordSize; /** * Symbol Table offset */ private long mSymTabOffset; /** * Symbol Table size */ private long mSymTabSize; /** * Dynamic Symbol Table offset */ private long mDynSymOffset; /** * Dynamic Symbol Table size */ private long mDynSymSize; /** * Section Header String Table offset */ private long mShStrTabOffset; /** * Section Header String Table size */ private long mShStrTabSize; /** * String Table offset */ private long mStrTabOffset; /** * String Table size */ private long mStrTabSize; /** * Dynamic String Table offset */ private long mDynStrOffset; /** * Dynamic String Table size */ private long mDynStrSize; /** * Symbol Table symbol names */ public Map mSymbols; /** * Dynamic Symbol Table symbol names */ private Map mDynamicSymbols; static ElfReader read(File file) throws IOException { return new ElfReader(file); } boolean isDynamic() { return mIsDynamic; } int getType() { return mType; } boolean isPIE() { return mIsPIE; } private ElfReader(File file) throws IOException { mFile = new RandomAccessFile(file, "r"); readIdent(); readHeader(); } protected void finalize() throws Throwable { try { mFile.close(); } catch (IOException e) { // nothing } finally { super.finalize(); } } private void readHeader() throws IOException { mType = readHalf(getHeaderOffset(OFFSET_TYPE)); final long shOffset = readWord(getHeaderOffset(OFFSET_SHOFF)); final int shNumber = readHalf(getHeaderOffset(OFFSET_SHNUM)); final int shSize = readHalf(getHeaderOffset(OFFSET_SHENTSIZE)); final int shStrIndex = readHalf(getHeaderOffset(OFFSET_SHSTRNDX)); readSectionHeaders(shOffset, shNumber, shSize, shStrIndex); final long phOffset = readWord(getHeaderOffset(OFFSET_PHOFF)); final int phNumber = readHalf(getHeaderOffset(OFFSET_PHNUM)); final int phSize = readHalf(getHeaderOffset(OFFSET_PHENTSIZE)); readProgramHeaders(phOffset, phNumber, phSize); } private void readSectionHeaders(long tableOffset, int shNumber, int shSize, int shStrIndex) throws IOException { // Read the Section Header String Table offset first. { final long shStrTabShOffset = tableOffset + shStrIndex * shSize; final long type = readWord(shStrTabShOffset + mHalfWordSize * SHOFF_TYPE); if (type == SHT_STRTAB) { mShStrTabOffset = readWord(shStrTabShOffset + mHalfWordSize * SHOFF_OFFSET); mShStrTabSize = readWord(shStrTabShOffset + mHalfWordSize * SHOFF_SIZE); } } for (int i = 0; i < shNumber; i++) { // Don't bother to re-read the Section Header StrTab. if (i == shStrIndex) { continue; } final long shOffset = tableOffset + i * shSize; final long type = readWord(shOffset + mHalfWordSize * SHOFF_TYPE); if ((type == SHT_SYMTAB) || (type == SHT_DYNSYM)) { final long nameOffset = readWord(shOffset + mHalfWordSize * SHOFF_NAME); final long offset = readWord(shOffset + mHalfWordSize * SHOFF_OFFSET); final long size = readWord(shOffset + mHalfWordSize * SHOFF_SIZE); final String symTabName = readShStrTabEntry(nameOffset); if (".symtab".equals(symTabName)) { mSymTabOffset = offset; mSymTabSize = size; } else if (".dynsym".equals(symTabName)) { mDynSymOffset = offset; mDynSymSize = size; } } else if (type == SHT_STRTAB) { final long nameOffset = readWord(shOffset + mHalfWordSize * SHOFF_NAME); final long offset = readWord(shOffset + mHalfWordSize * SHOFF_OFFSET); final long size = readWord(shOffset + mHalfWordSize * SHOFF_SIZE); final String strTabName = readShStrTabEntry(nameOffset); if (".strtab".equals(strTabName)) { mStrTabOffset = offset; mStrTabSize = size; } else if (".dynstr".equals(strTabName)) { mDynStrOffset = offset; mDynStrSize = size; } } else if (type == SHT_DYNAMIC) { mIsDynamic = true; } } } private void readProgramHeaders(long phOffset, int phNumber, int phSize) throws IOException { for (int i = 0; i < phNumber; i++) { final long baseOffset = phOffset + i * phSize; final long type = readWord(baseOffset); if (type == PT_LOAD) { final long virtAddress = readWord(baseOffset + mHalfWordSize * PHOFF_VADDR); if (virtAddress == 0) { mIsPIE = true; } } } } private void readSymbolTable(Map symbolMap, long symStrOffset, long symStrSize, long symOffset, long symSize) throws IOException { final long symEnd = symOffset + symSize; for (long off = symOffset; off < symEnd; off += SYMTAB_ENTRY_HALFWORD_SIZE * mHalfWordSize + SYMTAB_ENTRY_BYTE_EXTRA_SIZE) { long strOffset = readWord(off + SYMTAB_NAME); if (strOffset == 0) { continue; } final String symName = readStrTabEntry(symStrOffset, symStrSize, strOffset); if (symName != null) { final int st_info = readByte(off + SYMTAB_ST_INFO); symbolMap.put(symName, new Symbol(symName, st_info)); } } } private String readShStrTabEntry(long strOffset) throws IOException { if ((mShStrTabOffset == 0) || (strOffset < 0) || (strOffset >= mShStrTabSize)) { return null; } return readString(mShStrTabOffset + strOffset); } private String readStrTabEntry(long tableOffset, long tableSize, long strOffset) throws IOException { if ((tableOffset == 0) || (strOffset < 0) || (strOffset >= tableSize)) { return null; } return readString(tableOffset + strOffset); } private int getHeaderOffset(int halfWorldOffset) { return EI_NIDENT + halfWorldOffset * mHalfWordSize; } private int readByte(long offset) throws IOException { mFile.seek(offset); mFile.readFully(mBuffer, 0, 1); return mBuffer[0]; } private int readHalf(long offset) throws IOException { mFile.seek(offset); mFile.readFully(mBuffer, 0, mWordSize); final int answer; if (mEndian == ELFDATA2LSB) { answer = mBuffer[1] << 8 | mBuffer[0]; } else { answer = mBuffer[0] << 8 | mBuffer[1]; } return answer; } private long readWord(long offset) throws IOException { mFile.seek(offset); mFile.readFully(mBuffer, 0, mWordSize); int answer = 0; if (mEndian == ELFDATA2LSB) { for (int i = mWordSize - 1; i >= 0; i--) { answer = (answer << 8) | (mBuffer[i] & 0xFF); } } else { final int N = mWordSize - 1; for (int i = 0; i <= N; i++) { answer = (answer << 8) | mBuffer[i]; } } return answer; } private String readString(long offset) throws IOException { mFile.seek(offset); mFile.readFully(mBuffer, 0, (int) Math.min(mBuffer.length, mFile.length() - offset)); for (int i = 0; i < mBuffer.length; i++) { if (mBuffer[i] == 0) { return new String(mBuffer, 0, i); } } return null; } private void readIdent() throws IOException { mFile.seek(0); mFile.readFully(mBuffer, 0, EI_NIDENT); if ((mBuffer[0] != ELF_IDENT[0]) || (mBuffer[1] != ELF_IDENT[1]) || (mBuffer[2] != ELF_IDENT[2]) || (mBuffer[3] != ELF_IDENT[3])) { throw new IllegalArgumentException("Invalid ELF file"); } mClass = mBuffer[4]; if (mClass == ELFCLASS32) { mWordSize = 4; mHalfWordSize = 2; } else { throw new IOException("Invalid executable type " + mClass + ": not ELFCLASS32!"); } mEndian = mBuffer[5]; } public Symbol getSymbol(String name) { if ((mSymTabOffset == 0) && (mSymTabSize == 0)) { return null; } if (mSymbols == null) { mSymbols = new HashMap(); try { readSymbolTable(mSymbols, mStrTabOffset, mStrTabSize, mSymTabOffset, mSymTabSize); } catch (IOException e) { return null; } } return mSymbols.get(name); } public Symbol getDynamicSymbol(String name) { if ((mDynSymOffset == 0) && (mDynSymSize == 0)) { return null; } if (mDynamicSymbols == null) { mDynamicSymbols = new HashMap(); try { readSymbolTable(mDynamicSymbols, mDynStrOffset, mDynStrSize, mDynSymOffset, mDynSymSize); } catch (IOException e) { return null; } } return mDynamicSymbols.get(name); } public List getDynamicSymbols() { getDynamicSymbol(""); Set set = mDynamicSymbols.keySet(); ArrayList result = new ArrayList<>(set); Collections.sort(result); return result; } public static void main(String[] args) throws Exception { String soFile = System.getProperty("user.home") + "/Desktop/Scenarios/5 Sos/libsqlcipher_android.so"; File resource = new File(soFile); ElfReader elf = ElfReader.read(resource); for (String dynVal : elf.getDynamicSymbols()) { System.out.println(dynVal); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/elf/ElfTranslator.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.elf; import com.google.classyshark.silverghost.io.SherlockHash; import com.google.classyshark.silverghost.TokensMapper; import com.google.classyshark.silverghost.translator.Translator; import nl.lxtreme.binutils.elf.Elf; import java.io.File; import java.io.FileInputStream; import java.util.LinkedList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import static com.google.classyshark.silverghost.translator.jar.JarInfoTranslator.readableFileSize; /** * translator for the elf binary format */ public class ElfTranslator implements Translator { private File archiveFile; private File resource; private String elfName; private String dependencies; private StringBuilder dynamicSymbols; public ElfTranslator(String className, File archiveFile) { this.archiveFile = archiveFile; this.elfName = className; dynamicSymbols = new StringBuilder(); } @Override public String getClassName() { return archiveFile.getName(); } @Override public void addMapper(TokensMapper reverseMappings) { } @Override public void apply() { dependencies = ""; resource = extractElf(elfName, archiveFile); try { Elf dependenciesReader = new Elf(resource); List libraryDependencies = dependenciesReader.getSharedDependencies(); for (String dependency : libraryDependencies) { dependencies += " " + dependency + "\n"; } ElfReader dynamicSymbolsReader = ElfReader.read(resource); for (String dynVal : dynamicSymbolsReader.getDynamicSymbols()) { dynamicSymbols.append(" -- " + dynVal + "\n"); } } catch (Exception e) { } } @Override public List getElementsList() { LinkedList result = new LinkedList<>(); result.add(new ELEMENT("File size - ", TAG.DOCUMENT)); result.add(new ELEMENT(readableFileSize(resource.length()), TAG.DOCUMENT)); result.add(new ELEMENT("\n\nNative Dependencies\n\n", TAG.IDENTIFIER)); result.add(new ELEMENT(this.dependencies, TAG.DOCUMENT)); result.add(new ELEMENT("\n\n\n\nDynamic Symbols\n\n", TAG.IDENTIFIER)); result.add(new ELEMENT(this.dynamicSymbols.toString(), TAG.DOCUMENT)); return result; } @Override public List getDependencies() { return new LinkedList<>(); } // TODO currently support only dexes, here is how to do for jar // TODO https://github.com/adamheinrich/native-utils/blob/master/NativeUtils.java public static File extractElf(String elfName, File apkFile) { File file = new File("classes.dex"); ZipInputStream zipFile; try { zipFile = new ZipInputStream(new FileInputStream(apkFile)); ZipEntry zipEntry; while (true) { zipEntry = zipFile.getNextEntry(); if (zipEntry == null) { break; } if (zipEntry.getName().equals(elfName)) { String fName = elfName; String ext = "so"; file = SherlockHash.INSTANCE.getFileFromZipStream(apkFile, zipFile, fName, ext); break; } } zipFile.close(); } catch (Exception e) { e.printStackTrace(); } return file; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/jar/JarInfoTranslator.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.jar; import com.google.classyshark.silverghost.TokensMapper; import com.google.classyshark.silverghost.translator.Translator; import java.io.File; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * Translator for the jar files entry */ public class JarInfoTranslator implements Translator { private final File jarArchive; private final List allClassNames; private List elements = new ArrayList<>(); public JarInfoTranslator(File jarArchive, List allClassNames) { this.jarArchive = jarArchive; this.allClassNames = allClassNames; } @Override public String getClassName() { return jarArchive.getName(); } @Override public void addMapper(TokensMapper reverseMappings) { } @Override public void apply() { ELEMENT element = new ELEMENT("\nclasses: " + allClassNames.size(), TAG.ANNOTATION); elements.add(element); element = new ELEMENT("\nsize: " + readableFileSize(jarArchive.length()), TAG.ANNOTATION); elements.add(element); } @Override public List getElementsList() { return elements; } @Override public List getDependencies() { return new LinkedList<>(); } public static String readableFileSize(long size) { if(size <= 0) return "0"; final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" }; int digitGroups = (int) (Math.log10(size)/Math.log10(1024)); return new DecimalFormat("#,##0.#").format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups]; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/JavaTranslator.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java; import com.google.classyshark.silverghost.TokensMapper; import com.google.classyshark.silverghost.translator.Translator; import com.google.classyshark.silverghost.translator.TranslatorFactory; import com.google.classyshark.silverghost.translator.java.clazz.QualifiedTypesMap; import com.google.classyshark.silverghost.translator.java.clazz.reflect.MetaObjectClass; import java.io.File; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Is a function : (class name, archive file) --> class source, as list of tokens with tag */ public class JavaTranslator implements Translator { private MetaObject metaObject; private List sourceCode; private QualifiedTypesMap namesMapper; /** * used for testing * * @param clazz */ public JavaTranslator(Class clazz) { this.metaObject = new MetaObjectClass(clazz); sourceCode = new ArrayList<>(); namesMapper = new QualifiedTypesMap(); } public JavaTranslator(String className, File archiveFile) { this.metaObject = MetaObjectFactory.buildMetaObject(className, archiveFile); sourceCode = new ArrayList<>(); namesMapper = new QualifiedTypesMap(); } @Override public String getClassName() { return metaObject.getName(); } @Override public void addMapper(TokensMapper reverseMappings) { this.metaObject = new MetaObjectWithMapper(this.metaObject, reverseMappings); } @Override public void apply() { MetaObject.ConstructorInfo constructors[]; MetaObject.MethodInfo methods[]; MetaObject.FieldInfo fields[]; MetaObject.InterfaceInfo interfaces[]; String className, packageName; className = metaObject.getName(); if (className.lastIndexOf(".") != -1) { packageName = className.substring(0, className.lastIndexOf(".")); sourceCode.add(new ELEMENT("\npackage ", TAG.MODIFIER)); sourceCode.add(new ELEMENT(packageName, TAG.IDENTIFIER)); sourceCode.add(new ELEMENT(";\n", TAG.MODIFIER)); } interfaces = metaObject.getInterfaces(); fields = metaObject.getDeclaredFields(); constructors = metaObject.getDeclaredConstructors(); methods = metaObject.getDeclaredMethods(); fillTypes(interfaces, constructors, methods, fields); namesMapper.removeType(className); fillSource(interfaces, constructors, methods, fields, metaObject); } @Override public String toString() { if (sourceCode == null) { return ""; } else { StringBuilder sb = new StringBuilder(); for (ELEMENT word : sourceCode) { sb.append(word.text); } return sb.toString(); } } @Override public List getElementsList() { return Collections.unmodifiableList(this.sourceCode); } @Override public List getDependencies() { return new ArrayList<>(namesMapper.getFullTypes()); } private void fillTypes(MetaObject.InterfaceInfo[] interfaces, MetaObject.ConstructorInfo[] constructors, MetaObject.MethodInfo[] methods, MetaObject.FieldInfo[] fields) { for (MetaObject.InterfaceInfo iface : interfaces) { namesMapper.addType(iface.interfaceStr); } for (MetaObject.FieldInfo field : fields) { namesMapper.addType(field.typeName); } for (MetaObject.ConstructorInfo constructor : constructors) { MetaObject.ParameterInfo parameterTypes[] = constructor.parameterTypes; if (parameterTypes.length > 0) { for (MetaObject.ParameterInfo parameterInfo : parameterTypes) { namesMapper.addType(parameterInfo.parameterStr); } } } for (MetaObject.MethodInfo method : methods) { namesMapper.addType(method.returnType); MetaObject.ParameterInfo parameters[] = method.parameterTypes; if (parameters.length > 0) { for (MetaObject.ParameterInfo parameter : parameters) { namesMapper.addType(parameter.parameterStr); } } MetaObject.ExceptionInfo[] exceptions = method.exceptionTypes; for (MetaObject.ExceptionInfo exception : exceptions) { namesMapper.addType(exception.exceptionStr); } } } private void fillSource(MetaObject.InterfaceInfo[] interfaces, MetaObject.ConstructorInfo[] constructors, MetaObject.MethodInfo[] methods, MetaObject.FieldInfo[] fields, MetaObject metaObject) { fillImports(namesMapper, sourceCode); fillClassDecl(interfaces, metaObject, sourceCode, namesMapper); fillFields(fields, sourceCode, namesMapper); fillCtors(constructors, metaObject, sourceCode, namesMapper); fillMethods(methods, sourceCode, namesMapper); } private static void fillImports(QualifiedTypesMap namesMapper, List words) { List imports = namesMapper.getFullTypes(); for (String importStr : imports) { words.add(new ELEMENT("\nimport ", TAG.MODIFIER)); words.add(new ELEMENT(importStr + ";", TAG.DOCUMENT)); } words.add(new ELEMENT("\n\n", TAG.IDENTIFIER)); } private static void fillClassDecl(MetaObject.InterfaceInfo[] interfaces, MetaObject metaObject, List words, QualifiedTypesMap namesMapper) { MetaObject.AnnotationInfo[] annotations = metaObject.getAnnotations(); for (MetaObject.AnnotationInfo annot : annotations) { words.add(new ELEMENT("@" + annot.annotationStr + " \n", TAG.ANNOTATION)); } int mod = metaObject.getModifiers(); words.add(new ELEMENT(Modifier.toString(mod), TAG.MODIFIER)); if (!Modifier.isInterface(mod)) { words.add(new ELEMENT(" class", TAG.MODIFIER)); } words.add(new ELEMENT(" " + namesMapper.getTypeNull(metaObject.getName()), TAG.IDENTIFIER)); words.add(new ELEMENT(metaObject.getClassGenerics(metaObject.getName()), TAG.IDENTIFIER)); if (metaObject.getSuperclass() != null) { words.add(new ELEMENT(" extends ", TAG.MODIFIER)); words.add(new ELEMENT(namesMapper.getType(metaObject.getSuperclass()), TAG.IDENTIFIER)); words.add(new ELEMENT(metaObject.getSuperclassGenerics(), TAG.IDENTIFIER)); } if (interfaces.length != 0) { words.add(new ELEMENT(" implements ", TAG.MODIFIER)); for (MetaObject.InterfaceInfo iface : interfaces) { words.add(new ELEMENT(namesMapper.getType(iface.interfaceStr), TAG.IDENTIFIER)); words.add(new ELEMENT(iface.genericsStr, TAG.IDENTIFIER)); words.add(new ELEMENT(", ", TAG.IDENTIFIER)); } words.remove(words.size() - 1); } words.add(new ELEMENT("\n{", TAG.IDENTIFIER)); } private static void fillFields(MetaObject.FieldInfo[] fields, List words, QualifiedTypesMap namesMapper) { MetaObject.AnnotationInfo[] annotations; words.add(new ELEMENT("\n" + " //======================== F I E L D S ==================\n\n", TAG.DOCUMENT)); List sortedFields = Arrays.asList(fields); Collections.sort(sortedFields); for (MetaObject.FieldInfo field : sortedFields) { int md = field.modifiers; annotations = field.annotations; for (MetaObject.AnnotationInfo annot : annotations) { words.add(new ELEMENT("\n @" + annot.annotationStr + " ", TAG.ANNOTATION)); } words.add(new ELEMENT("\n " + Modifier.toString(md) + " ", TAG.MODIFIER)); words.add(new ELEMENT(namesMapper.getTypeNull(field.typeName) + " ", TAG.MODIFIER)); words.add(new ELEMENT(field.name, TAG.IDENTIFIER)); words.add(new ELEMENT(field.genericStr, TAG.DOCUMENT)); words.add(new ELEMENT(";", TAG.DOCUMENT)); } } private static void fillCtors(MetaObject.ConstructorInfo[] constructors, MetaObject metaObject, List words, QualifiedTypesMap namesMapper) { words.add(new ELEMENT("\n\n" + " //======================== C O N S T R U C T O R S ======\n\n", TAG.DOCUMENT)); String x = namesMapper.getTypeNull(metaObject.getName()); for (MetaObject.ConstructorInfo constructor : constructors) { int md = constructor.modifiers; words.add(new ELEMENT(" " + Modifier.toString(md) + " ", TAG.MODIFIER)); words.add(new ELEMENT(x, TAG.IDENTIFIER)); MetaObject.ParameterInfo parameterTypes[] = constructor.parameterTypes; words.add(new ELEMENT("(", TAG.DOCUMENT)); if (parameterTypes.length > 0) { for (int j = 0; j < parameterTypes.length; j++) { words.add(new ELEMENT( namesMapper.getTypeNull(parameterTypes[j].parameterStr), TAG.DOCUMENT)); words.add(new ELEMENT(parameterTypes[j].genericStr, TAG.DOCUMENT)); if (j < (parameterTypes.length - 1)) { words.add(new ELEMENT(", ", TAG.DOCUMENT)); words.add(new ELEMENT("\n ", TAG.DOCUMENT)); } } } words.add(new ELEMENT(") { ... }\n", TAG.DOCUMENT)); } } private static void fillMethods(MetaObject.MethodInfo[] methods, List words, QualifiedTypesMap namesMapper) { MetaObject.AnnotationInfo[] annotations; words.add(new ELEMENT("\n" + " //======================== M E T H O D S ================\n\n", TAG.DOCUMENT)); List sortedMethods = Arrays.asList(methods); Collections.sort(sortedMethods); for (MetaObject.MethodInfo method : sortedMethods) { int md = method.modifiers; annotations = method.annotations; for (MetaObject.AnnotationInfo annot : annotations) { words.add(new ELEMENT(" @" + annot.annotationStr + " \n", TAG.ANNOTATION)); } words.add(new ELEMENT(" " + Modifier.toString(md) + " ", TAG.MODIFIER)); words.add(new ELEMENT(namesMapper.getTypeNull(method.returnType) + " ", TAG.DOCUMENT)); words.add(new ELEMENT(method.genericReturnType, TAG.DOCUMENT)); words.add(new ELEMENT(method.name, TAG.IDENTIFIER)); MetaObject.ParameterInfo parameterTypes[] = method.parameterTypes; words.add(new ELEMENT("(", TAG.DOCUMENT)); if (parameterTypes.length > 0) { for (int j = 0; j < parameterTypes.length; j++) { words.add(new ELEMENT( namesMapper.getType(parameterTypes[j].parameterStr), TAG.DOCUMENT)); words.add(new ELEMENT(parameterTypes[j].genericStr, TAG.DOCUMENT)); if (j < (parameterTypes.length - 1)) { words.add(new ELEMENT(", ", TAG.DOCUMENT)); words.add(new ELEMENT("\n ", TAG.DOCUMENT)); } } } words.add(new ELEMENT(") ", TAG.DOCUMENT)); MetaObject.ExceptionInfo[] exceptionTypes = method.exceptionTypes; if (exceptionTypes.length > 0) { words.add(new ELEMENT(" throws ", TAG.MODIFIER)); for (MetaObject.ExceptionInfo aXType : exceptionTypes) { words.add(new ELEMENT(namesMapper.getType(aXType.exceptionStr), TAG.IDENTIFIER)); words.add(new ELEMENT(", ", TAG.IDENTIFIER)); } words.remove(words.size() - 1); } words.add(new ELEMENT("{ ... }\n", TAG.DOCUMENT)); } words.add(new ELEMENT("\n} ", TAG.DOCUMENT)); } public static void testJar() { final File testFile = new File(System.getProperty("user.home") + "/Desktop/" + "ClassyShark.jar"); String textClass = "com.google.classyshark.gui.panel.reducer.Reducer.class"; Translator sourceGenerator = TranslatorFactory.createTranslator(textClass, testFile); sourceGenerator.apply(); System.out.println(sourceGenerator.toString()); } public static void testSystemClass() { Translator translator = new JavaTranslator(Enum.class); translator.apply(); System.out.print(translator); } public static void testCustomClass() { final File testFile = new File(System.getProperty("user.home") + "/Desktop/Scenarios/2 Class/Reducer.class"); String textClass = "com.google.classyshark.gui.panel.reducer.Reducer.class"; Translator translator = TranslatorFactory.createTranslator(textClass, testFile); translator.apply(); System.out.println(translator.toString()); } public static void testInnerClass() { final File testFile = new File(System.getProperty("user.home") + "/Desktop/Scenarios/2 Class/Reducer$1.class"); String textClass = "com.google.classyshark.gui.panel.reducer.Reducer$1.class"; Translator translator = TranslatorFactory.createTranslator(textClass, testFile); translator.apply(); System.out.println(translator.toString()); } public static void main(String[] args) throws Exception { testJar(); testSystemClass(); testCustomClass(); testInnerClass(); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/MetaObject.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java; /** * Meta object representation for class output */ public abstract class MetaObject { /** * data class for interfaces */ public static class InterfaceInfo { public String interfaceStr; public String genericsStr = ""; } /** * data class for fields */ public static class FieldInfo implements Comparable { public String typeName; public int modifiers; public AnnotationInfo[] annotations; public String name; public String genericStr = ""; @Override public int compareTo(Object o) { if(o instanceof FieldInfo) { FieldInfo other = (FieldInfo)o; return this.name.compareTo(other.name); } return 0; } } /** * data class for constructors */ public static class ConstructorInfo { public AnnotationInfo[] annotations; public ParameterInfo[] parameterTypes; public int modifiers; } /** * data class for methods */ public static class MethodInfo implements Comparable{ public AnnotationInfo[] annotations; public ParameterInfo[] parameterTypes; public int modifiers; public String name; public ExceptionInfo[] exceptionTypes; public String returnType; public String genericReturnType = ""; @Override public int compareTo(Object o) { if(o instanceof MethodInfo) { MethodInfo other = (MethodInfo)o; return this.name.compareTo(other.name); } return 0; } } /** * data class for annotations */ public static class AnnotationInfo { public String annotationStr; } /** * data class for parameters */ public static class ParameterInfo { public String parameterStr; public String genericStr = ""; } /** * data class for exceptions */ public static class ExceptionInfo { public String exceptionStr; } public abstract String getClassGenerics(String name); public abstract String getName(); public abstract AnnotationInfo[] getAnnotations(); public abstract int getModifiers(); public abstract String getSuperclass(); public abstract String getSuperclassGenerics(); public abstract InterfaceInfo[] getInterfaces(); public abstract FieldInfo[] getDeclaredFields(); public abstract ConstructorInfo[] getDeclaredConstructors(); public abstract MethodInfo[] getDeclaredMethods(); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/MetaObjectFactory.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java; import com.google.classyshark.silverghost.contentreader.dex.DexlibLoader; import com.google.classyshark.silverghost.translator.java.clazz.asm.MetaObjectAsmClass; import com.google.classyshark.silverghost.translator.java.clazz.reflect.ClassUtils; import com.google.classyshark.silverghost.translator.java.clazz.reflect.MetaObjectClass; import com.google.classyshark.silverghost.translator.java.dex.DexlibAdapter; import com.google.classyshark.silverghost.translator.java.dex.MetaObjectDex; import com.google.classyshark.silverghost.translator.java.dex.MultidexReader; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.DexFile; /** * Factory for creating meta-objects to represent Java's data */ public class MetaObjectFactory { private MetaObjectFactory() { } public static MetaObject buildMetaObject(String className, File archiveFile) { MetaObject result; if (archiveFile.getName().toLowerCase().endsWith(".jar")) { result = getMetaObjectFromJar(className, archiveFile); } else if (archiveFile.getName().toLowerCase().endsWith(".class")) { result = getMetaObjectFromClass(archiveFile); } else if (archiveFile.getName().toLowerCase().endsWith(".dex")) { result = getMetaObjectFromDex(className, archiveFile); } else if (archiveFile.getName().toLowerCase().endsWith(".apk")) { result = getMetaObjectFromApk(className, archiveFile); } else if (archiveFile.getName().toLowerCase().endsWith(".aar")) { result = getMetaObjectFromAar(className, archiveFile); } else { result = new MetaObjectClass(Exception.class); } return result; } private static MetaObject getMetaObjectFromAar(String className, File archiveFile) { try { File file = File.createTempFile("classes", "jar"); file.deleteOnExit(); OutputStream out = new FileOutputStream(file); FileInputStream fin = new FileInputStream(archiveFile); BufferedInputStream bin = new BufferedInputStream(fin); ZipInputStream zin = new ZipInputStream(bin); ZipEntry ze; while ((ze = zin.getNextEntry()) != null) { if (ze.getName().endsWith(".jar")) { byte[] buffer = new byte[8192]; int len; while ((len = zin.read(buffer)) != -1) { out.write(buffer, 0, len); } out.close(); MetaObject result = getMetaObjectFromJar(className, file); return result; } } } catch (Exception e) { } return new MetaObjectClass(Exception.class); } private static MetaObject getMetaObjectFromJar(String className, File archiveFile) { MetaObject result = null; Class clazz; try { clazz = ClassUtils.loadClassFromJar(archiveFile.getPath(), className); } catch (ClassNotFoundException e) { clazz = Exception.class; } catch (MalformedURLException e) { clazz = Exception.class; } catch (NoClassDefFoundError e) { // the fallback to ASM case result = new MetaObjectAsmClass(className, archiveFile); return result; } result = verifyLoadedClassAndBuildASMFallback(className, archiveFile, result, clazz); return result; } private static MetaObject verifyLoadedClassAndBuildASMFallback(String className, File archiveFile, MetaObject result, Class clazz) { try { if (clazz.getFields() != null) { result = new MetaObjectClass(clazz); } if (clazz.getMethods() != null) { result = new MetaObjectClass(clazz); } if (clazz.getConstructors() != null) { result = new MetaObjectClass(clazz); } if (clazz.getDeclaredMethods() != null) { result = new MetaObjectClass(clazz); } } catch (NoClassDefFoundError e) { result = new MetaObjectAsmClass(className, archiveFile); } return result; } private static MetaObject getMetaObjectFromApk(String className, File apk) { MetaObject result; try { File classesDexWithClass = MultidexReader.extractClassesDexWithClass(className, apk); result = getMetaObjectFromDex(className, classesDexWithClass); } catch (Exception e) { result = new MetaObjectClass(Exception.class); } return result; } private static MetaObject getMetaObjectFromDex(String className, File archiveFile) { MetaObject result; try { DexFile dexFile = DexlibLoader.loadDexFile(archiveFile); ClassDef classDef = DexlibAdapter.getClassDefByName(className, dexFile); result = new MetaObjectDex(classDef); } catch (Exception e) { result = new MetaObjectClass(Exception.class); } return result; } private static MetaObject getMetaObjectFromClass(File archiveFile) { MetaObject result = new MetaObjectAsmClass(archiveFile); return result; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/MetaObjectWithMapper.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java; import com.google.classyshark.silverghost.TokensMapper; import java.util.Map; import java.util.TreeMap; public class MetaObjectWithMapper extends MetaObject { private Map reverseMappingClasses; private MetaObject metaObject; public MetaObjectWithMapper(MetaObject metaObject, TokensMapper reverseMappings) { super(); this.metaObject = metaObject; this.reverseMappingClasses = reverseMappings.getReverseClasses(); } @Override public String getClassGenerics(String name) { return metaObject.getClassGenerics(name); } @Override public String getName() { // TODO not clear why is it null if(reverseMappingClasses == null) { reverseMappingClasses = new TreeMap<>(); } if (reverseMappingClasses.containsKey(metaObject.getName())) { return reverseMappingClasses.get(metaObject.getName()); } return metaObject.getName(); } @Override public AnnotationInfo[] getAnnotations() { return metaObject.getAnnotations(); } @Override public int getModifiers() { return metaObject.getModifiers(); } @Override public String getSuperclass() { return metaObject.getSuperclass(); } @Override public String getSuperclassGenerics() { return metaObject.getSuperclassGenerics(); } @Override public InterfaceInfo[] getInterfaces() { return metaObject.getInterfaces(); } @Override public FieldInfo[] getDeclaredFields() { return metaObject.getDeclaredFields(); } @Override public ConstructorInfo[] getDeclaredConstructors() { return metaObject.getDeclaredConstructors(); } @Override public MethodInfo[] getDeclaredMethods() { return metaObject.getDeclaredMethods(); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/StressTest.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java; import com.google.classyshark.silverghost.contentreader.ContentReader; import com.google.classyshark.silverghost.contentreader.dex.DexlibLoader; import com.google.classyshark.silverghost.contentreader.jar.JarReader; import com.google.classyshark.silverghost.translator.Translator; import com.google.classyshark.silverghost.translator.TranslatorFactory; import com.google.classyshark.silverghost.translator.java.dex.DexlibAdapter; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.DexFile; import java.io.File; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * Stress test for classes */ public class StressTest { public static void runAllClassesInJar(String jarCanonicalPath) throws Exception { List allStuff = JarReader.readClassNamesFromJar( new File(jarCanonicalPath), new LinkedList()); for (String currentClass : allStuff) { Translator sourceGenerator = TranslatorFactory.createTranslator(currentClass, new File(jarCanonicalPath)); sourceGenerator.apply(); System.out.println(sourceGenerator.toString()); } } public static void runAllClassesInDex(String jarCanonicalPath) throws Exception { DexFile dexFile = DexlibLoader.loadDexFile(new File(jarCanonicalPath)); Set allClassesInDex = dexFile.getClasses(); for (ClassDef currentClass : allClassesInDex) { String normType = DexlibAdapter.getClassStringFromDex(currentClass.getType()); Translator sourceGenerator = TranslatorFactory.createTranslator( normType, new File(jarCanonicalPath)); sourceGenerator.apply(); System.out.println(sourceGenerator.toString()); } } public static void main(String[] args) throws Exception { String allAndroid = System.getProperty("user.home") + "/Desktop/Scenarios/2 Samples/android.jar"; runAllClassesInJar(allAndroid); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/clazz/QualifiedTypesMap.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java.clazz; import com.google.classyshark.silverghost.translator.java.dex.DexlibAdapter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; /** * Data structure for handling fully qualified class dependencies */ public class QualifiedTypesMap { private HashMap full2types; public QualifiedTypesMap() { full2types = new HashMap<>(); } public List getFullTypes() { List result = new ArrayList(full2types.keySet()); Collections.sort(result); return result; } public void addType(String type) { if (type == null || type.isEmpty()) { return; } decodeAndStore(type, full2types); } public String getType(String type) { if (type == null || type.isEmpty()) { return ""; } return decodeAndStore(type, full2types); } public String getTypeNull(String type) { if (type == null || type.isEmpty()) { return ""; } return decodeAndStore(type, null); } public void removeType(String name) { full2types.remove(name); } public static String decodeAndStore(String typeName, HashMap hashMap) { String result; String arr; if (!isArray(typeName.charAt(0))) { int i = typeName.lastIndexOf("."); if (i == -1) { return typeName; } else { result = typeName.substring(i + 1); if (hashMap != null) { hashMap.put(typeName, result); } return result; } } arr = "[]"; if (isArray(typeName.charAt(1))) { result = decodeAndStore(typeName.substring(1), hashMap); } else { if (typeName.charAt(1) == 'L') { result = decodeAndStore(extractReference(typeName), hashMap); } else { result = DexlibAdapter.primitiveTypes.get( String.valueOf(typeName.charAt(1))); } } return result + arr; } private static boolean isArray(char typeName) { return typeName == '['; } private static String extractReference(String param) { return param.substring(param.indexOf("L") + 1, param.indexOf(";")); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/clazz/asm/ClassBytesFromJarExtractor.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java.clazz.asm; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Extractor of class bytes from jar * based on * http://stackoverflow.com/questions/31482847/read-bytes-from-a-class-file-within-a-jar-file */ public class ClassBytesFromJarExtractor { public static byte[] getBytes(String fullClassName, String jar) throws IOException { // ... inputs check omitted ... try (JarFile jarFile = new JarFile(jar)) { Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().endsWith(".class")) { String qualiName = entry.getName().replaceAll("/", "\\."); if (qualiName.equalsIgnoreCase(fullClassName)) { try (InputStream inputStream = jarFile.getInputStream(entry)) { return getBytes(inputStream); } catch (IOException ioException) { System.out.println("Could not obtain class entry for " + entry.getName()); throw ioException; } } } } } throw new IOException("File not found"); } public static byte[] getBytes(InputStream is) throws IOException { try (ByteArrayOutputStream os = new ByteArrayOutputStream();) { byte[] buffer = new byte[0xFFFF]; for (int len; (len = is.read(buffer)) != -1; ) os.write(buffer, 0, len); os.flush(); return os.toByteArray(); } } private static char[] hexArray = "0123456789ABCDEF".toCharArray(); public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } public static void main(String[] args) { try { byte[] bytes = getBytes("jd.cli.AnalyzerPanel.class", System.getProperty("user.home") + "/Desktop/BytecodeViewer.jar"); System.out.println(bytesToHex(bytes)); } catch (IOException e) { e.printStackTrace(); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/clazz/asm/ClassDetailsFiller.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java.clazz.asm; import com.google.classyshark.silverghost.translator.java.MetaObject; import com.google.classyshark.silverghost.translator.java.dex.DexlibAdapter; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.io.File; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.List; import static org.objectweb.asm.Type.getArgumentTypes; import static org.objectweb.asm.Type.getReturnType; /** * ASM class visitor for scanning the class bytes */ public class ClassDetailsFiller extends ClassVisitor { private MetaObject.AnnotationInfo[] annotationInfo = new MetaObject.AnnotationInfo[0]; private String name = ""; private int modifiers = 0; private String superClass = ""; private String superclassGenerics = ""; private List interfaces = new ArrayList<>(); private List declaredFields = new ArrayList<>(); private List declaredConstructors = new ArrayList<>(); private List declaredMethods = new ArrayList<>(); public ClassDetailsFiller() { super(Opcodes.ASM5); } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.name = name.replaceAll("/", "\\."); this.superClass = superName.replaceAll("/", "\\."); this.modifiers = access; for (String iface : interfaces) { MetaObject.InterfaceInfo ii = new MetaObject.InterfaceInfo(); ii.interfaceStr = DexlibAdapter.getClassStringFromDex(iface); this.interfaces.add(ii); } } public void visitSource(String source, String debug) { } public void visitOuterClass(String owner, String name, String desc) { } public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return null; } public void visitAttribute(Attribute attr) { } public void visitInnerClass(String name, String outerName, String innerName, int access) { } public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { MetaObject.FieldInfo fi = new MetaObject.FieldInfo(); fi.typeName = DexlibAdapter.getTypeName(desc); fi.modifiers = access; fi.annotations = new MetaObject.AnnotationInfo[0]; fi.name = name.replaceAll("/", "\\."); declaredFields.add(fi); return null; } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (name.equals("")) { fillConstructor(access, desc); return null; } MetaObject.MethodInfo mi = new MetaObject.MethodInfo(); mi.modifiers = access; mi.annotations = new MetaObject.AnnotationInfo[0]; mi.parameterTypes = new MetaObject.ParameterInfo[0]; // TODO fill exceptions mi.exceptionTypes = new MetaObject.ExceptionInfo[0]; mi.name = name.replaceAll("/", "\\."); mi.returnType = DexlibAdapter.getTypeName(getReturnType(desc).toString()); org.objectweb.asm.Type[] arguments = getArgumentTypes(desc); mi.parameterTypes = new MetaObject.ParameterInfo[arguments.length]; int i = 0; for (org.objectweb.asm.Type t : arguments) { mi.parameterTypes[i] = new MetaObject.ParameterInfo(); mi.parameterTypes[i].parameterStr = DexlibAdapter.getTypeName(t.toString()); i++; } declaredMethods.add(mi); return null; } private void fillConstructor(int access, String desc) { MetaObject.ConstructorInfo ci = new MetaObject.ConstructorInfo(); ci.modifiers = access; ci.annotations = new MetaObject.AnnotationInfo[0]; ci.parameterTypes = new MetaObject.ParameterInfo[0]; org.objectweb.asm.Type[] arguments = getArgumentTypes(desc); ci.parameterTypes = new MetaObject.ParameterInfo[arguments.length]; int i = 0; for (org.objectweb.asm.Type t : arguments) { ci.parameterTypes[i] = new MetaObject.ParameterInfo(); ci.parameterTypes[i].parameterStr = DexlibAdapter.getTypeName(t.toString()); i++; } declaredConstructors.add(ci); } public void visitEnd() { } public String getClassGenerics(String name) { return ""; } public String getName() { return name; } public MetaObject.AnnotationInfo[] getAnnotationInfo() { return annotationInfo; } public int getModifiers() { return modifiers; } public String getSuperClass() { return superClass; } public String getSuperclassGenerics() { return superclassGenerics; } public MetaObject.InterfaceInfo[] getInterfaces() { MetaObject.InterfaceInfo[] array = new MetaObject.InterfaceInfo[interfaces.size()]; return interfaces.toArray(array); } public MetaObject.FieldInfo[] getDeclaredFields() { MetaObject.FieldInfo[] array = new MetaObject.FieldInfo[declaredFields.size()]; return declaredFields.toArray(array); } public MetaObject.ConstructorInfo[] getDeclaredConstructors() { MetaObject.ConstructorInfo[] array = new MetaObject.ConstructorInfo[declaredConstructors.size()]; return declaredConstructors.toArray(array); } public MetaObject.MethodInfo[] getDeclaredMethods() { MetaObject.MethodInfo[] array = new MetaObject.MethodInfo[declaredMethods.size()]; return declaredMethods.toArray(array); } public static void main(String[] args) throws Exception { final File testFile = new File(System.getProperty("user.home") + "/Desktop/Scenarios/3 Class/Reducer.class"); RandomAccessFile f = new RandomAccessFile(testFile, "r"); byte[] b = new byte[(int) f.length()]; f.read(b); ClassDetailsFiller cp = new ClassDetailsFiller(); ClassReader cr = new ClassReader(b); cr.accept(cp, 0); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/clazz/asm/MetaObjectAsmClass.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java.clazz.asm; import com.google.classyshark.silverghost.translator.java.MetaObject; import org.objectweb.asm.ClassReader; import java.io.File; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * Meta object for class format, based on ASM parsing */ public class MetaObjectAsmClass extends MetaObject { ClassDetailsFiller classDetailsFiller; public MetaObjectAsmClass(String className, File archiveFile) { String classFileName = className + ".class"; try { byte[] bytes = ClassBytesFromJarExtractor.getBytes(classFileName, archiveFile.getAbsolutePath()); classDetailsFiller = new ClassDetailsFiller(); ClassReader cr = new ClassReader(bytes); cr.accept(classDetailsFiller, 0); } catch (Exception e) { e.printStackTrace(); } } public MetaObjectAsmClass(File archiveFile) { try { Path path = Paths.get(archiveFile.getAbsolutePath()); byte[] bytes = Files.readAllBytes(path); classDetailsFiller = new ClassDetailsFiller(); ClassReader cr = new ClassReader(bytes); cr.accept(classDetailsFiller, 0); } catch (Exception e) { e.printStackTrace(); } } public MetaObjectAsmClass(Class clazz) throws Exception { super(); String className = clazz.getName(); String classAsPath = className.replace('.', '/') + ".class"; InputStream stream = clazz.getClassLoader().getResourceAsStream(classAsPath); classDetailsFiller = new ClassDetailsFiller(); ClassReader cr = new ClassReader(stream); cr.accept(classDetailsFiller, 0); } @Override public String getClassGenerics(String name) { return classDetailsFiller.getClassGenerics(name); } @Override public String getName() { return classDetailsFiller.getName(); } @Override public AnnotationInfo[] getAnnotations() { return classDetailsFiller.getAnnotationInfo(); } @Override public int getModifiers() { return classDetailsFiller.getModifiers(); } @Override public String getSuperclass() { return classDetailsFiller.getSuperClass(); } @Override public String getSuperclassGenerics() { return classDetailsFiller.getSuperclassGenerics(); } @Override public InterfaceInfo[] getInterfaces() { return classDetailsFiller.getInterfaces(); } @Override public FieldInfo[] getDeclaredFields() { return classDetailsFiller.getDeclaredFields(); } @Override public ConstructorInfo[] getDeclaredConstructors() { return classDetailsFiller.getDeclaredConstructors(); } @Override public MethodInfo[] getDeclaredMethods() { return classDetailsFiller.getDeclaredMethods(); } public static void testCustomClass() throws Exception { final File testFile = new File(System.getProperty("user.home") + "/Desktop/Scenarios/2 Samples/BytecodeViewer 2.9.8.jar"); String testClass = "jd.cli.AnalyzerPanel"; MetaObjectAsmClass moac = new MetaObjectAsmClass(testClass, testFile); MethodInfo[] methods = moac.getDeclaredMethods(); System.out.println(methods); } public static void main(String[] args) throws Exception { testCustomClass(); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/clazz/reflect/ClassUtils.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java.clazz.reflect; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; /** * Helper method for class loading */ public class ClassUtils { private ClassUtils() { } public static Class loadClassFromJar(String jarAbsolutePath, String className) throws MalformedURLException, ClassNotFoundException { Class result; URL[] classLoaderUrls = new URL[]{new File(jarAbsolutePath).toURI().toURL()}; URLClassLoader child = new URLClassLoader(classLoaderUrls); result = child.loadClass(className); return result; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/clazz/reflect/MetaObjectClass.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java.clazz.reflect; import com.google.classyshark.gui.panel.reducer.Reducer; import com.google.classyshark.silverghost.translator.java.MetaObject; import com.google.classyshark.silverghost.translator.java.clazz.QualifiedTypesMap; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.List; /** * Meta object for class format */ public class MetaObjectClass extends MetaObject { private Class clazz; public MetaObjectClass(Class clazz) { super(); this.clazz = clazz; } @Override public AnnotationInfo[] getAnnotations() { return convertAnnotations(clazz.getAnnotations()); } @Override public int getModifiers() { return clazz.getModifiers(); } @Override public String getSuperclass() { if (clazz.getSuperclass() == null) { return null; } return clazz.getSuperclass().getName(); } @Override public String getName() { return clazz.getName(); } @Override public String getClassGenerics(String name) { TypeVariable[] tv = clazz.getTypeParameters(); if (tv.length != 0) { String result = getClassGenericsString(tv); return result; } else { return ""; } } @Override public String getSuperclassGenerics() { if (clazz.getSuperclass() != null) { TypeVariable[] tv = clazz.getSuperclass().getTypeParameters(); if (tv.length != 0) { String result = getClassGenericsString(tv); return result; } else { return ""; } } return ""; } @Override public InterfaceInfo[] getInterfaces() { List result = new ArrayList<>(); for (Class iface : clazz.getInterfaces()) { InterfaceInfo ii = new InterfaceInfo(); ii.interfaceStr = iface.getName(); TypeVariable[] tv = iface.getTypeParameters(); if (tv.length != 0) { ii.genericsStr = getClassGenericsString(tv); } result.add(ii); } InterfaceInfo[] array = new InterfaceInfo[result.size()]; return result.toArray(array); } @Override public FieldInfo[] getDeclaredFields() { Field[] implFields = clazz.getDeclaredFields(); List result = new ArrayList<>(); for (Field field : implFields) { FieldInfo fi = new FieldInfo(); fi.typeName = field.getType().getName(); fi.modifiers = field.getModifiers(); fi.annotations = convertAnnotations(field.getAnnotations()); fi.name = field.getName(); Type type = field.getGenericType(); if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) type; fi.genericStr = getFieldGenericsString(pType.getActualTypeArguments()); } result.add(fi); } FieldInfo[] array = new FieldInfo[result.size()]; return result.toArray(array); } @Override public ConstructorInfo[] getDeclaredConstructors() { Constructor[] implConstructors = clazz.getDeclaredConstructors(); List result = new ArrayList<>(); for (Constructor constructor : implConstructors) { ConstructorInfo ci = new ConstructorInfo(); ci.parameterTypes = convertParameters(constructor.getParameterTypes(), constructor.getGenericParameterTypes()); ci.annotations = convertAnnotations(constructor.getAnnotations()); ci.modifiers = constructor.getModifiers(); result.add(ci); } ConstructorInfo[] array = new ConstructorInfo[result.size()]; return result.toArray(array); } @Override public MethodInfo[] getDeclaredMethods() { Method[] orMethods = clazz.getDeclaredMethods(); List result = new ArrayList<>(); for (Method method : orMethods) { MethodInfo mi = new MethodInfo(); mi.parameterTypes = convertParameters(method.getParameterTypes(), method.getGenericParameterTypes()); mi.annotations = convertAnnotations(method.getAnnotations()); mi.modifiers = method.getModifiers(); mi.name = method.getName(); mi.exceptionTypes = convertExceptions(method.getExceptionTypes()); mi.returnType = method.getReturnType().getName(); Type returnType = method.getGenericReturnType(); if (returnType instanceof ParameterizedType) { ParameterizedType type = (ParameterizedType) returnType; Type[] typeArguments = type.getActualTypeArguments(); mi.genericReturnType = getFieldGenericsString(typeArguments) + " "; } result.add(mi); } MethodInfo[] array = new MethodInfo[result.size()]; return result.toArray(array); } private String getClassGenericsString(TypeVariable[] tv) { String result = "<"; for (TypeVariable t : tv) { result += t.getName() + ", "; } result = result.substring(0, result.length() - 2) + ">"; return result; } private AnnotationInfo[] convertAnnotations(Annotation[] annotations) { List result = new ArrayList<>(); for (Annotation anot : annotations) { AnnotationInfo ai = new AnnotationInfo(); ai.annotationStr = anot.annotationType().getSimpleName(); result.add(ai); } AnnotationInfo[] array = new AnnotationInfo[result.size()]; return result.toArray(array); } private ParameterInfo[] convertParameters(Class[] parameterTypes, Type[] genericParameterTypes) { List result = new ArrayList<>(); for (int i = 0; i < parameterTypes.length; i++) { Class param = parameterTypes[i]; ParameterInfo pi = new ParameterInfo(); pi.parameterStr = param.getName(); if (genericParameterTypes != null && i < genericParameterTypes.length) { Type genericParameterType = genericParameterTypes[i]; if (genericParameterType instanceof ParameterizedType) { ParameterizedType aType = (ParameterizedType) genericParameterType; Type[] parameterArgTypes = aType.getActualTypeArguments(); pi.genericStr = getFieldGenericsString(parameterArgTypes); } } result.add(pi); } ParameterInfo[] array = new ParameterInfo[result.size()]; return result.toArray(array); } private String getFieldGenericsString(Type[] actualTypeArguments) { if (actualTypeArguments == null || actualTypeArguments.length == 0) { return ""; } String result = " <"; for (Type t : actualTypeArguments) { // TODO not sure in java generics spec // TODO are generic params evaluated with class params result += QualifiedTypesMap.decodeAndStore(t.toString(), null) + ", "; } result = result.substring(0, result.length() - 2) + ">"; return result; } private ExceptionInfo[] convertExceptions(Class[] exceptionTypes) { List result = new ArrayList<>(); for (Class param : exceptionTypes) { ExceptionInfo ei = new ExceptionInfo(); ei.exceptionStr = param.getName(); result.add(ei); } ExceptionInfo[] array = new ExceptionInfo[result.size()]; return result.toArray(array); } public static void main(String[] args) throws Exception { MetaObjectClass moc = new MetaObjectClass(Reducer.class); System.out.println(moc.getAnnotations()); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/dex/DexlibAdapter.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java.dex; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.DexFile; import java.util.HashMap; import java.util.Map; /** * Adapter for dexlib to fit the engine APIs */ public class DexlibAdapter { public static final Map primitiveTypes; static { primitiveTypes = new HashMap<>(); primitiveTypes.put("I", "int"); primitiveTypes.put("V", "void"); primitiveTypes.put("C", "char"); primitiveTypes.put("D", "double"); primitiveTypes.put("F", "float"); primitiveTypes.put("J", "long"); primitiveTypes.put("S", "short"); primitiveTypes.put("Z", "boolean"); primitiveTypes.put("B", "byte"); } public static String getTypeName(String dexlibType) { String result; if (dexlibType.length() == 1) { result = primitiveTypes.get(dexlibType); } else { result = getClassStringFromDex(dexlibType); } return result; } public static ClassDef getClassDefByName(String className, DexFile dexFile) throws Exception { ClassDef result = null; String dexName; for (ClassDef currentClassDef : dexFile.getClasses()) { dexName = currentClassDef.getType(); if (isMatchFromDex(className, dexName)) { result = currentClassDef; break; } } return result; } public static boolean isMatchFromDex(String className, String dexName) { String convertedDexName = getClassStringFromDex(dexName); return convertedDexName.equals(className); } public static String getClassStringFromDex(String dexName) { String convertedDexName = dexName.replaceAll("/", "."); if (convertedDexName.startsWith("[")) { return convertedDexName; } if (!dexName.startsWith("L") && !dexName.endsWith(";")) { return convertedDexName; } convertedDexName = convertedDexName.substring(1, convertedDexName.length() - 1); return convertedDexName; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/dex/MetaObjectDex.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java.dex; import com.google.classyshark.silverghost.translator.Translator; import com.google.classyshark.silverghost.translator.TranslatorFactory; import com.google.classyshark.silverghost.translator.java.MetaObject; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.jf.dexlib2.iface.Annotation; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.Field; import org.jf.dexlib2.iface.Method; import org.jf.dexlib2.iface.MethodParameter; /** * Dex implementation of MetaObject */ public class MetaObjectDex extends MetaObject { private ClassDef classDef; public MetaObjectDex(ClassDef classDef) { super(); this.classDef = classDef; if (this.classDef == null) { this.classDef = new EmptyClassDef(); } } @Override public String getName() { return DexlibAdapter.getClassStringFromDex(classDef.getType()); } @Override public InterfaceInfo[] getInterfaces() { List result = new ArrayList<>(); for (String iface : classDef.getInterfaces()) { InterfaceInfo ii = new InterfaceInfo(); ii.interfaceStr = DexlibAdapter.getClassStringFromDex(iface); result.add(ii); } InterfaceInfo[] array = new InterfaceInfo[result.size()]; return result.toArray(array); } @Override public FieldInfo[] getDeclaredFields() { List result = new ArrayList<>(); Iterable implFields = classDef.getFields(); for (Field field : implFields) { FieldInfo fi = new FieldInfo(); fi.typeName = DexlibAdapter.getTypeName(field.getType()); fi.modifiers = field.getAccessFlags(); fi.annotations = convertAnnotations(field.getAnnotations()); fi.name = field.getName(); result.add(fi); } FieldInfo[] array = new FieldInfo[result.size()]; return result.toArray(array); } @Override public ConstructorInfo[] getDeclaredConstructors() { Iterable implConstructors = classDef.getMethods(); List result = new ArrayList<>(); for (Method constructor : implConstructors) { if (isConstructor(constructor)) { ConstructorInfo ci = new ConstructorInfo(); ci.parameterTypes = convertParameters(constructor.getParameters()); ci.annotations = convertAnnotations(constructor.getAnnotations()); ci.modifiers = constructor.getAccessFlags(); result.add(ci); } } ConstructorInfo[] array = new ConstructorInfo[result.size()]; return result.toArray(array); } @Override public MethodInfo[] getDeclaredMethods() { Iterable implMethods = classDef.getMethods(); List result = new ArrayList<>(); for (Method method : implMethods) { if (!isConstructor(method)) { MethodInfo mi = new MethodInfo(); mi.parameterTypes = convertParameters(method.getParameters()); mi.annotations = convertAnnotations(method.getAnnotations()); mi.modifiers = method.getAccessFlags(); mi.name = method.getName(); mi.exceptionTypes = new ExceptionInfo[0]; mi.returnType = DexlibAdapter.getTypeName(method.getReturnType()); result.add(mi); } } MethodInfo[] array = new MethodInfo[result.size()]; return result.toArray(array); } @Override public AnnotationInfo[] getAnnotations() { return convertAnnotations(classDef.getAnnotations()); } private AnnotationInfo[] convertAnnotations(Set annotations) { List result = new ArrayList<>(); for (Annotation anot : annotations) { AnnotationInfo ai = new AnnotationInfo(); ai.annotationStr = DexlibAdapter.getTypeName(anot.getType()); result.add(ai); } AnnotationInfo[] array = new AnnotationInfo[result.size()]; return result.toArray(array); } private ParameterInfo[] convertParameters(List parameters) { List result = new ArrayList<>(); for (MethodParameter parameter : parameters) { ParameterInfo pi = new ParameterInfo(); pi.parameterStr = DexlibAdapter.getTypeName(parameter.getType()); result.add(pi); } ParameterInfo[] array = new ParameterInfo[result.size()]; return result.toArray(array); } @Override public String getClassGenerics(String name) { return ""; } @Override public int getModifiers() { return classDef.getAccessFlags(); } @Override public String getSuperclass() { return DexlibAdapter.getClassStringFromDex(classDef.getSuperclass()); } @Override public String getSuperclassGenerics() { return ""; } private static boolean isConstructor(Method constructor) { return constructor.getName().equals(""); } public static void main(String[] args) throws Exception { final File testFile = new File(System.getProperty("user.home") + "/Desktop/classes.dex"); String textClass = "com.google.common.collect.ImmutableSortedMap"; Translator translator = TranslatorFactory.createTranslator(textClass, testFile); translator.apply(); System.out.println(translator.toString()); } private static class EmptyClassDef implements ClassDef { @Override public String getType() { return ""; } @Override public int compareTo(CharSequence charSequence) { return 0; } @Override public int getAccessFlags() { return 0; } @Override public String getSuperclass() { return ""; } @Override public List getInterfaces() { return new LinkedList<>(); } @Override public String getSourceFile() { return ""; } @Override public Set getAnnotations() { return new LinkedHashSet<>(); } @Override public Iterable getStaticFields() { return new Iterable() { @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return false; } @Override public Field next() { return null; } @Override public void remove() { } }; } }; } @Override public Iterable getInstanceFields() { return new Iterable() { @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return false; } @Override public Field next() { return null; } @Override public void remove() { } }; } }; } @Override public Iterable getFields() { return new Iterable() { @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return false; } @Override public Field next() { return null; } @Override public void remove() { } }; } }; } @Override public Iterable getDirectMethods() { return new Iterable() { @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return false; } @Override public Method next() { return null; } @Override public void remove() { } }; } }; } @Override public Iterable getVirtualMethods() { return new Iterable() { @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return false; } @Override public Method next() { return null; } @Override public void remove() { } }; } }; } @Override public Iterable getMethods() { return new Iterable() { @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return false; } @Override public Method next() { return null; } @Override public void remove() { } }; } }; } @Override public int length() { return 0; } @Override public char charAt(int index) { return 0; } @Override public CharSequence subSequence(int start, int end) { return ""; } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/java/dex/MultidexReader.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.java.dex; import com.google.classyshark.silverghost.contentreader.ContentReader; import com.google.classyshark.silverghost.contentreader.dex.DexReader; import com.google.classyshark.silverghost.io.SherlockHash; import com.google.classyshark.silverghost.translator.apk.dashboard.ApkDashboard; import com.google.classyshark.silverghost.translator.apk.dashboard.DynamicSymbolsInspector; import com.google.classyshark.silverghost.translator.dex.DexInfoTranslator; import com.google.classyshark.silverghost.translator.elf.ElfTranslator; import java.io.File; import java.io.FileInputStream; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import nl.lxtreme.binutils.elf.Elf; import static com.google.classyshark.silverghost.translator.apk.dashboard.ApkDashboard.fillAnalysisPerClassesDexIndex; public class MultidexReader { private MultidexReader() { } public static void fillApkDashboard(File binaryArchiveFile, ApkDashboard to) { try { ZipInputStream zipFile = new ZipInputStream(new FileInputStream( binaryArchiveFile)); ZipEntry zipEntry; int customDexIndex = 0; while (true) { zipEntry = zipFile.getNextEntry(); if (zipEntry == null) { break; } if (zipEntry.getName().endsWith(".dex")) { customDexIndex++; String fName = customDexIndex + "ANALYZER_classes"; int dexIndex = Character.getNumericValue(zipEntry.getName().charAt(zipEntry.getName().length() - 5)); if (dexIndex != 28 /* classes.dex*/) { fName = fName + dexIndex; } else { dexIndex = 0; } String ext = "dex"; File file = SherlockHash.INSTANCE.getFileFromZipStream(binaryArchiveFile, zipFile, fName, ext); if(zipEntry.getName().equals("classes.dex") || (zipEntry.getName().startsWith("classes") && zipEntry.getName().endsWith(".dex"))) { to.classesDexEntries.add(fillAnalysisPerClassesDexIndex(dexIndex, file)); } else { to.customClassesDexEntries.add(fillAnalysisPerClassesDexIndex(999, file)); } } else if (zipEntry.getName().endsWith("jar") || zipEntry.getName().endsWith("zip")) { String fName = "inner_zip"; String ext = "zip"; File innerZip = SherlockHash.INSTANCE.getFileFromZipStream(binaryArchiveFile, zipFile, fName, ext); // so far we have a zip file ZipInputStream fromInnerZip = new ZipInputStream(new FileInputStream( innerZip)); ZipEntry innerZipEntry; while (true) { innerZipEntry = fromInnerZip.getNextEntry(); if (innerZipEntry == null) { fromInnerZip.close(); break; } if (innerZipEntry.getName().endsWith(".dex")) { fName = "inner_zip_classes"; ext = "dex"; File file = SherlockHash.INSTANCE.getFileFromZipStream(binaryArchiveFile, fromInnerZip, fName, ext); to.customClassesDexEntries.add(fillAnalysisPerClassesDexIndex(99, file)); } } } else if (zipEntry.getName().startsWith("lib")) { File nativeLib = ElfTranslator.extractElf(zipEntry.getName(), binaryArchiveFile); Elf dependenciesReader = new Elf(nativeLib); List libraryDependencies = dependenciesReader.getSharedDependencies(); for (String dependency : libraryDependencies) { to.nativeDependencies.add(dependency); } DynamicSymbolsInspector dsi = new DynamicSymbolsInspector(dependenciesReader); if (dsi.areErrors()) { to.nativeErrors.add(zipEntry.getName() + " " + dsi.getErrors()); } to.nativeLibs.add(zipEntry.getName() + "\n"); } } zipFile.close(); } catch (Exception e) { e.printStackTrace(); } readClassNamesFromMultidex(binaryArchiveFile, to.allClasses, new ArrayList()); } public static void readClassNamesFromMultidex(File binaryArchiveFile, List classNames, List components) { ZipInputStream zipInputStream; try { zipInputStream = new ZipInputStream(new FileInputStream( binaryArchiveFile)); ZipEntry zipEntry; while (true) { zipEntry = zipInputStream.getNextEntry(); if (zipEntry == null) { break; } if (zipEntry.getName().endsWith(".xml")) { classNames.add(zipEntry.getName()); } if (zipEntry.getName().endsWith(".dex")) { String fName = zipEntry.getName().substring(0, zipEntry.getName().lastIndexOf(".")); String ext = "dex"; File file = SherlockHash.INSTANCE.getFileFromZipStream(binaryArchiveFile, zipInputStream, fName, ext); List classesAtDex = DexReader.readClassNamesFromDex(file); classNames.add(fName + ".dex"); classNames.addAll(classesAtDex); } if (zipEntry.getName().startsWith("lib")) { components.add( new ContentReader.Component(zipEntry.getName(), ContentReader.ARCHIVE_COMPONENT.NATIVE_LIBRARY)); } // Dynamic dex loading, currently one one inner zip is supported if (zipEntry.getName().endsWith("jar") || zipEntry.getName().endsWith("zip")) { String fName = "inner_zip"; String ext = "zip"; File innerZip = SherlockHash.INSTANCE.getFileFromZipStream(binaryArchiveFile, zipInputStream, fName, ext); // so far we have a zip file ZipInputStream fromInnerZip = new ZipInputStream(new FileInputStream( innerZip)); ZipEntry innerZipEntry; while (true) { innerZipEntry = fromInnerZip.getNextEntry(); if (innerZipEntry == null) { break; } // currently only one is supported if (innerZipEntry.getName().endsWith(".dex")) { fName = "inner_zip_classes"; ext = "dex"; File tempDexFile = SherlockHash.INSTANCE.getFileFromZipStream(binaryArchiveFile, fromInnerZip, fName, ext); List classesAtDex = DexReader.readClassNamesFromDex(tempDexFile); String name = zipEntry.getName() + "###" + innerZipEntry.getName(); classNames.add(name); classNames.addAll(classesAtDex); } } } } zipInputStream.close(); } catch (Exception e) { e.printStackTrace(); } } public static File extractClassesDexWithClass(String className, File apkFile) { // TODO need to delete this file File file = new File("classes.dex"); ZipInputStream zipFile; try { zipFile = new ZipInputStream(new FileInputStream( apkFile)); ZipEntry zipEntry; while (true) { zipEntry = zipFile.getNextEntry(); if (zipEntry == null) { break; } if (zipEntry.getName().endsWith(".dex")) { String fName = zipEntry.getName().substring(0, zipEntry.getName().lastIndexOf(".")); String ext = "dex"; file = SherlockHash.INSTANCE.getFileFromZipStream(apkFile, zipFile, fName, ext); List classNamesInDex = DexReader.readClassNamesFromDex(file); if (classNamesInDex.contains(className)) { break; } } if (zipEntry.getName().endsWith("jar") || zipEntry.getName().endsWith("zip")) { String fName = "inner_zip"; String ext = "zip"; File innerZip = SherlockHash.INSTANCE.getFileFromZipStream(apkFile, zipFile, fName, ext); // so far we have a zip file ZipInputStream fromInnerZip = new ZipInputStream(new FileInputStream( innerZip)); ZipEntry innerZipEntry; while (true) { innerZipEntry = fromInnerZip.getNextEntry(); if (innerZipEntry == null) { fromInnerZip.close(); break; } if (innerZipEntry.getName().endsWith(".dex")) { fName = "inner_zip_classes"; ext = "dex"; file = SherlockHash.INSTANCE.getFileFromZipStream(apkFile, fromInnerZip, fName, ext); List classNamesInDex = DexReader.readClassNamesFromDex(file); if (classNamesInDex.contains(className)) { fromInnerZip.close(); zipFile.close(); return file; } } } } } zipFile.close(); } catch (Exception e) { e.printStackTrace(); } return file; } public static File extractClassesDex(String dexName, File apkFile, DexInfoTranslator diTranslator) { if (apkFile.getName().endsWith(".dex")) { return apkFile; } File file = new File("classes.dex"); ZipInputStream zipFile; try { zipFile = new ZipInputStream(new FileInputStream(apkFile)); ZipEntry zipEntry; while (true) { zipEntry = zipFile.getNextEntry(); if (zipEntry == null) { break; } if (zipEntry.getName().endsWith(".dex")) { String fName = zipEntry.getName().substring(0, zipEntry.getName().lastIndexOf(".")); String ext = "dex"; String currentClassesDexName = fName + ".dex"; file = SherlockHash.INSTANCE.getFileFromZipStream(apkFile, zipFile, fName, ext); if (dexName.equals(currentClassesDexName)) { if (dexName.equals("classes.dex")) { diTranslator.setIndex(0); } else { diTranslator.setIndex(Integer.parseInt(fName.substring(fName.length() - 1))); } break; } } if (zipEntry.getName().endsWith("jar") || zipEntry.getName().endsWith("zip")) { String fName = "inner_zip"; String ext = "zip"; File innerZip = SherlockHash.INSTANCE.getFileFromZipStream(apkFile, zipFile, fName, ext); // so far we have a zip file ZipInputStream fromInnerZip = new ZipInputStream(new FileInputStream( innerZip)); ZipEntry innerZipEntry; while (true) { innerZipEntry = fromInnerZip.getNextEntry(); if (innerZipEntry == null) { fromInnerZip.close(); break; } if (innerZipEntry.getName().endsWith(".dex")) { fName = "inner_zip_classes"; ext = "dex"; file = SherlockHash.INSTANCE.getFileFromZipStream(apkFile, fromInnerZip, fName, ext); if (dexName.startsWith(zipEntry.getName())) { diTranslator.setIndex(99); zipFile.close(); return file; } } } } } zipFile.close(); } catch (Exception e) { e.printStackTrace(); } return file; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/xml/AndroidXmlTranslator.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.xml; import com.google.classyshark.silverghost.TokensMapper; import com.google.classyshark.silverghost.translator.Translator; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Is a function : (apk file) --> ,manifest XML text, as list of tokens with tag * based om code posted on StackOverflow by Ribo: * http://stackoverflow.com/a/4761689/496992 *

* There is a bug that some manifests can't be shown, added a fallback case to display * all strings */ public class AndroidXmlTranslator implements Translator { private static final String DEFAULT_CLASS_NAME = "AndroidManifest.xml"; private final File archiveFile; private final String xmlName; private String xml; private XmlHighlighter xmlHighlighter = new XmlHighlighter(); private XmlDecompressor xmlDecompressor = new XmlDecompressor(); public AndroidXmlTranslator(String xmlName, File archiveFile) { this.xmlName = xmlName; this.archiveFile = archiveFile; } public AndroidXmlTranslator(File archiveFile) { this(DEFAULT_CLASS_NAME, archiveFile); } @Override public String getClassName() { return xmlName; } @Override public void addMapper(TokensMapper reverseMappings) { } @Override public void apply() { InputStream is = null; ZipFile zip = null; ByteArrayOutputStream bout = null; try { long size; if (archiveFile.getName().endsWith(".apk") || archiveFile.getName().endsWith(".zip") || archiveFile.getName().endsWith(".aar")) { zip = new ZipFile(archiveFile); ZipEntry mft = zip.getEntry(xmlName); size = mft.getSize(); is = zip.getInputStream(mft); } else { size = archiveFile.length(); is = new FileInputStream(archiveFile); } if (size > Integer.MAX_VALUE) { throw new IOException("File larger than " + Integer.MAX_VALUE + " bytes not supported"); } bout = new ByteArrayOutputStream((int) size); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = is.read(buffer)) > 0) { bout.write(buffer, 0, bytesRead); } if (archiveFile.getName().endsWith(".aar")) { this.xml = bout.toString(); } else { this.xml = xmlDecompressor.decompressXml(bout.toByteArray()); } } catch (Exception e) { System.err.println("Error reading AndroidManifext.xml " + e.getMessage()); e.printStackTrace(System.err); } finally { closeResource(is); closeResource(zip); closeResource(bout); } } private void closeResource(Closeable closeable) { if (closeable == null) { return; } try { closeable.close(); } catch (IOException ex) { System.err.println("Error closing resource: " + ex.getMessage()); ex.printStackTrace(System.err); } } @Override public List getElementsList() { return xmlHighlighter.getElements(this.xml); } @Override public List getDependencies() { // TODO fuzzy logic for permissions etc return new ArrayList<>(); } @Override public String toString() { return xml; } public static void main(String[] args) throws Exception { String archiveName = System.getProperty("user.home") + "/Desktop/Scenarios/ 2 Samples/app-debug.apk"; AndroidXmlTranslator t2ax = new AndroidXmlTranslator(new File(archiveName)); t2ax.apply(); System.out.print(t2ax.toString()); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/xml/XmlDecompressor.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.xml; import com.google.common.io.LittleEndianDataInputStream; import java.io.ByteArrayInputStream; import java.io.DataInput; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Decompresses an Android binary xml file. It is not the same as the original file, but should * contain the same data. * * This code is based on code posted on StackOverflow by Ribo: * http://stackoverflow.com/a/4761689/496992 * * It contains minor fixes to optionally support CDATA elements and namespaces. * * Improvements made using code android code as reference: * - https://android.googlesource.com/platform/frameworks/base/+/master/include/androidfw/ * ResourceTypes.h * - https://android.googlesource.com/platform/frameworks/base/+/master/libs/androidfw/ * ResourceTypes.cpp */ public class XmlDecompressor { //Identifiers for XML Chunk Types private static final int PACKED_XML_IDENTIFIER = 0x00080003; private static final int END_DOC_TAG = 0x0101; private static final int START_ELEMENT_TAG = 0x0102; private static final int END_ELEMENT_TAG = 0x0103; private static final int CDATA_TAG = 0x0104; private static final int ATTRS_MARKER = 0x00140014; private static final int RES_XML_RESOURCE_MAP_TYPE = 0x180; private static final int RES_XML_FIRST_CHUNK_TYPE = 0x100; private static final int RES_XML_STRING_TABLE = 0x0001; //Resource Types private static final int RES_TYPE_NULL = 0x00; private static final int RES_TYPE_REFERENCE = 0x01; private static final int RES_TYPE_ATTRIBUTE = 0x02; private static final int RES_TYPE_STRING = 0x03; private static final int RES_TYPE_FLOAT = 0x04; private static final int RES_TYPE_DIMENSION = 0x05; private static final int RES_TYPE_FRACTION = 0x06; private static final int RES_TYPE_DYNAMIC_REFERENCE = 0x07; private static final int RES_TYPE_INT_DEC = 0x10; private static final int RES_TYPE_INT_HEX = 0x11; private static final int RES_TYPE_INT_BOOLEAN = 0x12; //Complex Types private static final int COMPLEX_UNIT_SHIFT = 0; private static final int COMPLEX_UNIT_MASK = 0xf; private static final int COMPLEX_MANTISSA_SHIFT = 8; private static final int COMPLEX_MANTISSA_MASK = 0xffffff; private static final int COMPLEX_RADIX_SHIFT = 4; private static final int COMPLEX_RADIX_MASK = 0x3; private static final int COMPLEX_UNIT_FRACTION = 0; private static final int COMPLEX_UNIT_FRACTION_PARENT = 1; private static final float MANTISSA_MULT = 1.0f / (1 << COMPLEX_MANTISSA_SHIFT); private static final float RADIX_MULTS[] = { 1.0f * MANTISSA_MULT, 1.0f / (1 << 7)*MANTISSA_MULT, 1.0f / (1 << 15) * MANTISSA_MULT, 1.0f / (1 << 23)*MANTISSA_MULT }; //Resource Values private static final int RES_VALUE_TRUE = 0xffffffff; private static final int RES_VALUE_FALSE = 0x00000000; //Char array used to fill spaces. private static char[] SPACE_FILL = new char[160]; private static final int IDENT_SIZE = 2; private static final int ATTR_IDENT_SIZE = 4; private static final String ERROR_INVALID_MAGIC_NUMBER = "Invalid packed XML identifier. Expecting 0x%08X, found 0x%08X\n"; private static final String ERROR_INVALID_STRING_TABLE_ID = "Invalid String table identifier. Expecting 0x%08X, found 0x%08X\n"; private static final String ERROR_UNKNOWN_TAG = "Unknown Tag 0x%04X\n"; private static final String ERROR_ATTRIBUTE_MARKER = "Expecting %08X, Found %08X\n"; private boolean appendNamespaces = false; private boolean appendCData = true; static { Arrays.fill(SPACE_FILL, ' '); } public void setAppendNamespaces(boolean appendNamespaces) { this.appendNamespaces = appendNamespaces; } public void setAppendCData(boolean appendCData) { this.appendCData = appendCData; } public String decompressXml(byte[] bytes) throws IOException { try (ByteArrayInputStream bin = new ByteArrayInputStream(bytes)) { return decompressXml(bin); } } public String decompressXml(InputStream is) throws IOException { StringBuilder result = new StringBuilder("\n"); try(LittleEndianDataInputStream dis = new LittleEndianDataInputStream(is)) { //Getting and checking the marker for a valid XML file int fileMarker = dis.readInt(); if (fileMarker != PACKED_XML_IDENTIFIER) { throw new IOException( String.format(ERROR_INVALID_MAGIC_NUMBER, PACKED_XML_IDENTIFIER, fileMarker)); } dis.skipBytes(4); List packedStrings = parseStrings(dis); int ident = 0; int tag = dis.readShort(); do { int headerSize = dis.readShort(); int chunkSize = dis.readInt(); switch (tag) { case RES_XML_FIRST_CHUNK_TYPE: { dis.skipBytes(chunkSize - 8); break; } case RES_XML_RESOURCE_MAP_TYPE: { dis.skipBytes(chunkSize - 8); break; } case START_ELEMENT_TAG: { parseStartTag(result, dis, packedStrings, ident); ident++; break; } case END_ELEMENT_TAG: { ident--; parseEndTag(result, dis, packedStrings, ident); break; } case CDATA_TAG: { parseCDataTag(result, dis, packedStrings, ident); break; } default: System.err.println(String.format(ERROR_UNKNOWN_TAG, tag)); } tag = dis.readShort(); } while (tag != END_DOC_TAG); return result.toString(); } } private void parseCDataTag(StringBuilder sb, DataInput dis, List strings, int ident) throws IOException { //Skipping 3 unknowns integers: dis.skipBytes(8); int nameStringIndex = dis.readInt(); //Skipping 2 more unknown integers. dis.skipBytes(8); if (appendCData) { sb.append(SPACE_FILL, 0, ident * IDENT_SIZE); sb.append("\n"); } } private void parseEndTag(StringBuilder sb, DataInput dis, List strings, int ident) throws IOException { sb.append(SPACE_FILL, 0, ident * IDENT_SIZE); sb.append("= 0) { sb.append(strings.get(namespaceStringIndex)).append(":"); } int nameStringIndex = dis.readInt(); sb.append(strings.get(nameStringIndex)).append(">\n"); } private void parseStartTag(StringBuilder sb, DataInput dis, List strings, int ident) throws IOException { sb.append(SPACE_FILL, 0, ident * IDENT_SIZE); sb.append("<"); //Skipping 3 integers: // 1 - a flag?, like 38000000 // 2 - Line of where this tag appeared in the original source file // 3 - Unknown: always FFFFFFFF? dis.skipBytes(8); int namespaceStringIndex = dis.readInt(); if (appendNamespaces && namespaceStringIndex >= 0) { sb.append(strings.get(namespaceStringIndex)).append(":"); } int nameStringIndex = dis.readInt(); sb.append(strings.get(nameStringIndex)); parseAttributes(sb, dis, strings, ident); sb.append(">\n"); } private void parseAttributes(StringBuilder sb, DataInput dis, List strings, int ident) throws IOException { int marker = dis.readInt(); if (marker != ATTRS_MARKER) { System.err.printf(ERROR_ATTRIBUTE_MARKER, ATTRS_MARKER, marker); } int numAttributes = dis.readInt(); //skipping 1 unknown integer: always 00000000? dis.skipBytes(4); for (int i = 0; i < numAttributes; i++) { sb.append("\n").append(SPACE_FILL, 0, ident * IDENT_SIZE + ATTR_IDENT_SIZE); int attributeNamespaceIndex = dis.readInt(); int attributeNameIndex = dis.readInt(); int attributeValueIndex = dis.readInt(); dis.skipBytes(3); int attrValueType = dis.readByte(); int attributeResourceId = dis.readInt(); if (appendNamespaces && attributeNamespaceIndex >= 0) { sb.append(strings.get(attributeNamespaceIndex)).append(":"); } String attributeName = strings.get(attributeNameIndex); if (attributeName.isEmpty()) attributeName="unknown"; String attributeValue; switch (attrValueType) { case RES_TYPE_NULL: attributeValue = attributeResourceId == 0 ? "" : ""; break; case RES_TYPE_REFERENCE: attributeValue = String.format("@res/0x%08X", attributeResourceId); break; case RES_TYPE_ATTRIBUTE: attributeValue = String.format("@attr/0x%08X", attributeResourceId); break; case RES_TYPE_STRING: attributeValue = strings.get(attributeValueIndex); break; case RES_TYPE_FLOAT: attributeValue = Float.toString(Float.intBitsToFloat(attributeResourceId)); break; case RES_TYPE_DIMENSION: float value = resValue(attributeResourceId); String type = getDimensionType(attributeResourceId); attributeValue = value + type; break; case RES_TYPE_FRACTION: value = resValue(attributeResourceId); type = getFractionType(attributeResourceId); attributeValue = value + type; break; case RES_TYPE_DYNAMIC_REFERENCE: attributeValue = String.format("@dyn/0x%08X", attributeResourceId); break; case RES_TYPE_INT_DEC: attributeValue = Integer.toString(attributeResourceId); break; case RES_TYPE_INT_HEX: attributeValue = String.format("0x%08X", attributeResourceId); break; case RES_TYPE_INT_BOOLEAN: attributeValue = attributeResourceId == RES_VALUE_TRUE ? "true" : "false"; break; default: attributeValue = String.format("0x%08X", attributeResourceId); } sb.append(attributeName).append("=\"").append(attributeValue).append("\""); } } private List parseStrings(DataInput dis) throws IOException { int stringMarker = dis.readShort(); if (stringMarker != RES_XML_STRING_TABLE) { throw new IOException( String.format(ERROR_INVALID_MAGIC_NUMBER, PACKED_XML_IDENTIFIER, stringMarker)); } int headerSize = dis.readShort(); int chunkSize = dis.readInt(); int numStrings = dis.readInt(); int numStyles = dis.readInt(); int flags = dis.readInt(); int stringStart = dis.readInt(); int stylesStart = dis.readInt(); boolean isUtf8Encoded = (flags & 0x100) > 0 ; int glyphSize; String encoding; if (isUtf8Encoded) { glyphSize = 1; encoding = "UTF-8"; } else { glyphSize = 2; encoding = "UTF-16LE"; } return parseUsingByteBuffer(chunkSize, headerSize, numStrings, numStyles, isUtf8Encoded, glyphSize, encoding, dis); } private static List parseUsingByteBuffer(int chunkSize, int headerSize, int numStrings, int numStyles, boolean isUtf8Encoded, int glyphSize, String encoding, DataInput dis) throws IOException{ int dataSize = chunkSize - headerSize; byte[] buffer = new byte[dataSize]; ByteBuffer byteBuffer = ByteBuffer.allocate(dataSize); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); dis.readFully(buffer); byteBuffer.put(buffer); byteBuffer.rewind(); //Read the string offsets List packedStrings = new ArrayList<>(numStrings); int[] offsets = new int[numStrings]; for (int i = 0; i < numStrings; i++) { offsets[i] = byteBuffer.getInt(); } //Read the string from each offset int stringsStart = byteBuffer.position(); for (int i = 0; i < numStrings; i++) { byteBuffer.position(stringsStart + offsets[i]); int len; if (isUtf8Encoded) { len = getUnsignedByte(byteBuffer); byteBuffer.get(); } else { len = getUnsignedShort(byteBuffer); } int bytelen = len * glyphSize; String str = new String(buffer, stringsStart + offsets[i] + 2, bytelen, encoding); packedStrings.add(str); } return packedStrings; } public static int getUnsignedShort(ByteBuffer bb) { return (bb.getShort() & 0xffff); } public static int getUnsignedByte(ByteBuffer bb) { return (bb.get() & 0xff); } public static long getUnsignedInt(ByteBuffer bb) { return ((long)bb.getInt() & 0xffffffffL); } private static String getDimensionType(int data) { switch ((data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK) { case 0: return "px"; case 1: return "dp"; case 2: return "sp"; case 3: return "pt"; case 4: return "in"; case 5: return "mm"; default: return " (unknown unit)"; } } private static String getFractionType(int data) { switch ((data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK) { case COMPLEX_UNIT_FRACTION: return "%%"; case COMPLEX_UNIT_FRACTION_PARENT: return "%%p"; default: return "(unknown unit)"; } } private static float resValue(int data) { float value = (data&(COMPLEX_MANTISSA_MASK <>COMPLEX_RADIX_SHIFT) & COMPLEX_RADIX_MASK]; return value; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/silverghost/translator/xml/XmlHighlighter.java ================================================ /* * Copyright 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.silverghost.translator.xml; import com.google.classyshark.silverghost.translator.Translator; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Receives a String XML and highlights it. */ public class XmlHighlighter { public enum TagType { TAG, ATTR_NAME, ATTR_VALUE, COMMENT, CDATA } private static final Pattern TAG_PATTERN = Pattern.compile("(?", Pattern.MULTILINE); private static final Pattern TAG_PATTERN_2 = Pattern.compile("(<\\?)[a-z-]+\\s?>?", Pattern.MULTILINE); private static final Pattern TAG_END_PATTERN = Pattern.compile("[^\\]](\\??>)", Pattern.MULTILINE); private static final Pattern TAG_ATTRIBUTE_PATTERN = Pattern.compile("\\s(\\w*)\\=", Pattern.MULTILINE); private static final Pattern TAG_ATTRIBUTE_VALUE = Pattern.compile("[a-z-]*\\=(\"[^\"]*\")", Pattern.MULTILINE); private static final Pattern TAG_ATTRIBUTE_VALUE_2 = Pattern.compile("[a-z-]*\\=(\'[^\']*\')", Pattern.MULTILINE); private static final Pattern TAG_COMMENT = Pattern.compile("()", Pattern.MULTILINE); private static final Pattern TAG_CDATA_START = Pattern.compile("(\\)", Pattern.MULTILINE); private static final Map PATTERN_TAG_TYPE_MAP = new HashMap<>(); static { PATTERN_TAG_TYPE_MAP.put(TAG_PATTERN, TagType.TAG); PATTERN_TAG_TYPE_MAP.put(TAG_PATTERN_2, TagType.TAG); PATTERN_TAG_TYPE_MAP.put(TAG_END_PATTERN, TagType.TAG); PATTERN_TAG_TYPE_MAP.put(TAG_ATTRIBUTE_PATTERN, TagType.ATTR_NAME); PATTERN_TAG_TYPE_MAP.put(TAG_ATTRIBUTE_VALUE, TagType.ATTR_VALUE); PATTERN_TAG_TYPE_MAP.put(TAG_ATTRIBUTE_VALUE_2, TagType.ATTR_VALUE); PATTERN_TAG_TYPE_MAP.put(TAG_COMMENT, TagType.COMMENT); PATTERN_TAG_TYPE_MAP.put(TAG_CDATA_START, TagType.CDATA); PATTERN_TAG_TYPE_MAP.put(TAG_CDATA_END, TagType.CDATA); } public static class Element implements Comparable { private int start; private int end; private TagType tag; public Element(int start, int end, TagType tag) { this.start = start; this.end = end; this.tag = tag; } @Override public String toString() { return "Element{" + "start=" + start + ", end=" + end + ", tag='" + tag + '\'' + '}'; } @Override public int compareTo(Element o) { return Integer.compare(this.start, o.start); } } public List getElements(String xml) { List elementList = new ArrayList<>(); for (Map.Entry entry: PATTERN_TAG_TYPE_MAP.entrySet()) { Matcher m = entry.getKey().matcher(xml); while (m.find()) { elementList.add(new Element(m.start(1), m.end(1), entry.getValue())); } } Collections.sort(elementList); int xmlPos = 0; Element e; List elements = new ArrayList<>(); for (int i = 0; i < elementList.size(); i++) { e = elementList.get(i); if (xmlPos < e.start) { elements.add(new Translator.ELEMENT( xml.substring(xmlPos, e.start), Translator.TAG.XML_COMMENT)); } Translator.TAG tag; switch (e.tag) { case TAG: tag = Translator.TAG.XML_TAG; break; case ATTR_NAME: tag = Translator.TAG.XML_ATTR_NAME; break; case ATTR_VALUE: tag = Translator.TAG.XML_ATTR_VALUE; break; case CDATA: tag = Translator.TAG.XML_CDATA; break; case COMMENT: tag = Translator.TAG.XML_COMMENT; break; default: tag = Translator.TAG.XML_DEFAULT; } elements.add(new Translator.ELEMENT(xml.substring(e.start, e.end), tag)); xmlPos = e.end; if (i == elementList.size() - 1 && xmlPos < xml.length() - 1) { elements.add(new Translator.ELEMENT( xml.substring(e.end, xml.length()), Translator.TAG.XML_DEFAULT)); } } return elements; } public static void main(String[] args) { String xml = "\n" + "\n" + " \n" + " \n" + " \n" + " \n"+ " \n" + " \n" + " \n" + ""; System.out.println(xml); List elements = new XmlHighlighter().getElements(xml); for (Translator.ELEMENT e: elements) { System.out.println(e.text); } System.out.println(elements.size()); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/UpdateManager.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater; import com.google.classyshark.updater.networking.AbstractDownloader; import com.google.classyshark.updater.networking.CliDownloader; import com.google.classyshark.updater.networking.GuiDownloader; /** * This class is the core point for the update process: based on the response * received from GitHub, it will download the new release and, if ClassyShark * has been started as desktop app, when the download finishes, it will show a * dialog containing the changelog of the new version */ public class UpdateManager{ private static final UpdateManager instance = new UpdateManager(); private UpdateManager() { } public static UpdateManager getInstance() { return instance; } public void checkVersionConsole() { checkVersion(false); } public void checkVersionGui() { checkVersion(true); } private void checkVersion(boolean isGui) { getDownloaderFrom(isGui).checkNewVersion(); } private AbstractDownloader getDownloaderFrom(boolean isGui) { if (isGui) { return GuiDownloader.getInstance(); } else { return CliDownloader.getInstance(); } } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/models/Release.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.models; import com.google.classyshark.Version; import com.google.classyshark.updater.networking.GitHubApi; import com.google.gson.annotations.SerializedName; /** * This class represents the response that GitHub returns when queried with {@link GitHubApi#getLatestRelease()}. * Since we do not need all the fields from the response, we just add those we are gonna use. * At the moment, the {@link #preRelease} field is not used, but it will most probably be in the future in case we want to * only update to stable releases. */ public class Release { private final String name; @SerializedName("prerelease") private final boolean preRelease; private final String body; private final ReleaseDownloadData[] assets; @SerializedName("created_at") private final String createdAt; private Release(String name, boolean preRelease, String body, ReleaseDownloadData[] assets, String createdAt) { this.name = name; this.preRelease = preRelease; this.body = body; this.assets = assets; this.createdAt = createdAt; } public Release() { this(String.format("%d.%d", Version.MAJOR, Version.MINOR), false, "", null, ""); } @Override public boolean equals(Object other) { if (!(other instanceof Release)) { return false; } return name.equals(((Release) other).name); } private int getMajorVersion() { return getVersionField(0); } private int getVersionField(int field) { String cleanedReleaseName = name.split("\\s")[0]; return Integer.parseInt(cleanedReleaseName.split("\\.")[field]); } private int getMinorVersion() { return getVersionField(1); } @Override public int hashCode() { return name.hashCode(); } @Override public String toString() { return "REL:\t" + name + "\nCHANGELOG:\n" + body; } public boolean isNewerThan(Release other) { return getMajorVersion() > other.getMajorVersion() || getMajorVersion() == other.getMajorVersion() && getMinorVersion() > other.getMinorVersion(); } public String getDownloadURL() { if (assets != null && assets.length > 0) { return assets[0].getURL(); } else { return null; } } public String getReleaseName() { return name; } public boolean isPreRelease() { return preRelease; } public String getChangelog() { return body; } public String getCreatedAt() { return createdAt; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/models/ReleaseDownloadData.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.models; import com.google.gson.annotations.SerializedName; class ReleaseDownloadData { @SerializedName("browser_download_url") private final String browserDownloadUrl; ReleaseDownloadData(String browserDownloadUrl) { this.browserDownloadUrl = browserDownloadUrl; } @Override public boolean equals(Object other) { if (!(other instanceof ReleaseDownloadData)) { return false; } return browserDownloadUrl.equals(((ReleaseDownloadData) other).browserDownloadUrl); } @Override public int hashCode() { return browserDownloadUrl.hashCode(); } String getURL() { return browserDownloadUrl; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/networking/AbstractDownloader.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.networking; import com.google.classyshark.updater.models.Release; import com.google.classyshark.updater.utils.FileUtils; import retrofit2.Call; import java.io.File; import java.io.IOException; /** * This class is the skeleton of both the {@link GuiDownloader} and {@link CliDownloader}: it contains the core logic * needed to download the new JAR and a couple of callbacks that helps in managing the different outputs between the * two logics */ public abstract class AbstractDownloader extends AbstractReleaseCallback{ private final Release current = new Release(); public void checkNewVersion() { Call call = NetworkManager.getGitHubApi().getLatestRelease(); call.enqueue(this); } @Override public void onReleaseReceived(final Release release) { if (release.isNewerThan(current)) { new Thread(new Runnable() { @Override public void run() { if (warnAboutNew(release)) { obtainNew(release); } } }).start(); } } abstract boolean warnAboutNew(Release release); private void obtainNew(Release release) { try { onReleaseDownloaded(FileUtils.downloadFileFrom(release), release); } catch (IOException e) { System.err.println("ERROR: " + e.getMessage()); } } abstract void onReleaseDownloaded(File file, Release release); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/networking/AbstractReleaseCallback.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.networking; import com.google.classyshark.updater.models.Release; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public abstract class AbstractReleaseCallback implements Callback{ @Override public void onResponse(Call call, Response response) { onReleaseReceived(response.body()); } public abstract void onReleaseReceived(Release release); @Override public void onFailure(Call call, Throwable throwable) { System.err.println("ERROR: " + throwable.getMessage()); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/networking/CliDownloader.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.networking; import com.google.classyshark.updater.models.Release; import java.io.File; import java.util.Scanner; /** * This class is the one taking care of downloading the update from the CLI, asking the user if they want to update * to the new version when one is found. */ public class CliDownloader extends AbstractDownloader{ private static final AbstractDownloader instance = new CliDownloader(); private CliDownloader() {} @Override boolean warnAboutNew(Release release) { String message = "New ClassyShark version available!\n" + release.getReleaseName() + "\n" + release.getChangelog() + "\n" + "Do you wish to download it? (y/N)"; System.out.println(message); Scanner scanner = new Scanner(System.in); return scanner.next().equalsIgnoreCase("y"); } @Override void onReleaseDownloaded(File file, Release release) { String message = "New ClassyShark version available offline!\n" + "The new release " + release.getReleaseName() + " has been downloaded to " + file.getAbsolutePath(); System.out.println(message); } public static AbstractDownloader getInstance() { return instance; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/networking/GitHubApi.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.networking; import com.google.classyshark.updater.models.Release; import retrofit2.Call; import retrofit2.http.GET; /** * This class is the one taking care of representing the API needed * in order to retrieve the latest release data from GitHub. */ public interface GitHubApi { String ENDPOINT = "https://api.github.com/"; @GET("repos/google/android-classyshark/releases/latest") Call getLatestRelease(); } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/networking/GuiDownloader.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.networking; import com.google.classyshark.updater.models.Release; import javax.swing.*; import java.io.File; /** * This class is the one being called when ClassyShark shall be updated from the GUI. * Its callback is informing the user about the new version and the new changelog */ public class GuiDownloader extends AbstractDownloader{ private final static AbstractDownloader instance = new GuiDownloader(); private GuiDownloader() {} @Override boolean warnAboutNew(Release release) { return true; } @Override void onReleaseDownloaded(File file, Release release) { SwingUtilities.invokeLater(new MessageRunnable(release.getReleaseName(), release.getChangelog())); } public static AbstractDownloader getInstance() { return instance; } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/networking/MessageRunnable.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.networking; import javax.swing.*; class MessageRunnable implements Runnable { private final String title; private final String changelog; private final String ICON_PATH = "/resources/ic_update.png"; MessageRunnable(String title, String changelog) { this.title = buildTitleFrom(title); this.changelog = buildChangelogFrom(changelog); } private String buildTitleFrom(String title) { return "New ClassyShark version " + title; } private String buildChangelogFrom(String changelog) { return "A new ClassyShark version is available for you in the same directory where the old one was!\nCheck it out!\nCHANGELOG:\n" + changelog; } @Override public void run() { final Icon icon = new ImageIcon(getClass().getResource(ICON_PATH)); JOptionPane.showConfirmDialog(null, changelog, title, JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, icon); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/networking/NetworkManager.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.networking; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class NetworkManager { public static GitHubApi getGitHubApi() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(GitHubApi.ENDPOINT) .addConverterFactory(GsonConverterFactory.create()) .build(); return retrofit.create(GitHubApi.class); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/utils/FileUtils.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.utils; import com.google.classyshark.updater.models.Release; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import static com.google.classyshark.updater.utils.NamingUtils.extractCurrentPath; import static javax.script.ScriptEngine.FILENAME; /** * This class is the one taking care of managing the files: it will download the new version and, when needed, replace * the old one with the new. * * @see #downloadFileFrom(Release) Implementation details */ public class FileUtils { /** * This method will download the new version if and only if it has not been downloaded already. * In case the software detects that the same new JAR is already present, it will skip the download and just * notify the user about the new version */ public static File downloadFileFrom(Release release) throws IOException { File file = new File(NamingUtils.buildNameFrom(release)); if (!file.exists()) { obtainNewJarFrom(release, file); } return file; } private static void obtainNewJarFrom(Release release, File file) throws IOException { URL url = new URL(release.getDownloadURL()); ReadableByteChannel rbc = Channels.newChannel(url.openStream()); file.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(file); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); } private void overwriteOld(File file) throws IOException { Path path = new File(extractCurrentPath() + File.separator + FILENAME).toPath(); Files.copy(file.toPath(), path, StandardCopyOption.REPLACE_EXISTING); file.delete(); } } ================================================ FILE: ClassySharkWS/src/com/google/classyshark/updater/utils/NamingUtils.java ================================================ /* * Copyright 2016 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.classyshark.updater.utils; import com.google.classyshark.updater.models.Release; import java.io.File; import java.nio.file.Paths; public class NamingUtils { private static final String PREFIX = "ClassyShark"; private static final String SUFFIX = ".jar"; final String FILENAME = "ClassyShark.jar"; static String buildNameFrom(Release release) { String[] creationDates = release.getCreatedAt().split("T"); String timeStamp = ""; if (creationDates.length > 0) { timeStamp = "_" + creationDates[0]; } return extractCurrentPath() + File.separator + PREFIX + timeStamp + SUFFIX; } static String extractCurrentPath() { return Paths.get(".").toAbsolutePath().normalize().toString(); } } ================================================ FILE: NOTICE ================================================ This sample uses the following software: Copyright 2015, Google Inc. 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. ================================================ FILE: README.md ================================================ # ClassyShark ### Introduction ![alt text](https://github.com/borisf/classyshark-user-guide/blob/master/images/5%20ClassesDexData.png) ClassyShark is a standalone binary inspection tool for Android developers. It can reliably browse any Android executable and show important info such as class interfaces and members, dex counts and dependencies. ClassyShark supports multiple formats including libraries (.dex, .aar, .so), executables (.apk, .jar, .class) and all Android binary XMLs: AndroidManifest, resources, layouts etc. ### Useful links * [User guide](https://github.com/borisf/classyshark-user-guide) * [Command-line reference](https://github.com/google/android-classyshark/blob/master/CommandLine.pdf) * Gradle [sample](https://github.com/google/android-classyshark/tree/master/Samples/SampleGradle) * [Vision and Strategy](https://docs.google.com/document/d/1sK_WNzHn_6Q1V_dohxrtk1tlsPXsi9cEVnIuYuVig0M/edit?usp=sharing) ### Download To run, grab the [latest JAR](https://github.com/google/android-classyshark/releases) and run `java -jar ClassyShark.jar`. ### Export data in text format * [Exporter](https://medium.com/@BorisFarber/exporting-data-from-classyshark-e3cf3fe3fab8#.deec4nyjq) * API finder :construction: work in progress ### Develop 1. Clone the repo 2. Open in your favorite IDE/editor 3. Build options: * IntelliJ - builds automatically when exporting the project * [Gradle script](https://github.com/google/android-classyshark/blob/master/ClassySharkWS/build.gradle) * [RetroBuild](https://github.com/borisf/RetroBuild) ### Arch Linux If you're running Arch Linux you can install the latest [prebuilt jar from the AUR](https://aur.archlinux.org/packages/classyshark/). ### Dependencies * [dexlib2](https://github.com/JesusFreke/smali/tree/master/dexlib2) by jesusfreke * [guava](https://github.com/google/guava) by Google * [ASM](http://asm.ow2.org/) by OW2 * [ASMDEX](http://asm.ow2.org/asmdex-index.html) by OW2 * [java-binutils](https://github.com/jawi/java-binutils) by jawi * [BCEL](https://commons.apache.org/proper/commons-bcel) by Apache ### Support If you've found an error, please file an issue: https://github.com/google/android-classyshark/issues Patches are encouraged, and may be submitted by forking this project and submitting a pull request through GitHub. License ======= Copyright 2020 Google, Inc. 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. ================================================ FILE: Samples/.gitignore ================================================ *.class .idea/.name *.xml classyshark-sample-plugin.iml Sample/.idea/.name SampleGradle/.gradle/ *.jar *.iml ================================================ FILE: Samples/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. ================================================ FILE: Samples/README.md ================================================ # classyshark-sample-plugin How to use [ClassyShark] (https://github.com/google/android-classyshark) in your build/continous integration and call its APIs. ``` Java public class Shark { /** * @param className class name to generate such as "com.bumptech.glide.request.target.BaseTarget" * @return a new Shark instance */ public static Shark with(File archiveFile); /** * @param className class name to generate such as "com.bumptech.glide.request.target.BaseTarget" * @return */ public String getGeneratedClass(String className); /** * @return list of class names */ public List getAllClassNames(); /** * @return manifest */ public String getManifest(); /** * @return all methods */ public List getAllMethods(); /** * @return all strings from all string tables */ public List getAllStrings(); } ``` ================================================ FILE: Samples/SampleGradle/README.md ================================================ # ClassyShark Gradle plugin ![How to run](RunScreenshot.png) ================================================ FILE: Samples/SampleGradle/build.gradle ================================================ apply plugin: 'java' apply plugin: 'application' repositories { flatDir { dirs 'libs' } } dependencies { compile name: 'ClassyShark' } sourceSets { main { java { srcDirs 'src/main/java' } } } // Main.java is located in src/main/java/Main.java mainClassName = "Main" ================================================ FILE: Samples/SampleGradle/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # For Cygwin, ensure paths are in UNIX format before anything is touched. if $cygwin ; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` fi # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >&- APP_HOME="`pwd -P`" cd "$SAVED" >&- CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: Samples/SampleGradle/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: Samples/SampleGradle/src/main/java/Main.java ================================================ /* * Copyright 2016 Google, Inc. * * 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. */ import com.google.classyshark.Shark; import java.io.File; public class Main { public static void main(String[] args) { File apk = new File("/Users/bfarber/Desktop/Scenarios/4 APKs/" + "com.google.samples.apps.iosched-333.apk"); Shark shark = Shark.with(apk); System.out.println( shark.getGeneratedClass("com.bumptech.glide.request.target.BaseTarget")); System.out.println(shark.getAllClassNames()); System.out.println(shark.getManifest()); System.out.println(shark.getAllMethods()); System.out.println("\n\n\n\nAll Classes " + shark.getAllClassNames().size() + "\nAll Methods " + shark.getAllMethods().size()); } } ================================================ FILE: third_party/ASMDEX.LICENSE ================================================ Software Name : AsmDex Version : 1.0 Copyright © 2012 France Télécom All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: third_party/java-binutils.LICENSE ================================================ Eclipse Public License - v 1.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and b) in the case of each subsequent Contributor: i) changes to the Program, and ii) additions to the Program; where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. "Contributor" means any person or entity that distributes the Program. "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Program" means the Contributions distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 3. REQUIREMENTS A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: a) it complies with the terms and conditions of this Agreement; and b) its license agreement: i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. When the Program is made available in source code form: a) it must be made available under this Agreement; and b) a copy of this Agreement must be included with each copy of the Program. Contributors may not remove or alter any copyright notices contained within the Program. Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.