/* * This file is part of the RootTools Project: http://code.google.com/p/roottools/ * * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks * * This code is dual-licensed under the terms of the Apache License Version 2.0 and * the terms of the General Public License (GPL) Version 2. * You may use this code according to either of these licenses as is most appropriate * for your project on a case-by-case basis. * * The terms of each license can be found in the root directory of this project's repository as well as at: * * * http://www.apache.org/licenses/LICENSE-2.0 * * http://www.gnu.org/licenses/gpl-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under these Licenses is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See each License for the specific language governing permissions and * limitations under that License. */ package com.stericson.RootTools.internal; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import android.util.Log; import com.stericson.RootTools.RootTools; import com.stericson.RootTools.execution.Command; import com.stericson.RootTools.execution.CommandCapture; import com.stericson.RootTools.execution.Shell; import android.content.Context; class Installer { //------------- //# Installer # //------------- static final String LOG_TAG = "RootTools::Installer"; static final String BOGUS_FILE_NAME = "bogus"; Context context; String filesPath; public Installer(Context context) throws IOException { this.context = context; this.filesPath = context.getFilesDir().getCanonicalPath(); } /** * This method can be used to unpack a binary from the raw resources folder and store it in * /data/data/app.package/files/ * This is typically useful if you provide your own C- or C++-based binary. * This binary can then be executed using sendShell() and its full path. * * @param sourceId resource id; typically <code>R.raw.id</code> * @param destName destination file name; appended to /data/data/app.package/files/ * @param mode chmod value for this file * @return a <code>boolean</code> which indicates whether or not we were * able to create the new file. */ protected boolean installBinary(int sourceId, String destName, String mode) { File mf = new File(filesPath + File.separator + destName); if (!mf.exists() || !getFileSignature(mf).equals( getStreamSignature( context.getResources().openRawResource(sourceId)) )) { Log.e(LOG_TAG, "Installing a new version of binary: " + destName); // First, does our files/ directory even exist? // We cannot wait for android to lazily create it as we will soon // need it. try { FileInputStream fis = context.openFileInput(BOGUS_FILE_NAME); fis.close(); } catch (FileNotFoundException e) { FileOutputStream fos = null; try { fos = context.openFileOutput("bogus", Context.MODE_PRIVATE); fos.write("justcreatedfilesdirectory".getBytes()); } catch (Exception ex) { if (RootTools.debugMode) { Log.e(LOG_TAG, ex.toString()); } return false; } finally { if (null != fos) { try { fos.close(); context.deleteFile(BOGUS_FILE_NAME); } catch (IOException e1) {} } } } catch (IOException ex) { if (RootTools.debugMode) { Log.e(LOG_TAG, ex.toString()); } return false; } // Only now can we start creating our actual file InputStream iss = context.getResources().openRawResource(sourceId); ReadableByteChannel rfc = Channels.newChannel(iss); FileOutputStream oss = null; try { oss = new FileOutputStream(mf); FileChannel ofc = oss.getChannel(); long pos = 0; try { long size = iss.available(); while ((pos += ofc.transferFrom(rfc, pos, size- pos)) < size) ; } catch (IOException ex) { if (RootTools.debugMode) { Log.e(LOG_TAG, ex.toString()); } return false; } } catch (FileNotFoundException ex) { if (RootTools.debugMode) { Log.e(LOG_TAG, ex.toString()); } return false; } finally { if (oss != null) { try { oss.flush(); oss.getFD().sync(); oss.close(); } catch (Exception e) { } } } try { iss.close(); } catch (IOException ex) { if (RootTools.debugMode) { Log.e(LOG_TAG, ex.toString()); } return false; } try { CommandCapture command = new CommandCapture(0, false, "chmod " + mode + " " + filesPath + File.separator + destName); Shell.startRootShell().add(command); commandWait(command); } catch (Exception e) {} } return true; } protected boolean isBinaryInstalled(String destName) { boolean installed = false; File mf = new File(filesPath + File.separator + destName); if (mf.exists()) { installed = true; // TODO: pass mode as argument and check it matches } return installed; } protected String getFileSignature(File f) { String signature = ""; try { signature = getStreamSignature(new FileInputStream(f)); } catch (FileNotFoundException ex) { Log.e(LOG_TAG, ex.toString()); } return signature; } /* * Note: this method will close any string passed to it */ protected String getStreamSignature(InputStream is) { String signature = ""; try { MessageDigest md = MessageDigest.getInstance("MD5"); DigestInputStream dis = new DigestInputStream(is, md); byte [] buffer = new byte[4096]; while(-1 != dis.read(buffer)); byte[] digest = md.digest(); StringBuffer sb = new StringBuffer(); for(int i=0; i<digest.length; i++) sb.append(Integer.toHexString(digest[i] & 0xFF)); signature = sb.toString(); } catch (IOException ex) { Log.e(LOG_TAG, ex.toString()); } catch (NoSuchAlgorithmException ex) { Log.e(LOG_TAG, ex.toString()); } finally { try { is.close(); } catch (IOException e) {} } return signature; } private void commandWait(Command cmd) { synchronized (cmd) { try { if (!cmd.isFinished()) { cmd.wait(2000); } } catch (InterruptedException ex) { Log.e(LOG_TAG, ex.toString()); } } } }