/* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * 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.tencent.tinker.loader.shareutil; import android.app.ActivityManager; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Created by zhangshaowen on 16/3/10. */ public class ShareTinkerInternals { private static final String TAG = "Tinker.TinkerInternals"; private static final boolean VM_IS_ART = isVmArt(System.getProperty("java.vm.version")); private static final boolean VM_IS_JIT = isVmJitInternal(); private static final String PATCH_PROCESS_NAME = ":patch"; private static Boolean isPatchProcess = null; /** * or you may just hardcode them in your app */ private static String processName = null; private static String tinkerID = null; public static boolean isVmArt() { return VM_IS_ART || Build.VERSION.SDK_INT >= 21; } public static boolean isVmJit() { return VM_IS_JIT && Build.VERSION.SDK_INT < 24; } public static boolean isSystemOTA(String lastFingerPrint) { String currentFingerprint = Build.FINGERPRINT; if (lastFingerPrint == null || lastFingerPrint.equals("") || currentFingerprint == null || currentFingerprint.equals("")) { Log.d(TAG, "fingerprint empty:" + lastFingerPrint + ",current:" + currentFingerprint); return false; } else { if (lastFingerPrint.equals(currentFingerprint)) { Log.d(TAG, "same fingerprint:" + currentFingerprint); return false; } else { Log.d(TAG, "system OTA,fingerprint not equal:" + lastFingerPrint + "," + currentFingerprint); return true; } } } public static boolean isNullOrNil(final String object) { if ((object == null) || (object.length() <= 0)) { return true; } return false; } /** * thinker package check * * @param context * @param tinkerFlag * @param patchFile * @param securityCheck * @return */ public static int checkTinkerPackage(Context context, int tinkerFlag, File patchFile, ShareSecurityCheck securityCheck) { int returnCode = checkSignatureAndTinkerID(context, patchFile, securityCheck); if (returnCode == ShareConstants.ERROR_PACKAGE_CHECK_OK) { returnCode = checkPackageAndTinkerFlag(securityCheck, tinkerFlag); } return returnCode; } /** * check patch file signature and TINKER_ID * * @param context * @param patchFile * @param securityCheck * @return */ public static int checkSignatureAndTinkerID(Context context, File patchFile, ShareSecurityCheck securityCheck) { if (!securityCheck.verifyPatchMetaSignature(patchFile)) { return ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL; } String oldTinkerId = getManifestTinkerID(context); if (oldTinkerId == null) { return ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND; } HashMap<String, String> properties = securityCheck.getPackagePropertiesIfPresent(); if (properties == null) { return ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND; } String patchTinkerId = properties.get(ShareConstants.TINKER_ID); if (patchTinkerId == null) { return ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND; } if (!oldTinkerId.equals(patchTinkerId)) { Log.e(TAG, "tinkerId is not equal, base is " + oldTinkerId + ", but patch is " + patchTinkerId); return ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL; } return ShareConstants.ERROR_PACKAGE_CHECK_OK; } public static int checkPackageAndTinkerFlag(ShareSecurityCheck securityCheck, int tinkerFlag) { if (isTinkerEnabledAll(tinkerFlag)) { return ShareConstants.ERROR_PACKAGE_CHECK_OK; } HashMap<String, String> metaContentMap = securityCheck.getMetaContentMap(); //check dex boolean dexEnable = isTinkerEnabledForDex(tinkerFlag); if (!dexEnable && metaContentMap.containsKey(ShareConstants.DEX_META_FILE)) { return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT; } //check native library boolean nativeEnable = isTinkerEnabledForNativeLib(tinkerFlag); if (!nativeEnable && metaContentMap.containsKey(ShareConstants.SO_META_FILE)) { return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT; } //check resource boolean resEnable = isTinkerEnabledForResource(tinkerFlag); if (!resEnable && metaContentMap.containsKey(ShareConstants.RES_META_FILE)) { return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT; } return ShareConstants.ERROR_PACKAGE_CHECK_OK; } /** * not like {@cod ShareSecurityCheck.getPackagePropertiesIfPresent} * we don't check Signatures or other files, we just get the package meta's properties directly * * @param patchFile * @return */ public static Properties fastGetPatchPackageMeta(File patchFile) { if (patchFile == null || !patchFile.isFile() || patchFile.length() == 0) { Log.e(TAG, "patchFile is illegal"); return null; } ZipFile zipFile = null; try { zipFile = new ZipFile(patchFile); ZipEntry packageEntry = zipFile.getEntry(ShareConstants.PACKAGE_META_FILE); if (packageEntry == null) { Log.e(TAG, "patch meta entry not found"); return null; } InputStream inputStream = null; try { inputStream = zipFile.getInputStream(packageEntry); Properties properties = new Properties(); properties.load(inputStream); return properties; } finally { SharePatchFileUtil.closeQuietly(inputStream); } } catch (IOException e) { Log.e(TAG, "fastGetPatchPackageMeta exception:" + e.getMessage()); return null; } finally { SharePatchFileUtil.closeZip(zipFile); } } public static String getManifestTinkerID(Context context) { if (tinkerID != null) { return tinkerID; } try { ApplicationInfo appInfo = context.getPackageManager() .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); Object object = appInfo.metaData.get(ShareConstants.TINKER_ID); if (object != null) { tinkerID = String.valueOf(object); } else { tinkerID = null; } } catch (Exception e) { Log.e(TAG, "getManifestTinkerID exception:" + e.getMessage()); return null; } return tinkerID; } public static boolean isTinkerEnabledForDex(int flag) { return (flag & ShareConstants.TINKER_DEX_MASK) != 0; } public static boolean isTinkerEnabledForNativeLib(int flag) { return (flag & ShareConstants.TINKER_NATIVE_LIBRARY_MASK) != 0; } public static boolean isTinkerEnabledForResource(int flag) { //FIXME:res flag depends dex flag return (flag & ShareConstants.TINKER_RESOURCE_MASK) != 0; } public static String getTypeString(int type) { switch (type) { case ShareConstants.TYPE_DEX: return "dex"; case ShareConstants.TYPE_DEX_OPT: return "dex_opt"; case ShareConstants.TYPE_LIBRARY: return "lib"; case ShareConstants.TYPE_PATCH_FILE: return "patch_file"; case ShareConstants.TYPE_PATCH_INFO: return "patch_info"; case ShareConstants.TYPE_RESOURCE: return "resource"; default: return "unknown"; } } /** * you can set Tinker disable in runtime at some times! * * @param context */ public static void setTinkerDisableWithSharedPreferences(Context context) { SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); sp.edit().putBoolean(getTinkerSharedPreferencesName(), false).commit(); } /** * can't load or receive any patch! * * @param context * @return */ public static boolean isTinkerEnableWithSharedPreferences(Context context) { if (context == null) { return false; } SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS); return sp.getBoolean(getTinkerSharedPreferencesName(), true); } private static String getTinkerSharedPreferencesName() { return ShareConstants.TINKER_ENABLE_CONFIG + ShareConstants.TINKER_VERSION; } public static boolean isTinkerEnabled(int flag) { return (flag != ShareConstants.TINKER_DISABLE); } public static boolean isTinkerEnabledAll(int flag) { return (flag == ShareConstants.TINKER_ENABLE_ALL); } public static boolean isInMainProcess(Context context) { String pkgName = context.getPackageName(); String processName = getProcessName(context); if (processName == null || processName.length() == 0) { processName = ""; } return pkgName.equals(processName); } public static boolean isInPatchProcess(Context context) { if (isPatchProcess != null) { return isPatchProcess; } isPatchProcess = getProcessName(context).endsWith(PATCH_PROCESS_NAME); return isPatchProcess; } public static String getCurrentOatMode(Context context, String current) { if (current.equals(ShareConstants.CHANING_DEX_OPTIMIZE_PATH)) { if (isInMainProcess(context)) { current = ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH; } else { current = ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH; } } return current; } public static void killAllOtherProcess(Context context) { final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am == null) { return; } List<ActivityManager.RunningAppProcessInfo> appProcessList = am .getRunningAppProcesses(); if (appProcessList == null) { return; } // NOTE: getRunningAppProcess() ONLY GIVE YOU THE PROCESS OF YOUR OWN PACKAGE IN ANDROID M // BUT THAT'S ENOUGH HERE for (ActivityManager.RunningAppProcessInfo ai : appProcessList) { // KILL OTHER PROCESS OF MINE if (ai.uid == android.os.Process.myUid() && ai.pid != android.os.Process.myPid()) { android.os.Process.killProcess(ai.pid); } } } /** * add process name cache * * @param context * @return */ public static String getProcessName(final Context context) { if (processName != null) { return processName; } //will not null processName = getProcessNameInternal(context); return processName; } private static String getProcessNameInternal(final Context context) { int myPid = android.os.Process.myPid(); if (context == null || myPid <= 0) { return ""; } ActivityManager.RunningAppProcessInfo myProcess = null; ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> appProcessList = activityManager .getRunningAppProcesses(); if (appProcessList != null) { try { for (ActivityManager.RunningAppProcessInfo process : appProcessList) { if (process.pid == myPid) { myProcess = process; break; } } } catch (Exception e) { Log.e(TAG, "getProcessNameInternal exception:" + e.getMessage()); } if (myProcess != null) { return myProcess.processName; } } byte[] b = new byte[128]; FileInputStream in = null; try { in = new FileInputStream("/proc/" + myPid + "/cmdline"); int len = in.read(b); if (len > 0) { for (int i = 0; i < len; i++) { // lots of '0' in tail , remove them if (b[i] > 128 || b[i] <= 0) { len = i; break; } } return new String(b, 0, len); } } catch (Exception e) { Log.e(TAG, "getProcessNameInternal exception:" + e.getMessage()); } finally { try { if (in != null) { in.close(); } } catch (Exception e) { } } return ""; } /** * vm whether it is art * * @return */ private static boolean isVmArt(String versionString) { boolean isArt = false; if (versionString != null) { Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString); if (matcher.matches()) { try { int major = Integer.parseInt(matcher.group(1)); int minor = Integer.parseInt(matcher.group(2)); isArt = (major > 2) || ((major == 2) && (minor >= 1)); } catch (NumberFormatException e) { // let isMultidexCapable be false } } } return isArt; } private static boolean isVmJitInternal() { try { Class<?> clazz = Class.forName("android.os.SystemProperties"); Method mthGet = clazz.getDeclaredMethod("get", String.class); String jit = (String) mthGet.invoke(null, "dalvik.vm.usejit"); String jitProfile = (String) mthGet.invoke(null, "dalvik.vm.usejitprofiles"); //usejit is true and usejitprofiles is null if (!isNullOrNil(jit) && isNullOrNil(jitProfile) && jit.equals("true")) { return true; } } catch (Throwable e) { Log.e(TAG, "isVmJitInternal ex:" + e); } return false; } public static String getExceptionCauseString(final Throwable ex) { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(bos); try { // print directly Throwable t = ex; while (t.getCause() != null) { t = t.getCause(); } t.printStackTrace(ps); return toVisualString(bos.toString()); } finally { try { bos.close(); } catch (IOException e) { } } } public static String toVisualString(String src) { boolean cutFlg = false; if (null == src) { return null; } char[] chr = src.toCharArray(); if (null == chr) { return null; } int i = 0; for (; i < chr.length; i++) { if (chr[i] > 127) { chr[i] = 0; cutFlg = true; break; } } if (cutFlg) { return new String(chr, 0, i); } else { return src; } } }