How to shrink resources and remove unused language resources with Android.mk - proguard

I am working on AOSP Settings application which builds properly.
I am trying to build "user build" and have enabled proguard using proguard.flags and disabled jack compilation
ANDROID_COMPILE_WITH_JACK := false
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
My question is how can I shrink resources and strip/remove unneeded language string resources that we generally do in build.gradle using Android.mk file
android {
buildTypes {
release {
// Enables code shrinking, obfuscation, and optimization for only
// your project's release build type.
minifyEnabled true
// Enables resource shrinking, which is performed by the
// Android Gradle plugin.
shrinkResources true
}
}
}
And to keep only US and Indian English
android {
defaultConfig {
resConfigs "en_US", "en_IN"
}
}
My Android.mk file
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
$(call all-logtags-files-under, src)
LOCAL_MODULE := settings-logtags
include $(BUILD_STATIC_JAVA_LIBRARY)
# Build the Settings APK
include $(CLEAR_VARS)
LOCAL_PACKAGE_NAME := Settings
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform
LOCAL_PRODUCT_MODULE := true
LOCAL_PRIVILEGED_MODULE := true
LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.settings
LOCAL_MODULE_TAGS := optional
LOCAL_USE_AAPT2 := true
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx-constraintlayout_constraintlayout \
androidx.slice_slice-builders \
androidx.slice_slice-core \
androidx.slice_slice-view \
androidx.core_core \
androidx.appcompat_appcompat \
androidx.cardview_cardview \
androidx.preference_preference \
androidx.recyclerview_recyclerview \
com.google.android.material_material \
setupcompat \
setupdesign
LOCAL_JAVA_LIBRARIES := \
telephony-common \
ims-common
LOCAL_STATIC_JAVA_LIBRARIES := \
androidx-constraintlayout_constraintlayout-solver \
androidx.lifecycle_lifecycle-runtime \
androidx.lifecycle_lifecycle-extensions \
guava \
jsr305 \
settings-contextual-card-protos-lite \
settings-log-bridge-protos-lite \
contextualcards \
settings-logtags \
zxing-core-1.7
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
ANDROID_COMPILE_WITH_JACK := false
include frameworks/base/packages/SettingsLib/common.mk
include frameworks/base/packages/SettingsLib/search/common.mk
include $(BUILD_PACKAGE)
# ==== prebuilt library ========================
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
contextualcards:libs/contextualcards.aar
include $(BUILD_MULTI_PREBUILT)
# Use the following include to make our test apk.
ifeq (,$(ONE_SHOT_MAKEFILE))
include $(call all-makefiles-under,$(LOCAL_PATH))
endif
My proguard.flags file
# Some tests use thenThrow from Mockito which require information on
# checked exceptions.
-keepattributes Exceptions
# Keep all Fragments in this package, which are used by reflection.
-keep public class com.android.settings.** extends androidx.fragment.app.Fragment
# Keep all preference controllers needed by slice and DashboardFragment.
-keep class * extends com.android.settings.core.BasePreferenceController {
*;
}
-keep class * extends com.android.settings.core.TogglePreferenceController {
*;
}
# We want to keep methods in Activity that could be used in the XML attribute onClick.
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
public void *(android.view.MenuItem);
}
# Keep setters in Views so that animations can still work.
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
void set*(***);
*** get*();
}
# Keep classes that may be inflated from XML.
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int, int);
}
# Keep annotated classes or class members.
-keep #androidx.annotation.Keep class *
-keepclassmembers class * {
#androidx.annotation.Keep *;
}
# Keep specific fields used via reflection.
-keepclassmembers class * {
public static ** SEARCH_INDEX_DATA_PROVIDER;
public static ** SUMMARY_PROVIDER_FACTORY;
}
-keep class androidx.core.app.CoreComponentFactory
# Keep classes that implements CustomSliceable, which are used by reflection.
-keepclasseswithmembers class * implements com.android.settings.slices.CustomSliceable {
public <init>(android.content.Context);
}
# Keep classes that extends SliceBackgroundWorker, which are used by reflection.
-keepclasseswithmembers class * extends com.android.settings.slices.SliceBackgroundWorker {
public <init>(android.content.Context, android.net.Uri);
}
documentation of
Android.mk
Application.mk
Proguard

You cannot set for one app, but you can set PRODUCT_LOCALES in device or product level, which will take effect for all apps built with aosp.
Take the following as example, it will only include en_US and en_IN.
PRODUCT_LOCALES := en_US en_IN

Related

Liferay 7.2 api json url 404

