/*
*
* 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 2016 Alibaba Group
*
* 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 android.taobao.atlas.framework;
import android.app.PreVerifier;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Environment;
import android.taobao.atlas.bundleInfo.AtlasBundleInfoManager;
import android.taobao.atlas.bundleInfo.BundleListing;
import android.taobao.atlas.framework.bundlestorage.BundleArchive;
import android.taobao.atlas.runtime.ClassNotFoundInterceptorCallback;
import android.taobao.atlas.runtime.InstrumentationHook;
import android.taobao.atlas.runtime.LowDiskException;
import android.taobao.atlas.runtime.RuntimeVariables;
import android.taobao.atlas.util.*;
import android.taobao.atlas.util.AtlasFileLock;
import android.taobao.atlas.versionInfo.BaselineInfoManager;
import android.text.TextUtils;
import android.util.Log;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
public final class Framework {
public final static String DEPRECATED_MARK = "deprecated";
public final static String UPDATED_MARK = "markUpdated";
/**
* Version displayed upon startup and returned by System Bundle
*/
static final String FRAMEWORK_VERSION = "0.9.0";
static Map<String,Integer> restoreFailedMap = new HashMap<String,Integer>();
static String containerVersion = "";
private static ClassNotFoundInterceptorCallback classNotFoundCallback;
public static String ATLAS_DEBUG_DIRECTORY;
// the runtime args
/**
* framework basedir.
*/
private static String BASEDIR;
/**
* bundle location.
*/
private static String BUNDLE_LOCATION;
/**
* the location where the storage resides.
*/
public static final String STORAGE_LOCATION;
/**
* classloader buffer size.
*/
static int CLASSLOADER_BUFFER_SIZE;
/**
* log level.
*/
static int LOG_LEVEL;
/**
* debug outputs from bundles ?
*/
static boolean DEBUG_BUNDLES;
/**
* debug outputs from class loading ?
*/
static boolean DEBUG_CLASSLOADING;
/**
* location -> bundle.
*/
static Map<String, Bundle> bundles = new ConcurrentHashMap<String, Bundle>();
/**
* bundle listeners.
*/
static List<BundleListener> bundleListeners = new ArrayList<BundleListener>();
/**
* synchronous bundle listeners.
*/
static List<BundleListener> syncBundleListeners = new ArrayList<BundleListener>();
/**
* framework listeners.
*/
static List<FrameworkListener> frameworkListeners = new ArrayList<FrameworkListener>();
/**
* system ClassLoader
*/
static ClassLoader systemClassLoader;
// the system bundle
/**
* system bundle.
*/
static SystemBundle systemBundle;
// the fields
/**
* properties.
*/
static Properties properties;
/**
* the framework start level.
*/
static int startlevel = 0;
/**
* the dir name of WAL
*/
static List<String> writeAheads = new ArrayList<String>();
/**
* the initial startlevel for installed bundles.
*/
static int initStartlevel = 1;
static Map<String,String> mMapForComAndBundles = new HashMap<String,String>();
/**
* a hook in delegateClassLoader's find class to trigger whether install/dexopt bundle when class not find happend
*/
public static boolean needRestart = false;
static String currentProcessName;
// constants
/**
* the admin permission.
*/
private static boolean bundleUpdated = false;
static {
File fileDir = RuntimeVariables.androidApplication.getFilesDir();
if (fileDir == null || !fileDir.exists()) {
fileDir = RuntimeVariables.androidApplication.getFilesDir();
}
try {
ATLAS_DEBUG_DIRECTORY = RuntimeVariables.androidApplication.getExternalFilesDir("atlas-debug").getAbsolutePath();
} catch (Exception e) {
ATLAS_DEBUG_DIRECTORY = "/sdcard/Android/data/" + RuntimeVariables.androidApplication.getPackageName() + "/files/atlas-debug";
}
BASEDIR = fileDir.getAbsolutePath();
STORAGE_LOCATION = BASEDIR + File.separatorChar + "storage" + File.separatorChar;
}
/**
* Hide defautlt constructor
*/
private Framework() {
if(Boolean.FALSE.booleanValue()){
String.valueOf(PreVerifier.class);
}
}
/*
* startup and shutdown related methods
*/
/**
* launch the framework.
*
* @throws Throwable
*/
static void startup(Properties props) throws BundleException {
// Log.e("Framwork","patch success!");
properties = props == null ? new Properties() : props;
AtlasBundleInfoManager.instance().getBundleInfo();
startup();
}
/**
* launch the framework.
*
* @throws Throwable
*/
static void startup() throws BundleException {
long time = System.currentTimeMillis();
initialize();
launch();
// if profile set, try to restart the profile
boolean init = getProperty("osgi.init", true);
// some non-main process, major dex is enough
if (needRestart) {
init = true;
}
if (init) {
resetStorage();
} else if (RuntimeVariables.getProcessName(RuntimeVariables.androidApplication).equals(RuntimeVariables.androidApplication.getPackageName())) {
restoreProfile();
}
if(RuntimeVariables.shouldSyncUpdateInThisProcess()) {
BaselineInfoManager.instance().checkUpdateBundles(STORAGE_LOCATION);
}
notifyFrameworkListeners(0 /* STARTING */, systemBundle, null);
// save the metadata
if (init) {
try {
storeProfile();
}catch(Exception e){
throw new RuntimeException("storeProfile failed", e);
}
}
final long timediff = System.currentTimeMillis() - time;
systemBundle.state = Bundle.ACTIVE;
try {
notifyFrameworkListeners(FrameworkEvent.STARTED, systemBundle, null);
} catch (Exception e) {
throw new RuntimeException("notifyFrameworkListeners failed", e);
}
}
public static ClassLoader getSystemClassLoader() {
return systemClassLoader;
}
public static List<Bundle> getBundles() {
final List<Bundle> res = new ArrayList<Bundle>(bundles.size());
synchronized (bundles) {
res.addAll(bundles.values());
}
return res;
}
public synchronized static Bundle getBundle(String location) {
if (location == null){
return null;
}
return bundles.get(location);
}
public static Bundle getBundle(long id) {
return null;
}
private static void initialize() {
BUNDLE_LOCATION = properties.getProperty("android.taobao.atlas.jars", "file:" + BASEDIR);
CLASSLOADER_BUFFER_SIZE = getProperty("android.taobao.atlas.classloader.buffersize", 2048);
LOG_LEVEL = getProperty("android.taobao.atlas.log.level", Log.ERROR);
DEBUG_BUNDLES = getProperty("android.taobao.atlas.debug.bundles", false);
DEBUG_CLASSLOADING = getProperty("android.taobao.atlas.debug.classloading", false);
if (getProperty("android.taobao.atlas.debug", false)) {
LOG_LEVEL = Log.DEBUG;
DEBUG_BUNDLES = true;
DEBUG_CLASSLOADING = true;
}
final String sv = System.getProperty("java.specification.version");
final String sn = System.getProperty("java.specification.name");
properties.put("org.osgi.framework.executionenvironment", sn + "/" + sv);
// set framework properties
Object obj;
properties.put(Constants.FRAMEWORK_OS_NAME, (obj = System.getProperty("os.name")) != null ? obj : "undefined");
properties.put(Constants.FRAMEWORK_OS_VERSION,
(obj = System.getProperty("os.version")) != null ? obj : "undefined");
properties.put(Constants.FRAMEWORK_PROCESSOR, (obj = System.getProperty("os.arch")) != null ? obj : "undefined");
properties.put(Constants.FRAMEWORK_VERSION, FRAMEWORK_VERSION);
properties.put(Constants.FRAMEWORK_VENDOR, "Atlas");
final String lang = java.util.Locale.getDefault().getLanguage();
properties.put(Constants.FRAMEWORK_LANGUAGE, lang != null ? lang : "en");
}
/**
* create the setup with the properties and the internal framework flags.
*/
private static void launch() {
// create the system bundle
systemBundle = new SystemBundle();
systemBundle.state = Bundle.STARTING;
}
/**
* get a boolean property.
*
* @param key the key.
* @param defaultVal the default.
* @return the value.
*/
public static boolean getProperty(final String key, final boolean defaultVal) {
if (properties == null) {
return defaultVal;
}
final String val = (String) properties.get(key);
return val != null ? Boolean.valueOf(val).booleanValue() : defaultVal;
}
/**
* get an int property.
*
* @param key the key.
* @param defaultVal the default.
* @return the value.
*/
public static int getProperty(final String key, final int defaultVal) {
if (properties == null) {
return defaultVal;
}
final String val = (String) properties.get(key);
return val != null ? Integer.parseInt(val) : defaultVal;
}
/**
* get an String property.
*
* @param key the key.
* @return the value.
*/
public static String getProperty(final String key) {
if (properties == null) {
return null;
}
return (String) properties.get(key);
}
/**
* get an String property.
*
* @param key
* @param defaultString
* @return
*/
public static String getProperty(final String key, final String defaultString) {
if (properties == null) {
return defaultString;
}
return (String) properties.get(key);
}
/**
* write a warning or throw an Exception
*
* @param message
* @throws BundleException
*/
protected static void warning(String message) throws RuntimeException {
if (getProperty("android.taobao.atlas.strictStartup", false)) {
throw new RuntimeException(message);
}
System.err.println("WARNING: " + message);
}
/**
* store the profile.
*/
private static void storeProfile() {
// final BundleImpl[] bundleArray = (BundleImpl[]) getBundles().toArray(new BundleImpl[bundles.size()]);
// for (int i = 0; i < bundleArray.length; i++) {
// bundleArray[i].updateMetadata();
// }
storeMetadata();
}
/**
* store the framework metadata.
*/
static void storeMetadata() {
File file = null;
try {
file = new File(STORAGE_LOCATION, "meta");
final DataOutputStream out = new DataOutputStream(new FileOutputStream(file));
out.writeInt(startlevel);
String str = StringUtils.join(writeAheads.toArray(), ",");
out.writeUTF(str != null ? str : "");
out.flush();
out.close();
} catch (IOException ioe) {
// AtlasMonitor.getInstance().trace(AtlasMonitor.BUNDLE_INSTALL_FAIL, "System", AtlasMonitor.UPDATE_META_FAILED_MSG,
// FileUtils.getDataAvailableSpace());
Log.e("Framework","Could not save meta data.", ioe);
}
}
public static String getCurProcessName() {
return RuntimeVariables.getProcessName(RuntimeVariables.androidApplication);
}
/**
* restore a profile.
*
* @return the startlevel or -1 if the profile could not be restored.
*/
private static int restoreProfile() {
try {
final File file = new File(STORAGE_LOCATION, "meta");
if (!file.exists()) {
return -1;
}
final DataInputStream in = new DataInputStream(new FileInputStream(file));
final int targetStartlevel = in.readInt();
String[] arr = StringUtils.split(in.readUTF(), ",");
if (arr != null) {
writeAheads.addAll(Arrays.asList(arr));
}
try {
in.close();
} catch (Throwable e) {
}
final File storageDir = new File(STORAGE_LOCATION);
// merge writeAheads to storage
MergeWirteAheads(storageDir);
return targetStartlevel;
} catch (Exception ioe) {
// throw new RuntimeException(ioe);
return -1;
}
}
private static void resetStorage() {
File storage = new File(STORAGE_LOCATION);
// Try to remove the directory by three times.
int count = 3;
while (count-- > 0) {
if (storage.exists()) {
try {
deleteDirectory(storage);
} catch (Exception e) {
if (count == 1) {
// AtlasMonitor.getInstance().trace(AtlasMonitor.DELETE_STORAGE_FAIL,
// "System", AtlasMonitor.DELETE_STORAGE_FAILED_MSG, FileUtils.getDataAvailableSpace());
throw new RuntimeException("deleteDirectory failed", e);
}
}
} else {
break;
}
}
// Still not removed yet, throw exception
if (storage.exists()) {
// AtlasMonitor.getInstance().trace(AtlasMonitor.DELETE_STORAGE_FAIL,
// "System", AtlasMonitor.DELETE_STORAGE_FAILED_MSG, FileUtils.getDataAvailableSpace());
throw new RuntimeException("deleteDirectory failed");
}
try {
storage.mkdirs();
} catch (Throwable e) {
}
if (!storage.exists()) {
throw new RuntimeException("mkdirs failed");
}
}
private static void MergeWirteAheads(final File storageDir) {
try{
final File walsDir = new File(STORAGE_LOCATION, "wal");
String process = RuntimeVariables.getProcessName(RuntimeVariables.androidApplication);
Log.d("Framework","restoreProfile in process "+process);
if(process!=null) {
mergeWalsDir(walsDir, storageDir);
}
}catch(Throwable e){
if (android.os.Build.MODEL != null){
throw new RuntimeException(e);
}
}
}
private static void mergeWalsDir(File walsDir, File storageDir) {
if (writeAheads != null && writeAheads.size() > 0) {
for (int i = 0; i < writeAheads.size(); i++) {
if (writeAheads.get(i) == null) {
continue;
}
File walDir = new File(walsDir, writeAheads.get(i));
try {
if (walDir != null && walDir.exists()) {
// merge wal dir to storage
final File[] walBundleDirs = walDir.listFiles();
if ((walBundleDirs != null) && (walBundleDirs.length > 0)) {
for (File walBundleDir : walBundleDirs) {
if (walBundleDir.isDirectory()) {
File bundleDir = new File(storageDir, walBundleDir.getName());
// if (bundleDir.exists()) {
// deleteDirectory(bundleDir);
// }
File newRevDir = new File(walBundleDir,"version.1");
if(!newRevDir.exists()){
return;
}
File[] revisions = bundleDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
return filename.startsWith(BundleArchive.REVISION_DIRECTORY);
}
});
int targetREV = 1;
List<String> useless = new ArrayList<>();
File latestRevision = null;
String targetVersion = BundleArchive.REVISION_DIRECTORY+".1";
int rev;
if(revisions!=null && revisions.length>0){
for(File revision : revisions){
if((rev=Integer.parseInt(revision.getName().substring(8)))>= targetREV){
targetREV = rev;
if(!new File(revision,BundleArchive.DEPRECATED_MARK).exists()) {
if (latestRevision != null) {
useless.add(latestRevision.getName());
}
latestRevision = revision;
}
}else{
useless.add(revision.getName());
}
if(new File(revision,BundleArchive.DEPRECATED_MARK).exists()){
useless.add(revision.getName());
}
}
if(!BaselineInfoManager.instance().isCachePreVersion()){
useless.add(latestRevision.getName());
}
targetVersion = BundleArchive.REVISION_DIRECTORY+"."+(++targetREV);
}
File targetDir = new File(bundleDir,targetVersion);
targetDir.mkdirs();
File meta = new File(bundleDir,"meta");
if(meta.exists()){
meta.delete();
}
new File(walBundleDir,"meta").renameTo(meta);
// move bundle to storage
boolean result = newRevDir.renameTo(targetDir);
if(!result || !targetDir.exists()){
targetDir.mkdirs();
result = newRevDir.renameTo(targetDir);
new File(walBundleDir,"meta").renameTo(meta);
if(!result || !targetDir.exists() || !meta.exists()){
BaselineInfoManager.instance().rollbackHardly();
android.os.Process.killProcess(android.os.Process.myPid());
}
}else{
//remove old bundles
for(String uselessDir : useless){
File uselessFile = new File(bundleDir,uselessDir);
if(uselessFile.exists()){
deleteDirectory(uselessFile);
}
if(uselessFile.exists()){
new File(uselessFile,BundleArchive.DEPRECATED_MARK).createNewFile();
}
}
}
}
}
}
}
writeAheads.set(i, null);
} catch (Exception e) {
Log.e("Framework","Error while merge wal dir", e);
}
}
}
if (walsDir.exists()) {
deleteDirectory(walsDir);
}
}
/**
* delete a directory with all subdirs.
*
* @param path the directory.
*/
public static void deleteDirectory(final File path) {
final File[] files = path.listFiles();
if (files == null){
return;
}
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
deleteDirectory(files[i]);
} else {
files[i].delete();
}
}
path.delete();
}
/*
* framework operations
*/
/**
* install a bundle. if location starts with asset: then fetch file from the assets folder.
*
* @param location the bundle location.
* @return a Bundle object.
* @throws BundleException if the installation failed.
*/
static Bundle installNewBundle(final String location) throws BundleException {
try{
final String location2 = location.indexOf(":") > -1 ? location : BUNDLE_LOCATION + File.separatorChar
+ location;
return installNewBundle(location2, new URL(location2).openConnection().getInputStream());
} catch (IOException e) {
throw new BundleException("Cannot retrieve bundle from " + location, e);
}
}
/**
* install a bundle from input stream.
*
* @param location the bundle location.
* @param in the input stream.
* @return a Bundle object.
* @throws BundleException if the installation failed.
*/
static BundleImpl installNewBundle(final String location, final InputStream in)throws BundleException {
File bundleDir = null;
try{
BundleLock.WriteLock(location);
/*
* <specs page="58">Every bundle is uniquely identified by its location string. If an installed bundle is using
* the specified location, the installBundle method must return the Bundle object for that installed bundle and
* not install a new bundle.</specs>
*/
bundleDir = new File(STORAGE_LOCATION, location);
if(!bundleDir.exists()){
bundleDir.mkdirs();
}
AtlasFileLock.getInstance().LockExclusive(bundleDir);
final BundleImpl cached;
if ((cached = (BundleImpl) getBundle(location)) != null) {
return cached;
}
Log.e("BundleInstaller","real install " + location);
BundleImpl bundle = null;
BundleListing.BundleInfo info = AtlasBundleInfoManager.instance().getBundleInfo(location);
String version = info!=null ? info.getVersion() : "-1";
bundle = new BundleImpl(bundleDir, location, new BundleContext(), in, null, version,true,-1);
// storeMetadata();
return bundle;
} catch (IOException e) {
BundleException e1 = new BundleException("Failed to install bundle." + FileUtils.getAvailableDisk(), e);
if (bundleDir != null)
Framework.deleteDirectory(bundleDir);
if (FileUtils.getUsableSpace(Environment.getDataDirectory()) < LowDiskException.thredshold) {
throw new LowDiskException(FileUtils.getAvailableDisk(), e);
}
throw new BundleException("Failed to install bundle.", e);
} catch (BundleException e) {
BundleException e1 = new BundleException("Failed to install bundle." + FileUtils.getAvailableDisk(), e);
if (bundleDir != null)
Framework.deleteDirectory(bundleDir);
throw e1;
} finally {
BundleLock.WriteUnLock(location);
if (bundleDir != null) {
AtlasFileLock.getInstance().unLock(bundleDir);
}
}
}
/**
* install a bundle from input file, the file will move to bundle storage directory.
*
* @param location the bundle location.
* @param file the input file.
* @return a Bundle object.
* @throws BundleException if the installation failed.
* @throws
*/
static BundleImpl installNewBundle(final String location, final File file) throws BundleException {
File bundleDir = null;
try {
bundleDir = new File(STORAGE_LOCATION, location);
if(!bundleDir.exists()){
bundleDir.mkdirs();
}
BundleLock.WriteLock(location);
AtlasFileLock.getInstance().LockExclusive(bundleDir);
/*
* <specs page="58">Every bundle is uniquely identified by its location string. If an installed bundle is using
* the specified location, the installBundle method must return the Bundle object for that installed bundle and
* not install a new bundle.</specs>
*/
final BundleImpl cached;
if ((cached = (BundleImpl) getBundle(location)) != null) {
return cached;
}
Log.e("BundleInstaller","real install " + location);
BundleImpl bundle = null;
BundleListing.BundleInfo info = AtlasBundleInfoManager.instance().getBundleInfo(location);
String version = info!=null ? info.getVersion() : "-1";
bundle = new BundleImpl(bundleDir, location, new BundleContext(), null, file,version,true,-1);
// storeMetadata();
return bundle;
} catch (IOException e) {
BundleException e1 = new BundleException("Failed to install bundle." + FileUtils.getAvailableDisk(), e);
if (bundleDir != null)
Framework.deleteDirectory(bundleDir);
if (FileUtils.getUsableSpace(Environment.getDataDirectory()) < LowDiskException.thredshold) {
throw new LowDiskException(FileUtils.getAvailableDisk(), e);
}
throw new BundleException("Failed to install bundle.", e);
} catch (BundleException e) {
BundleException e1 = new BundleException("Failed to install bundle." + FileUtils.getAvailableDisk(), e);
if (bundleDir != null)
Framework.deleteDirectory(bundleDir);
throw e1;
} finally {
BundleLock.WriteUnLock(location);
if (bundleDir != null) {
AtlasFileLock.getInstance().unLock(bundleDir);
}
}
}
static boolean restoreBundle(String[] locations) {
try {
for (String location : locations) {
if (isKernalBundle(location)) {
final File bundleDir = new File(STORAGE_LOCATION, "com.taobao.maindex");
if (!bundleDir.exists()) {
return false;
} else {
Class KernalBundleClass = RuntimeVariables.getRawClassLoader().loadClass("android.taobao.atlas.startup.patch.KernalBundle");
Method downgradeRevision = KernalBundleClass.getDeclaredMethod("downgradeRevision",File.class,boolean.class);
boolean success = (Boolean)downgradeRevision.invoke(KernalBundleClass,bundleDir,false);
if (!success) {
return false;
}
}
} else {
final File bundleDir = new File(STORAGE_LOCATION, location);
if (!bundleDir.exists()) {
return false;
} else {
if (!BundleArchive.downgradeRevision(location, bundleDir, false)) {
return false;
}
}
}
}
} catch (Exception e) {
return false;
}
return true;
}
/**
* boolean lockSuccess = false;
try {
lockSuccess = BundleLock.ReadLock(location);
if (location == null){
return null;
}
Bundle bundle = bundles.get(location);
if(bundle==null) {
bundle = restoreFromExistedBundle(location);
}
return bundle;
}finally {
if(lockSuccess) {
try {
BundleLock.ReadUnLock(location);
}catch(Throwable e){}
}
}
* @param location
* @return
*/
public static BundleImpl restoreFromExistedBundle(final String location) {
boolean lockSuccess = false;
File bundleDir = new File(STORAGE_LOCATION, location);
BundleImpl bundle = null;
// just restore
if(bundleDir.exists() && new File(bundleDir,"meta").exists()) {
try {
lockSuccess = BundleLock.ReadLock(location);
AtlasFileLock.getInstance().LockExclusive(bundleDir);
BundleContext impl = new BundleContext();
if(BaselineInfoManager.instance().dexPatchVersion()>0 && BaselineInfoManager.instance().isDexPatched(location)) {
impl.dexPatchVersion = BaselineInfoManager.instance().dexPatchVersion();
bundle = new BundleImpl(bundleDir, impl);
if (bundle != null) {
bundle.optDexFile();
}
}else{
String[] versions = bundleDir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
return filename.startsWith(BundleArchive.REVISION_DIRECTORY);
}
});
if(versions!=null && versions.length>0){
bundle = new BundleImpl(bundleDir, impl);
if (bundle != null) {
bundle.optDexFile();
}
}
}
} catch (Exception e) {
if (e instanceof BundleArchive.MisMatchException) {
if (bundleDir.exists()) {
bundle = null;
}
}
// AtlasMonitor.getInstance().trace(AtlasMonitor.BUNDLE_INSTALL_FAIL,
// location, AtlasMonitor.RESTORED_FAILED_MSG, FileUtils.getDataAvailableSpace());
Log.e("Framework","restore bundle failed" + location, e);
}finally {
if(lockSuccess) {
try {
BundleLock.ReadUnLock(location);
}catch(Throwable e){}
}
AtlasFileLock.getInstance().unLock(bundleDir);
}
}
return bundle;
}
static void installOrUpdate(final String[] locations, final File[] files, String[] newBundleVersions,long dexPatchVersion) throws BundleException {
if (locations == null || files == null || locations.length != files.length) {
throw new IllegalArgumentException("locations and files must not be null and must be same length");
}
String writeAhead = String.valueOf(System.currentTimeMillis());
File walsDir = new File(STORAGE_LOCATION, "wal");
File walDir = new File(walsDir, writeAhead);
walDir.mkdirs();
Class KernalBundleClass = null;
Object kernalBundle = null;
for (int i = 0; i < locations.length; i++) {
if (locations[i] == null || files[i] == null) {
continue;
}
File bundleDir = null;
try {
BundleLock.WriteLock(locations[i]);
if (isKernalBundle(locations[i])) {
KernalBundleClass = RuntimeVariables.getRawClassLoader().loadClass("android.taobao.atlas.startup.patch.KernalBundle");
kernalBundle = KernalBundleClass.getDeclaredField("kernalBundle").get(KernalBundleClass);
if (kernalBundle != null) {
Method updateMethod = KernalBundleClass.getMethod("update",File.class,String.class,long.class);
updateMethod.setAccessible(true);
updateMethod.invoke(kernalBundle,files[i],Framework.containerVersion,dexPatchVersion);
// bundle.update();
} else {
bundleDir = new File(STORAGE_LOCATION, "com.taobao.maindex");
if (!bundleDir.exists()){
bundleDir.mkdirs();
}
AtlasFileLock.getInstance().LockExclusive(bundleDir);
Constructor cons = KernalBundleClass.getDeclaredConstructor(File.class,File.class,String.class,long.class);
cons.setAccessible(true);
Object b = cons.newInstance(bundleDir, files[i],Framework.containerVersion,dexPatchVersion);
// KernalBundle b = new KernalBundle(bundleDir, files[i],Framework.containerVersion,dexPatchVersion);
if (b != null) {
Method getRevisionDir = b.getClass().getDeclaredMethod("getRevisionDir");
File file = (File) getRevisionDir.invoke(b);
FileUtils.createNewDirIfNotExist(file, UPDATED_MARK);
}
}
} else {
Bundle bundle = Framework.getBundle(locations[i]);
if (bundle != null) {
bundle.update(files[i],newBundleVersions[i],dexPatchVersion);
} else {
if(dexPatchVersion>0){
bundleDir = new File(STORAGE_LOCATION, locations[i]);
}else {
bundleDir = new File(walDir, locations[i]);
}
if (!bundleDir.exists()) {
bundleDir.mkdirs();
}
// Hold the storage file lock
AtlasFileLock.getInstance().LockExclusive(bundleDir);
BundleImpl b = new BundleImpl(bundleDir, locations[i], new BundleContext(), null, files[i],newBundleVersions[i], false,dexPatchVersion);
if (b != null) {
FileUtils.createNewDirIfNotExist(b.archive.getCurrentRevision().getRevisionDir(), UPDATED_MARK);
}
}
}
} catch (Exception e) {
// AtlasMonitor.getInstance().trace(AtlasMonitor.BUNDLE_INSTALL_FAIL,
// locations[i], AtlasMonitor.UPDATE_FAILED_MSG, FileUtils.getDataAvailableSpace());
/**
* bundle 安装需要支持事务,失败时回滚已部署的bundle
*/
for (int x = 0; x <= i; x++) {
if (isKernalBundle(locations[x])) {
if (kernalBundle != null) {
try {
Method downgradeRevision = kernalBundle.getClass().getDeclaredMethod("downgradeRevision",File.class,boolean.class);
downgradeRevision.setAccessible(true);
downgradeRevision.invoke(KernalBundleClass,new File(STORAGE_LOCATION, "com.taobao.maindex"), true);
// KernalBundle.downgradeRevision(new File(STORAGE_LOCATION, "com.taobao.maindex"), true);
} catch (Throwable e2) {
}
} else {
deleteDirectory(new File(STORAGE_LOCATION, "com.taobao.maindex"));
}
} else {
try {
BundleArchive.downgradeRevision(locations[x], new File(STORAGE_LOCATION, locations[x]), true);
} catch (IOException e1) {
e1.printStackTrace();
}
deleteDirectory(walDir);
if (walDir.exists()) {
try {
new File(walDir, DEPRECATED_MARK).createNewFile();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
throw new BundleException("failed to installOrUpdate bundles ", e);
} finally {
if (bundleDir != null) {
AtlasFileLock.getInstance().unLock(bundleDir);
}
BundleLock.WriteUnLock(locations[i]);
}
}
bundleUpdated = true;
WrapperUtil.appendLog("installedVersionWhenUpdated", Framework.containerVersion);
WrapperUtil.appendLog("VersionWhenUpdated", WrapperUtil.getPackageInfo(RuntimeVariables.androidApplication).versionName);
writeAheads.add(writeAhead);
InstrumentationHook.notifyAppUpdated();
storeMetadata();
}
static boolean isKernalBundle(String location) {
if (TextUtils.isEmpty(location)) {
return false;
}
return location.equals("com.taobao.maindex");
}
/**
* notify all bundle listeners.
*
* @param state the new state.
* @param bundle the bundle.
*/
static void notifyBundleListeners(final int state, final Bundle bundle) {
if (syncBundleListeners.isEmpty() && bundleListeners.isEmpty()) {
return;
}
final BundleEvent event = new BundleEvent(state, bundle);
// inform the synchrounous bundle listeners first ...
final BundleListener[] syncs = (BundleListener[]) syncBundleListeners.toArray(new BundleListener[syncBundleListeners.size()]);
for (int i = 0; i < syncs.length; i++) {
syncs[i].bundleChanged(event);
}
if (bundleListeners.isEmpty()) {
return;
}
final BundleListener[] asyncs = (BundleListener[]) bundleListeners.toArray(new BundleListener[bundleListeners.size()]);
for (int i = 0; i < asyncs.length; i++) {
asyncs[i].bundleChanged(event);
}
}
public static void addFrameworkListener(FrameworkListener listener) {
frameworkListeners.add(listener);
}
public static void removeFrameworkListener(FrameworkListener listener) {
frameworkListeners.remove(listener);
}
static void addBundleListener(BundleListener listener) {
bundleListeners.add(listener);
}
static void removeBundleListener(BundleListener listener) {
bundleListeners.remove(listener);
}
/**
* notify all framework listeners.
*
* @param state the new state.
* @param bundle the bundle.
* @param throwable a throwable.
*/
static void notifyFrameworkListeners(final int state, final Bundle bundle, final Throwable throwable) {
if (frameworkListeners.isEmpty()) {
return;
}
final FrameworkEvent event = new FrameworkEvent(state, bundle, throwable);
final FrameworkListener[] listeners = (FrameworkListener[]) frameworkListeners.toArray(new FrameworkListener[frameworkListeners.size()]);
for (int i = 0; i < listeners.length; i++) {
final FrameworkListener listener = listeners[i];
listener.frameworkEvent(event);
}
}
/**
* clear all traces of a bundle.
*
* @param bundle the bundle.
*/
static void clearBundleTrace(final BundleImpl bundle) {
// remove all registered listeners
if (bundle.registeredFrameworkListeners != null) {
frameworkListeners.removeAll(bundle.registeredFrameworkListeners);
bundle.registeredFrameworkListeners = null;
}
if (bundle.registeredBundleListeners != null) {
bundleListeners.removeAll(bundle.registeredBundleListeners);
syncBundleListeners.removeAll(bundle.registeredBundleListeners);
bundle.registeredBundleListeners = null;
}
}
/**
* add a value to a value list in a Map.
*
* @param map the map.
* @param key the key.
* @param value the value to be added to the list.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
static void addValue(final Map map, final Object key, final Object value) {
List values;
if ((values = (List) map.get(key)) == null) {
values = new ArrayList<Bundle>();
}
values.add(value);
map.put(key, values);
}
/*
* inner classes
*/
/**
* The systemBundle.
*/
private static final class SystemBundle implements Bundle {
/**
* the state
*/
int state;
/**
* the properties.
*/
private final Dictionary<String, String> props = new Hashtable<String, String>();
/**
* create the system bundle instance.
*/
SystemBundle() {
props.put(Constants.BUNDLE_NAME, Constants.SYSTEM_BUNDLE_LOCATION);
props.put(Constants.BUNDLE_VERSION, FRAMEWORK_VERSION);
props.put(Constants.BUNDLE_VENDOR, "Atlas");
}
/**
* get the bundle id.
*
* @return 0.
* @category Bundle
* @see org.osgi.framework.Bundle#getBundleId()
*/
public long getBundleId() {
return 0;
}
/**
* get the properties.
*
* @return the properties.
* @category Bundle
* @see org.osgi.framework.Bundle#getHeaders()
*/
public Dictionary<String, String> getHeaders() {
return props;
}
/**
* get the location.
*
* @return "System Bundle"
* @category Bundle
* @see org.osgi.framework.Bundle#getLocation()
*/
public String getLocation() {
return Constants.SYSTEM_BUNDLE_LOCATION;
}
/**
* get resources.
*
* @param name the name.
* @return the URL or null.
* @category Bundle
* @see org.osgi.framework.Bundle#getResource(java.lang.String)
*/
public URL getResource(final String name) {
return getClass().getResource(name);
}
/**
* get the state.
*
* @return the state.
* @category Bundle
* @see org.osgi.framework.Bundle#getState()
*/
public int getState() {
return state;
}
/**
* check if some permissions are granted.
*
* @param permission the permissions.
* @return true, if the permissions hold.
* @category Bundle
* @see org.osgi.framework.Bundle#hasPermission(java.lang.Object)
*/
public boolean hasPermission(final Object permission) {
return true;
}
/**
* start the system bundle.
*
* @throws BundleException never.
* @category Bundle
* @see org.osgi.framework.Bundle#start()
*/
public void start() throws BundleException {
// this method has no effect
}
/**
* stopping the system bundle means shutting down the framework.
*
* @throws BundleException never.
* @category Bundle
* @see org.osgi.framework.Bundle#stop()
*/
public void stop() throws BundleException {
}
/**
* the system bundle cannot be uninstalled.
*
* @throws BundleException always.
* @category Bundle
* @see org.osgi.framework.Bundle#uninstall()
*/
public void uninstall() throws BundleException {
throw new BundleException("Cannot uninstall the System Bundle");
}
public void update(final File in,String version,long dexPatchVersion) throws BundleException {
throw new BundleException("Cannot update the System Bundle");
}
/**
* get a string representation.
*
* @return the string representation.
* @category Object
* @see java.lang.Object#toString()
*/
public String toString() {
return "SystemBundle";
}
}
public static ClassNotFoundInterceptorCallback getClassNotFoundCallback() {
return classNotFoundCallback;
}
public static void setClassNotFoundCallback(ClassNotFoundInterceptorCallback classNotFoundCallback) {
Framework.classNotFoundCallback = classNotFoundCallback;
}
/**
* for debug not release
*/
static void checkInstallDebugBundle() {
File debugDirectory = new File(ATLAS_DEBUG_DIRECTORY);
if (debugDirectory.exists()) {
File[] bundles = debugDirectory.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
if (filename.endsWith(".so")) {
return true;
}
return false;
}
});
if (bundles != null) {
String[] packageNames = new String[bundles.length];
String[] versions = new String[bundles.length];
for (int x = 0; x < bundles.length; x++) {
versions[x] = "1.0.0";
if (bundles[x].getName().startsWith("kernal") || bundles[x].getName().contains("com_taobao_mainDex")) {
//主dexbundle
packageNames[x] = "com.taobao.maindex";
} else {
//业务bundle
PackageInfo info = RuntimeVariables.androidApplication.getPackageManager().getPackageArchiveInfo(bundles[x].getAbsolutePath(), 0);
if (info != null) {
packageNames[x] = info.applicationInfo.packageName;
} else {
String fileName = bundles[x].getName();
fileName = fileName.substring(3, fileName.length() - 3);
packageNames[x] = fileName.replace("_", ".");
}
}
}
try {
Atlas.getInstance().installOrUpdate(packageNames, bundles,versions,-1);
Log.d("Framework", "patch success and delete file");
try {
for (File installFile : bundles) {
if (installFile.exists()) {
installFile.delete();
}
}
} catch (Exception e) {
}
} catch (BundleException e) {
e.printStackTrace();
}
}
}
}
/**
* 存在隐患 storelocation后期被更改
*/
public static File getInstalledBundle(String location,String bundleVersion) {
try {
if (new File(STORAGE_LOCATION + location).exists()) {
BundleArchive archive = new BundleArchive(location, new File(STORAGE_LOCATION + location),0,0);
String version = archive.getCurrentRevision().getVersion();
if(TextUtils.isEmpty(bundleVersion) || version.equals(bundleVersion)) {
return archive.getArchiveFile();
}else{
return null;
}
}
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static boolean isUpdated() {
return bundleUpdated;
}
public static String getContainerVersion() {
return containerVersion;
}
public static boolean isDeubgMode() {
try {
/**
* enable patch debug if in debug mode
*/
final ApplicationInfo app_info = RuntimeVariables.androidApplication.getApplicationInfo();
boolean DEBUG = (app_info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
if (DEBUG) {
return true;
}
} catch (final Exception e) {
return false;
}
return false;
}
public static boolean shouldSyncUpdateInThisProcess(){
String processName = getCurProcessName();
if(processName!=null &&
(processName.equals(RuntimeVariables.androidApplication.getPackageName()) ||
processName.toLowerCase().contains(":safemode")
)){
return true;
}else{
return false;
}
}
}