package com.majeur.applicationsinfo; import android.app.Activity; import android.app.ProgressDialog; import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.view.MenuItem; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.widget.Toast; import com.majeur.xmlapkparser.AXMLPrinter; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringWriter; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; /** * Activity that shows AndroidManifest.xml of any apps. Application package name must be passed as extra. * To correctly display returned xml file, we show it in a {@link android.webkit.WebView}. The way we do that * is not very natural, but it's the simplest way to do that. * So, asynchronously, we get raw xml string and save it to a file. Then we ask to WebView to display this file, * by this way, WebView auto detect xml and display it nicely. * File do not need to be kept. We delete it to keep used memory as low as possible, but anyway, each time * we will show application's manifest, the same file will be used, so used memory will not grow. */ public class ViewManifestActivity extends Activity { public static final String EXTRA_PACKAGE_NAME = "package_name"; private WebView mWebView; private ProgressDialog mProgressDialog; private String mPath; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getActionBar().setDisplayHomeAsUpEnabled(true); mWebView = new WebView(this); setContentView(mWebView); mProgressDialog = new ProgressDialog(this); mProgressDialog.setCancelable(false); mProgressDialog.setMessage(getString(R.string.loading)); mPath = getFilesDir() + "/data.xml"; String packageName = getIntent().getStringExtra(EXTRA_PACKAGE_NAME); String filePath = null, applicationLabel = null; try { filePath = getPackageManager().getPackageInfo(packageName, 0).applicationInfo.sourceDir; applicationLabel = getPackageManager().getApplicationInfo(packageName, 0).loadLabel(getPackageManager()).toString(); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); Toast.makeText(this, R.string.app_not_installed, Toast.LENGTH_LONG).show(); finish(); } setTitle(getString(R.string.manifest) + ": " + applicationLabel); new AsyncManifestLoader().execute(filePath); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { finish(); return true; } return super.onOptionsItemSelected(item); } private void displayContent() { WebSettings settings = mWebView.getSettings(); settings.setBuiltInZoomControls(true); settings.setUseWideViewPort(true); mWebView.setWebChromeClient(new MyWebChromeClient()); mWebView.loadUrl(Uri.fromFile(new File(mPath)).toString()); } private void handleError() { Toast.makeText(this, R.string.error, Toast.LENGTH_LONG).show(); finish(); } @Override protected void onDestroy() { super.onDestroy(); File file = new File(mPath); file.delete(); } final class MyWebChromeClient extends WebChromeClient { @Override public void onProgressChanged(WebView view, int progress) { if (progress == 100) showProgressBar(false); } } private void showProgressBar(boolean show) { if (show) mProgressDialog.show(); else mProgressDialog.dismiss(); } /** * This AsyncTask takes manifest file path as argument */ private class AsyncManifestLoader extends AsyncTask<String, Integer, Boolean> { @Override protected void onPreExecute() { super.onPreExecute(); showProgressBar(true); } @Override protected Boolean doInBackground(String... strings) { String filePath = strings[0]; String code = getProperXml(AXMLPrinter.getManifestXMLFromAPK(filePath)); if (code == null) return false; try { File file = new File(mPath); if (!file.exists()) file.createNewFile(); FileOutputStream output = new FileOutputStream(file); output.write(code.getBytes()); output.flush(); output.close(); } catch (IOException e) { e.printStackTrace(); } return true; } /** * Format xml file to correct indentation ... */ private String getProperXml(String dirtyXml) { try { Document document = DocumentBuilderFactory.newInstance() .newDocumentBuilder() .parse(new InputSource(new ByteArrayInputStream(dirtyXml.getBytes("utf-8")))); XPath xPath = XPathFactory.newInstance().newXPath(); NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']", document, XPathConstants.NODESET); for (int i = 0; i < nodeList.getLength(); ++i) { Node node = nodeList.item(i); node.getParentNode().removeChild(node); } Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); StringWriter stringWriter = new StringWriter(); StreamResult streamResult = new StreamResult(stringWriter); transformer.transform(new DOMSource(document), streamResult); return stringWriter.toString(); } catch (Exception e) { e.printStackTrace(); return null; } } /** * Do not hide progressDialog here, WebView will hide it when content will be displayed */ @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); if (result) displayContent(); else handleError(); } } }