I have:
Eclispse
Version: 2019-06 (4.12.0)
Build id: 20190614-1200
Liferay 7.2.0 CE GA1.
I use Gradle.
I followed this tutorial:
https://www.liferaystack.com/2017/11/rest-extender-and-jax-rs-restful-web-service-in-liferay-7-dxp.html
I created a rest module.
I created two file of configuration inside the folder "src/main/resources/configuration":
com.liferay.portal.remote.cxf.common.configuration.CXFEndpointPublisherConfiguration-cxf.properties
Code:
contextPath=/my-rest-service
authVerifierProperties=auth.verifier.BasicAuthHeaderAuthVerifier.urls.includes=*
com.liferay.portal.remote.rest.extender.configuration.RestExtenderConfiguration-rest.properties
Code:
contextPaths=/my-rest-service
jaxRsServiceFilterStrings=(component.name=com.liferaystack.application.MyRestServiceApplication)
This is the MyRestWebserviceApplication.java:
package com.liferaystack.application;
import java.util.Collections;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Application;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
/**
* #author pinoteam
*/
#ApplicationPath("/my-rest-service")
#Component(immediate = true, service = Application.class)
public class MyRestWebserviceApplication extends Application {
public Set<Object> getSingletons() {
return Collections.<Object>singleton(this);
}
#GET
#Produces("text/plain")
public String working() {
return "It works!";
}
#GET
#Path("/morning")
#Produces("text/plain")
public String hello() {
return "Good morning!";
}
#GET
#Path("/mattina")
#Produces("text/plain")
public String helloGa() {
return "Good morning!";
}
#GET
#Path("/morning/{name}")
#Produces("text/plain")
public String morning(
#PathParam("name") String name,
#QueryParam("drink") String drink) {
String greeting = "Good Morning " + name;
if (drink != null) {
greeting += ". Would you like some " + drink + "?";
}
return greeting;
}
}
I run build and deploy.
The app is active, but nothing work.
In control panel i have not any Rest Extender Configuration and i have not api at any url.
what am i missing? any idea?
The tutorial you link is for Liferay 7.0 - the package/path names of the configuration files might have changed, and I've seen a context path being declared in bnd.bnd, rather than those property files - things might have changed since then.
An alternative starting point might be the blade sample, which is written for 7.2, and provides a Whiteboard-properties in the Java class:
#Component(
property = {
JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE + "=/users",
JaxrsWhiteboardConstants.JAX_RS_NAME + "=Users"
},
service = Application.class
)
public class UsersRestService extends Application {
#GET
#Path("/list")
#Produces("text/plain")
public String getUsers() {
...
}
...
That plugin's configuration file is rather trivial (looks optional on first glance, but don't take my word for it)

Class pointcut that intercepts a method call with field access

