/*
* Copyright 2014-present Facebook, 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.facebook.buck.android.support.exopackage;
import java.lang.reflect.Constructor;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
/**
* A base class for implementing an Application that delegates to an {@link ApplicationLike}
* instance. This is used in conjunction with secondary dex files so that the logic that would
* normally live in the Application class is loaded after the secondary dexes are loaded.
*/
public abstract class ExopackageApplication<T extends ApplicationLike> extends Application {
private static final int SECONDARY_DEX_MASK = 1;
private static final int NATIVE_LIBRARY_MASK = 2;
private static final int RESOURCES_MASK = 4;
private final String delegateClassName;
private final int exopackageFlags;
private T delegate;
/**
* @param exopackageFlags Bitmask used to determine which exopackage feature is enabled in the
* current build. This should usually be {@code BuildConfig.EXOPACKAGE_FLAGS}.
*/
protected ExopackageApplication(int exopackageFlags) {
this(DefaultApplicationLike.class.getName(), exopackageFlags);
}
/**
* @param delegateClassName The fully-qualified name of the {@link ApplicationLike} class
* that will act as the delegate for application lifecycle callbacks.
* @param exopackageFlags Bitmask used to determine which exopackage feature is enabled in the
* current build. This should usually be {@code BuildConfig.EXOPACKAGE_FLAGS}.
*/
protected ExopackageApplication(
String delegateClassName,
int exopackageFlags) {
this.delegateClassName = delegateClassName;
this.exopackageFlags = exopackageFlags;
}
private boolean isExopackageEnabledForSecodaryDex() {
return (exopackageFlags & SECONDARY_DEX_MASK) != 0;
}
private boolean isExopackageEnabledForNativeLibraries() {
return (exopackageFlags & NATIVE_LIBRARY_MASK) != 0;
}
private boolean isExopackageEnabledForResources() {
return (exopackageFlags & RESOURCES_MASK) != 0;
}
private T createDelegate() {
if (isExopackageEnabledForSecodaryDex()) {
ExopackageDexLoader.loadExopackageJars(this);
}
if (isExopackageEnabledForNativeLibraries()) {
ExopackageSoLoader.init(this);
}
if (isExopackageEnabledForResources()) {
ResourcesLoader.init(this);
}
try {
// Use reflection to create the delegate so it doesn't need to go into the primary dex.
Class<T> implClass = (Class<T>) Class.forName(delegateClassName);
Constructor<T> constructor = implClass.getConstructor(Application.class);
return constructor.newInstance(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private synchronized void ensureDelegate() {
if (delegate == null) {
delegate = createDelegate();
}
}
/**
* Hook for sub-classes to run logic after the {@link Application#attachBaseContext} has been
* called but before the delegate is created. Implementors should be very careful what they do
* here since {@link android.app.Application#onCreate} will not have yet been called.
*/
protected void onBaseContextAttached() {
}
/**
* @return the delegate, or {@code null} if not set up.
*/
// @Nullable - Don't want to force a reference to that annotation in the primary dex.
public final T getDelegateIfPresent() {
return delegate;
}
@Override
protected final void attachBaseContext(Context base) {
super.attachBaseContext(base);
onBaseContextAttached();
ensureDelegate();
}
@Override
public final void onCreate() {
super.onCreate();
ensureDelegate();
delegate.onCreate();
}
@Override
public final void onTerminate() {
super.onTerminate();
if (delegate != null) {
delegate.onTerminate();
}
}
@Override
public final void onLowMemory() {
super.onLowMemory();
if (delegate != null) {
delegate.onLowMemory();
}
}
@TargetApi(14)
@Override
public final void onTrimMemory(int level) {
super.onTrimMemory(level);
if (delegate != null) {
delegate.onTrimMemory(level);
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (delegate != null) {
delegate.onConfigurationChanged(newConfig);
}
}
@Override
public Object getSystemService(String name) {
if (delegate != null) {
Object service = delegate.getSystemService(name);
if (service != null) {
return service;
}
}
return super.getSystemService(name);
}
}