I am writing some aspects to make my gradle pugin development a little cleaner. In gradle, there is an interface, like this
interface Plugin {
def apply(Project project);
}
Applied to a plugin
class MyPlugin implements Plugin<Project> {
def apply(Project project) {
do stuff
}
}
Now, i want to be able to annotate this class like this
#OnlyAllowedOnRoot
class MyPlugin implements Plugin<Project> {
def apply(Project project) {
do stuff
}
}
and have a pointcut that fires when the 'apply' method is fired, and pick up the parameter. because the logic for the pointcut would be
if (project.rootProject.name != project.name) {
throw new GradleScriptExeption("This plugin can only be applied to root")
}
how would I do this? This example is the foundation for about a dozen other pointcuts i would want to write, but i really don't know where to start. I know i can annotate the apply method directly, but im worried about readability, which is mostly the reason why im doing this to begin with. I can if i have to, but id rather not. And because of the lifecycle of Gradle, it must be checked when the 'apply' method is called, it can't be checked at the instantiation.
Here is a complete example, but in Java, not in Groovy. It should not make any difference if you use Groovy, though. BTW, I did not want to add the Gradle API as a dependency to my project, so I just replicated the relevant parts of its API with the right package names and signatures. Not being a Gradle user myself, I implemented the root project property hierarchically as a direct parent, not as an absolute root. If Gradle does it differently, just adjust the condition in the aspect throwing the exception back to your own sample code.
Gradle API:
package org.gradle.api;
public class Project {
private String name;
private Project rootProject;
public Project(String name, Project rootProject) {
this.name = name;
this.rootProject = rootProject;
}
public String getName() {
return name;
}
public Project getRootProject() {
return rootProject;
}
#Override
public String toString() {
return "Project(name = " + name + ", rootProject = " + rootProject + ")";
}
}
package org.gradle.api;
public interface Plugin<T> {
void apply(T target);
}
package org.gradle.api;
public class GradleScriptExeption extends RuntimeException {
private static final long serialVersionUID = 1L;
public GradleScriptExeption(String message, Throwable cause) {
super(message, cause);
}
}
Marker annotation + plugins:
package de.scrum_master.app;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
class NormalPlugin implements Plugin<Project> {
public void apply(Project project) {}
}
package de.scrum_master.app;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
#Retention(RUNTIME)
public #interface OnlyAllowedOnRoot {}
package de.scrum_master.app;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
#OnlyAllowedOnRoot
class RootPlugin implements Plugin<Project> {
public void apply(Project project) {}
}
Driver application:
package de.scrum_master.app;
import org.gradle.api.Project;
public class Application {
public static void main(String[] args) {
Project rootProject = new Project("root", null);
Project childProject = new Project("child", rootProject);
Project grandChildProject = new Project("grandchild", childProject);
NormalPlugin normalPlugin = new NormalPlugin();
normalPlugin.apply(rootProject);
normalPlugin.apply(childProject);
normalPlugin.apply(grandChildProject);
RootPlugin rootPlugin = new RootPlugin();
rootPlugin.apply(rootProject);
rootPlugin.apply(childProject);
rootPlugin.apply(grandChildProject);
}
}
Aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.gradle.api.GradleScriptExeption;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
#Aspect
public class GradlePluginAspect {
#Pointcut("execution(void apply(*)) && target(plugin) && args(project)")
private static void pluginExecution(Plugin plugin, Project project) {}
#Before("pluginExecution(plugin, project) && #target(de.scrum_master.app.OnlyAllowedOnRoot)")
public void illegalRootPlugin(JoinPoint thisJoinPoint, Plugin plugin, Project project) {
if (project.getRootProject() != null)
throw new GradleScriptExeption("Cannot apply " + plugin.getClass().getSimpleName() + " to non-root project " + project, null);
}
#Before("pluginExecution(plugin, project)")
public void logPluginApply(JoinPoint thisJoinPoint, Plugin plugin, Project project) {
System.out.println("Applying " + plugin.getClass().getSimpleName() + " to " + project);
}
}
Console log:
Applying NormalPlugin to Project(name = root, rootProject = null)
Applying NormalPlugin to Project(name = child, rootProject = Project(name = root, rootProject = null))
Applying NormalPlugin to Project(name = grandchild, rootProject = Project(name = child, rootProject = Project(name = root, rootProject = null)))
Applying RootPlugin to Project(name = root, rootProject = null)
Exception in thread "main" org.gradle.api.GradleScriptExeption: Cannot apply RootPlugin to non-root project Project(name = child, rootProject = Project(name = root, rootProject = null))
at de.scrum_master.aspect.GradlePluginAspect.illegalRootPlugin(GradlePluginAspect.aj:19)
at de.scrum_master.app.RootPlugin.apply(RootPlugin.java:8)
at de.scrum_master.app.Application.main(Application.java:18)

Adding version info to jar file name using IntelliJ

I'm used to Android Studio and developing Android projects.
In Android Studio I put this in the build.gradle file:
defaultConfig {
applicationId "com.domain.myapp"
minSdkVersion 19
targetSdkVersion 19
versionCode 1
versionName "1.0"
setProperty("archivesBaseName", "myapp.$versionName.$versionCode")
signingConfig signingConfigs.config
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
applicationVariants.all { variant ->
variant.outputs.each { output ->
def newName = output.outputFile.name
newName = newName.replace("-release", "")
output.outputFile = new File(output.outputFile.parent, newName)
}
}
signingConfig signingConfigs.config
}
debug {
signingConfig signingConfigs.config
}
}
When I build I get myapp.1.0.1.apk, works wonderfully.
Now I am developing a java project .jar using IntelliJ, NOT Android Studio.
How can I accomplish the same thing? I'm finding sparse information ...
Android does this by adding a task that build the R.java file. As simple as Android makes it look replicating the behavior takes a little effort. You can create your own gradle task to accomplish the same. You will need a gradle plugin that creates and extension and a task. The extension will be used to track values added in the build.gradle
Plugin should create extensions and tasks
// create DSL model
target.extensions.create('buildConfig', BuildConfigModel)
// create task to generate file
def buildConfigTask = target.tasks.create('generateBuildConfig', BuildConfigTask)
target.tasks.findByName('clean').configure {
delete new File("$project.projectDir/src/main", "generated-sources")
}
// this task must always run... it's never `upToDate`
buildConfigTask.outputs.upToDateWhen { false }
// java compiler depends on this task... allows us to reference generated code from
// human written code
target.tasks.getByName('compileJava').dependsOn buildConfigTask
Here is how you can use your task to add a generated sources file
project.configure(project, { Project configuredProject ->
// compilerJava task {#link JavaCompile}
def compileJava = configuredProject.compileJava
// add the created java file to source path so it gets compiled
compileJava.source(project.buildConfig.createBuildConfig(project, compileJava))
})
Then our extension would looks something like this
package com.jbirdvegas.q41680813;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.tasks.compile.JavaCompile;
import javax.lang.model.element.Modifier;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Handles creating the BuildConfig.java from a module project
* <p>
* Warning... Keep this in java, gradle doesn't have the streams api we are using
*/
public class BuildConfigModel {
/**
* Final java file output path pattern for {#link String#format(String, Object...) String#format} formatting of
* the file's path. Directory structure will be created if needed
*/
private static final String OUTPUT_PATH_FORMAT = "%s/src/main/generated-sources/%s/%s/BuildConfig.java";
/**
* List of DSL supplied {#link BuildValue buildConfig#add}
*/
private List<BuildValue> mBuildValues = new ArrayList<>();
/**
* Required... do not remove
*/
public BuildConfigModel() {
}
/**
* Create a new field to the project's generated `BuildConfig.java`'s inner class for each type
*
* #param clazz Type of value to be written (will be grouped by clazz)
* #param name field name to be created
* #param value value to be assigned to field's name
*/
#SuppressWarnings({"unused", "WeakerAccess"})
public void add(Class clazz, String name, Object value) {
mBuildValues.add(new BuildValue(clazz, name, value));
}
/**
* Create `BuildConfig.java` and add it to the {#link JavaCompile#source(Object...)} compileJava#source(Object...)}
*
* #param project module to generate BuildConfig for
* #param javaCompile project's `compileJava` task
* #return generated `BuildConfig.java` file
*/
public File createBuildConfig(Project project, JavaCompile javaCompile) {
File buildConfigFile = getBuildConfigFile(project);
createJavaClass(project, buildConfigFile);
javaCompile.source(buildConfigFile);
return buildConfigFile;
}
/**
* Destination file for given project's `BuildConfig.java`
*
* #param project module to generate BuildConfig for
* #return File representing the destination of the created `BuildConfig.java`
*/
#SuppressWarnings("WeakerAccess")
public File getBuildConfigFile(Project project) {
return project.file(String.format(OUTPUT_PATH_FORMAT,
project.getProjectDir().getAbsolutePath(),
project.getGroup().toString().replaceAll("\\.", "/"),
project.getName()));
}
/**
* Create `BuildConfig.java` with a few default values and any values supplied
* to the `buildConfig`'s {#link #add(Class, String, Object) add} method.
* <p>
* Default BuildConfig fields will be generated by {#link #getDefaultFields}
* <p>
* Fields added via {#link #add(Class, String, Object) add} method will be grouped into inner classes
* named <pre>{#code Class#getSimpleName().toLowerCase() + "s"}</pre>
*
* #param project module to generate BuildConfig for
* #param buildConfigFile File representing the destination of the BuildConfig.java output
*/
#SuppressWarnings("WeakerAccess")
public void createJavaClass(Project project, File buildConfigFile) {
//noinspection unchecked
Collections.sort(mBuildValues);
// group our configs by their types into a map of groups
Map<Class, List<BuildValue>> groupedConfigs = mBuildValues.stream()
// put the values in groups based on the simple name of the class in lowercase
.collect(Collectors.groupingBy(BuildValue::getValueType));
// create the fully qualified class that will contain our build settings
TypeSpec.Builder buildConfigJavaBuilder = TypeSpec.classBuilder("BuildConfig")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
// note for javadoc
.addJavadoc("$S\n", "DO NOT MODIFY; this class is written automatically by the compiler")
// replace public constructor with private
.addMethod(createPrivateConstructor());
// add any fields that will be in all BuildConfig classes
buildConfigJavaBuilder.addFields(getDefaultFields(project));
groupedConfigs.forEach((aClass, buildValues) -> {
// create the inner class
String safeInnerClassName = aClass.getSimpleName().toLowerCase() + 's';
TypeSpec.Builder innerClass = TypeSpec.classBuilder(safeInnerClassName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
// make a constructor that's private since all the members of this class are public static final
.addMethod(createPrivateConstructor());
// for each inner class type create a field
// each object type gets it's own inner class
//noinspection SimplifyStreamApiCallChains
buildValues.stream().forEachOrdered(buildValue -> {
// create the requested field in the class
FieldSpec fieldSpec = FieldSpec.builder(buildValue.clazz, buildValue.name)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer(CodeBlock.of(getStringFormatter(buildValue.clazz), buildValue.value))
.build();
// add the field to the inner class
innerClass.addField(fieldSpec);
});
// add the inner class to the fully qualified class
buildConfigJavaBuilder.addType(innerClass.build());
});
// determine the package name from project.group + '.' + project.name
String packageName = project.getGroup() + "." + project.getName();
// create a java file writer
JavaFile.Builder builder = JavaFile.builder(packageName, buildConfigJavaBuilder.build());
// don't import java.lang.* it's redundant
builder.skipJavaLangImports(true);
// use four spaces for indent instead of default two spaces
builder.indent(" ");
// create the file in memory
JavaFile javaFile = builder.build();
// ensure file structure
if (!buildConfigFile.getParentFile().exists() && !buildConfigFile.getParentFile().mkdirs()) {
throw new GradleException("Failed to create directory structure for " + buildConfigFile.getAbsolutePath());
}
// write BuildConfig.java to location
try (FileWriter writer = new FileWriter(buildConfigFile)) {
javaFile.writeTo(writer);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Strings require being treated specially in order to be encapsulated in quotes correctly
* All other classes are treated as literals... We may want to handle more {#link java.lang.reflect.Type Type}
*
* #param clazz Class formatter is needed for
* #return "$S" if the class is a {#link String} else a literal formatter is returned "$L"
*/
private String getStringFormatter(Class clazz) {
switch (clazz.getSimpleName().toLowerCase()) {
case "string":
// causes the formatter used to wrap the value in quotes correctly
case "date":
// date objects are serialized to a string
return "$S";
case "long":
return "$LL";
case "double":
return "$LD";
case "float":
return "$LF";
default:
// for the reset use literal
return "$L";
}
}
/**
* get project added build values
*
* #return List of build values added by project's `buildConfig` closure
*/
#SuppressWarnings("unused")
public List<BuildValue> collectBuildValues() {
return mBuildValues;
}
/**
* Make a private constructor for the class. Default is public but our classes only contain
* <pre>{#code public static final {#link Object}}</pre> so public constructors are redundant
*
* #return private constructor method
*/
private MethodSpec createPrivateConstructor() {
return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
}
/**
* Create default field references
*
* #param project module to generate BuildConfig for
* #return List of fields to write to generated BuildConfig
*/
private List<FieldSpec> getDefaultFields(Project project) {
List<FieldSpec> fields = new ArrayList<>();
// set current version
fields.add(FieldSpec.builder(String.class, "version")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer(CodeBlock.of(getStringFormatter(String.class), project.getVersion()))
.build());
return fields;
}
class BuildValue implements Comparable {
/**
* Type of field's value
*/
/* package */ Class clazz;
/**
* Field name
*/
/* package */ String name;
/**
* Field's value Value must be able to be serialized as a string
*/
/* package */ Object value;
/* package */ BuildValue(Class clazz, String name, Object value) {
this.clazz = clazz;
this.name = name;
this.value = value;
}
/* package */ Class getValueType() {
return clazz;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BuildValue)) return false;
BuildValue that = (BuildValue) o;
if (clazz != null ? !clazz.equals(that.clazz) : that.clazz != null) return false;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
return value != null ? value.equals(that.value) : that.value == null;
}
#Override
public int hashCode() {
int result = clazz != null ? clazz.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
#Override
public int compareTo(Object o) {
return (name != null && o != null) ? name.compareTo(o.toString()) : -1;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("BuildValue{");
sb.append("class=").append(clazz.getCanonicalName());
sb.append(", name='").append(name).append('\'');
sb.append(", value=").append(value);
sb.append('}');
return sb.toString();
}
}
}
Here by default the plugin will create the BuildConfig.java with the default field of version but you could add your own values also
buildConfig {
add Boolean, 'myKey', false
}
Then at runtime you could get the reference's value with BuildConfig.value and for the example above you would also have the field BuildConfig.myKey available as a Boolean type.
Edit: my example uses groovy for the plugin class and the task class, however the extension, BuildConfigModel, is written in java. All my sources are located in src/main/groovy

AspectJ, Intertype Definition

I am receiving this error when I compile
The type XXX must implement the inherited abstract method
I have three files
A default implementation [com.SafeReaderIMPL.java]
public class SafeReaderIMPL implements ISafeReader {
private boolean successfulRead;
public SafeReaderIMPL() {
successfulRead = true;
}
protected void fail() {
successfulRead = false;
}
#Override
public boolean isSuccessfulRead() {
return successfulRead;
}
}
An interface file [com.ISafeReader.java]
public interface ISafeReader {
public boolean isSuccessfulRead();
}
An apsect (using annotations) [com.SafeReaderAspect.java]
#Aspect
public class SafeReaderAspect {
#DeclareParents(value = "com.BadReader", defaultImpl = SafeReaderIMPL.class)
public ISafeReader implementedInterface;
#AfterThrowing(pointcut = "execution(* *.*(..)) && this(m)", throwing = "e")
public void handleBadRead(JoinPoint joinPoint, ISafeReader m, Throwable e) {
((SafeReaderIMPL)m).fail();
}
}
And a Test Class [com.BadReader]
public class BadReader {
public void fail() throws Throwable {
throw new Throwable();
}
}
I compile the first three files in a separate jar using
ajc -source 1.8 -sourceroots . -outjar aspectLib.jar
I then compile the second file using the aspectLib like so
ajc -source 1.8 -sourceroots . -aspectpath ./aspectLib.jar -outjar common.jar
When I go to compile the second jar I get the error. I am using the latest stable version of AspectJ 1.8.3
BadReader.java:10 [error] The type BadReader must implement the
inherited abstract method ISafeReader.isSuccessfulRead() public class
BadReader {
^^^^^^^^
The problem is not two-step compilation as such, but the fact that #DeclareParents in #AspectJ syntax in not 100% compatible with declare parents in native syntax. Actually, #DeclareParents for introducing default interface implementations is superseded by #DeclareMixin (see this bug ticket), but the downside of the mixin approach is that you do not have a real A implements B scenario there, i.e. you cannot cast as you wish in your after-throwing advice, so this is also not a good option in your case.
So what do you do if you want to keep the two-step compilation approach? Just use native syntax:
Interface:
package com;
public interface ISafeReader {
boolean isSuccessfulRead();
}
Default implementation:
package com;
public class SafeReaderIMPL implements ISafeReader {
private boolean successfulRead;
public SafeReaderIMPL() { successfulRead = true; }
public void fail() { successfulRead = false; }
#Override public boolean isSuccessfulRead() { return successfulRead; }
}
ITD aspect:
package com;
public aspect SafeReaderAspect {
declare parents : com.BadReader implements SafeReaderIMPL;
after(ISafeReader safeReader) throwing : execution(* *(..)) && this(safeReader) {
System.out.println(thisJoinPoint + " - calling 'fail()' before rethrowing error");
((SafeReaderIMPL) safeReader).fail();
}
}
ITD target class with sample main method:
package com;
public class BadReader {
public void doSomething() {
throw new RuntimeException("my error");
}
public static void main(String[] args) {
BadReader badReader = new BadReader();
System.out.println("badReader.isSuccessfulRead() = " + badReader.isSuccessfulRead());
try { badReader.doSomething(); }
catch(Throwable t) { System.out.println(t); }
System.out.println("badReader.isSuccessfulRead() = " + badReader.isSuccessfulRead());
}
}
Now you can use the two-stage compilation approach.
Console output:
badReader.isSuccessfulRead() = true
execution(void com.BadReader.doSomething()) - calling 'fail()' before rethrowing error
java.lang.RuntimeException: my error
badReader.isSuccessfulRead() = false
The problem is due to the two-step compilation. During the second step, ajc needs the source code of SafeReaderIMPL to be able to weave BadReader, but it cannot find it into aspectLib.jar
In fact, if you try compiling in a single step (I did), it compiles and runs.
Unfortunately I don't know a way to fix this without providing the source code during the second compile step, which I suppose would render the whole two-step approach a bit pointless.

Gradle : how to use BuildConfig in an android-library with a flag that gets set in an app

My (gradle 1.10 and gradle plugin 0.8)-based android project consists of a big android-library that is a dependency for 3 different android-apps
In my library, I would love to be able to use a structure like this
if (BuildConfig.SOME_FLAG) {
callToBigLibraries()
}
as proguard would be able to reduce the size of the produced apk, based on the final value of SOME_FLAG
But I can't figure how to do it with gradle as :
* the BuildConfig produced by the library doesn't have the same package name than the app
* I have to import the BuildConfig with the library package in the library
* The apk of an apps includes the BuildConfig with the package of the app but not the one with the package of the library.
I tried without success to play with BuildTypes and stuff like
release {
// packageNameSuffix "library"
buildConfigField "boolean", "SOME_FLAG", "true"
}
debug {
//packageNameSuffix "library"
buildConfigField "boolean", "SOME_FLAG", "true"
}
What is the right way to builds a shared BuildConfig for my library and my apps whose flags will be overridden at build in the apps?
As a workaround, you can use this method, which uses reflection to get the field value from the app (not the library):
/**
* Gets a field from the project's BuildConfig. This is useful when, for example, flavors
* are used at the project level to set custom fields.
* #param context Used to find the correct file
* #param fieldName The name of the field-to-access
* #return The value of the field, or {#code null} if the field is not found.
*/
public static Object getBuildConfigValue(Context context, String fieldName) {
try {
Class<?> clazz = Class.forName(context.getPackageName() + ".BuildConfig");
Field field = clazz.getField(fieldName);
return field.get(null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
To get the DEBUG field, for example, just call this from your Activity:
boolean debug = (Boolean) getBuildConfigValue(this, "DEBUG");
I have also shared this solution on the AOSP Issue Tracker.
Update: With newer versions of the Android Gradle plugin publishNonDefault is deprecated and has no effect anymore. All variants are now published.
The following solution/workaround works for me. It was posted by some guy in the google issue tracker:
Try setting publishNonDefault to true in the library project:
android {
...
publishNonDefault true
...
}
And add the following dependencies to the app project that is using the library:
dependencies {
releaseCompile project(path: ':library', configuration: 'release')
debugCompile project(path: ':library', configuration: 'debug')
}
This way, the project that uses the library includes the correct build type of the library.
You can't do what you want, because BuildConfig.SOME_FLAG isn't going to get propagated properly to your library; build types themselves aren't propagated to libraries -- they're always built as RELEASE. This is bug https://code.google.com/p/android/issues/detail?id=52962
To work around it: if you have control over all of the library modules, you could make sure that all the code touched by callToBigLibraries() is in classes and packages that you can cleave off cleanly with ProGuard, then use reflection so that you can access them if they exist and degrade gracefully if they don't. You're essentially doing the same thing, but you're making the check at runtime instead of compile time, and it's a little harder.
Let me know if you're having trouble figuring out how to do this; I could provide a sample if you need it.
I use a static BuildConfigHelper class in both the app and the library, so that I can have the packages BuildConfig set as final static variables in my library.
In the application, place a class like this:
package com.yourbase;
import com.your.application.BuildConfig;
public final class BuildConfigHelper {
public static final boolean DEBUG = BuildConfig.DEBUG;
public static final String APPLICATION_ID = BuildConfig.APPLICATION_ID;
public static final String BUILD_TYPE = BuildConfig.BUILD_TYPE;
public static final String FLAVOR = BuildConfig.FLAVOR;
public static final int VERSION_CODE = BuildConfig.VERSION_CODE;
public static final String VERSION_NAME = BuildConfig.VERSION_NAME;
}
And in the library:
package com.your.library;
import android.support.annotation.Nullable;
import java.lang.reflect.Field;
public class BuildConfigHelper {
private static final String BUILD_CONFIG = "com.yourbase.BuildConfigHelper";
public static final boolean DEBUG = getDebug();
public static final String APPLICATION_ID = (String) getBuildConfigValue("APPLICATION_ID");
public static final String BUILD_TYPE = (String) getBuildConfigValue("BUILD_TYPE");
public static final String FLAVOR = (String) getBuildConfigValue("FLAVOR");
public static final int VERSION_CODE = getVersionCode();
public static final String VERSION_NAME = (String) getBuildConfigValue("VERSION_NAME");
private static boolean getDebug() {
Object o = getBuildConfigValue("DEBUG");
if (o != null && o instanceof Boolean) {
return (Boolean) o;
} else {
return false;
}
}
private static int getVersionCode() {
Object o = getBuildConfigValue("VERSION_CODE");
if (o != null && o instanceof Integer) {
return (Integer) o;
} else {
return Integer.MIN_VALUE;
}
}
#Nullable
private static Object getBuildConfigValue(String fieldName) {
try {
Class c = Class.forName(BUILD_CONFIG);
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(null);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Then, anywhere in your library where you want to check BuildConfig.DEBUG, you can check BuildConfigHelper.DEBUG and access it from anywhere without a context, and the same for the other properties. I did it this way so that the library will work with all my applications, without needing to pass a context in or set the package name some other way, and the application class only needs the import line changed to suit when adding it into a new application
Edit: I'd just like to reiterate, that this is the easiest (and only one listed here) way to get the values to be assigned to final static variables in the library from all of your applications without needing a context or hard coding the package name somewhere, which is almost as good as having the values in the default library BuildConfig anyway, for the minimal upkeep of changing that import line in each application.
For the case where the applicationId is not the same as the package (i.e. multiple applicationIds per project) AND you want to access from a library project:
Use Gradle to store the base package in resources.
In main/AndroidManifest.xml:
android {
applicationId "com.company.myappbase"
// note: using ${applicationId} here will be exactly as above
// and so NOT necessarily the applicationId of the generated APK
resValue "string", "build_config_package", "${applicationId}"
}
In Java:
public static boolean getDebug(Context context) {
Object obj = getBuildConfigValue("DEBUG", context);
if (obj instanceof Boolean) {
return (Boolean) o;
} else {
return false;
}
}
private static Object getBuildConfigValue(String fieldName, Context context) {
int resId = context.getResources().getIdentifier("build_config_package", "string", context.getPackageName());
// try/catch blah blah
Class<?> clazz = Class.forName(context.getString(resId) + ".BuildConfig");
Field field = clazz.getField(fieldName);
return field.get(null);
}
use both
my build.gradle
// ...
productFlavors {
internal {
// applicationId "com.elevensein.sein.internal"
applicationIdSuffix ".internal"
resValue "string", "build_config_package", "com.elevensein.sein"
}
production {
applicationId "com.elevensein.sein"
}
}
I want to call like below
Boolean isDebug = (Boolean) BuildConfigUtils.getBuildConfigValue(context, "DEBUG");
BuildConfigUtils.java
public class BuildConfigUtils
{
public static Object getBuildConfigValue (Context context, String fieldName)
{
Class<?> buildConfigClass = resolveBuildConfigClass(context);
return getStaticFieldValue(buildConfigClass, fieldName);
}
public static Class<?> resolveBuildConfigClass (Context context)
{
int resId = context.getResources().getIdentifier("build_config_package",
"string",
context.getPackageName());
if (resId != 0)
{
// defined in build.gradle
return loadClass(context.getString(resId) + ".BuildConfig");
}
// not defined in build.gradle
// try packageName + ".BuildConfig"
return loadClass(context.getPackageName() + ".BuildConfig");
}
private static Class<?> loadClass (String className)
{
Log.i("BuildConfigUtils", "try class load : " + className);
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
private static Object getStaticFieldValue (Class<?> clazz, String fieldName)
{
try { return clazz.getField(fieldName).get(null); }
catch (NoSuchFieldException e) { e.printStackTrace(); }
catch (IllegalAccessException e) { e.printStackTrace(); }
return null;
}
}
For me this is the ONLY ONE AND ACCEPTABLE* SOLUTION TO determine the ANDROID APPLICATION BuildConfig.class:
// base entry point
// abstract application
// which defines the method to obtain the desired class
// the definition of the application is contained in the library
// that wants to access the method or in a superior library package
public abstract class BasApp extends android.app.Application {
/*
* GET BUILD CONFIG CLASS
*/
protected Class<?> getAppBuildConfigClass();
// HELPER METHOD TO CAST CONTEXT TO BASE APP
public static BaseApp getAs(android.content.Context context) {
BaseApp as = getAs(context, BaseApp.class);
return as;
}
// HELPER METHOD TO CAST CONTEXT TO SPECIFIC BASEpp INHERITED CLASS TYPE
public static <I extends BaseApp> I getAs(android.content.Context context, Class<I> forCLass) {
android.content.Context applicationContext = context != null ?context.getApplicationContext() : null;
return applicationContext != null && forCLass != null && forCLass.isAssignableFrom(applicationContext.getClass())
? (I) applicationContext
: null;
}
// STATIC HELPER TO GET BUILD CONFIG CLASS
public static Class<?> getAppBuildConfigClass(android.content.Context context) {
BaseApp as = getAs(context);
Class buildConfigClass = as != null
? as.getAppBuildConfigClass()
: null;
return buildConfigClass;
}
}
// FINAL APP WITH IMPLEMENTATION
// POINTING TO DESIRED CLASS
public class MyApp extends BaseApp {
#Override
protected Class<?> getAppBuildConfigClass() {
return somefinal.app.package.BuildConfig.class;
}
}
USAGE IN LIBRARY:
Class<?> buildConfigClass = BaseApp.getAppBuildConfigClass(Context);
if(buildConfigClass !- null) {
// do your job
}
*there are couple of things need to be watched out:
getApplicationContext() - could return a context which is not an App ContexWrapper implementation - see what Applicatio class extends & get to know of the possibilities of context wrapping
the class returned by final app could be loaded by different class loaders than those who will use it - depends of loader implementation and some principals typical (chierarchy, visibility) for loaders
everything depends on the implemmentation of as in this case simple DELEGATION!!! - the solution could be more sophisticetaded - i wanted only to show here the usage of DELEGATION pattern :)
** why i downwoted all of reflection based patterns because they all have weak points and they all in some certain conditions will fail:
Class.forName(className); - because of not speciified loader
context.getPackageName() + ".BuildConfig"
a) context.getPackageName() - "by default - else see b)" returns not package defined in manifest but application id (somtimes they both are the same), see how the manifest package property is used and its flow - at the end apt tool will replace it with applicaton id (see ComponentName class for example what the pkg stands for there)
b) context.getPackageName() - will return what the implementaio wants to :P
*** what to change in my solution to make it more flawless
replace class with its name that will drop the problems wchich could appear when many classes loaded with different loaders accessing / or are used to obtain a final result involving class (get to know what describes the equality between two classes (for a compiler at runtime) - in short a class equality defines not a self class but a pair which is constituted by the loader and the class. (some home work - try load a inner class with different loader and access it by outer class loaded with different loader) - it would turns out that we will get illegal access error :) even the inner class is in the same package has all modificators allowing access to it outer class :) compiler/linker "VM" treats them as two not related classes...