blob: 7c2d3e2e1ccaaef93da016a890827bf72ac2ac03 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.android.tools.idea.model;
import com.android.annotations.VisibleForTesting;
import com.android.resources.ScreenSize;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.devices.Device;
import com.google.common.collect.Lists;
import com.intellij.openapi.application.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Key;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.Function;
import org.jetbrains.android.dom.manifest.*;
import org.jetbrains.android.dom.manifest.Application;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.xml.AndroidManifest.*;
/**
* Retrieves and caches manifest information such as the themes to be used for
* a given activity.
*
* @see com.android.xml.AndroidManifest
*/
public abstract class ManifestInfo {
public static class ActivityAttributes {
@Nullable
private final String myIcon;
@Nullable
private final String myLabel;
@NotNull
private final String myName;
@Nullable
private final String myParentActivity;
@Nullable
private final String myTheme;
@Nullable
private final String myUiOptions;
public ActivityAttributes(@NotNull XmlTag activity, @Nullable String packageName) {
// Get activity name.
String name = activity.getAttributeValue(ATTRIBUTE_NAME, ANDROID_URI);
if (name == null || name.length() == 0) {
throw new RuntimeException("Activity name cannot be empty.");
}
int index = name.indexOf('.');
if (index <= 0 && packageName != null && !packageName.isEmpty()) {
name = packageName + (index == -1 ? "." : "") + name;
}
myName = name;
// Get activity icon.
String value = activity.getAttributeValue(ATTRIBUTE_ICON, ANDROID_URI);
if (value != null && value.length() > 0) {
myIcon = value;
} else {
myIcon = null;
}
// Get activity label.
value = activity.getAttributeValue(ATTRIBUTE_LABEL, ANDROID_URI);
if (value != null && value.length() > 0) {
myLabel = value;
} else {
myLabel = null;
}
// Get activity parent. Also search the meta-data for parent info.
value = activity.getAttributeValue(ATTRIBUTE_PARENT_ACTIVITY_NAME, ANDROID_URI);
if (value == null || value.length() == 0) {
// TODO: Not sure if meta data can be used for API Level > 16
XmlTag[] metaData = activity.findSubTags(NODE_METADATA);
for (XmlTag data : metaData) {
String metaDataName = data.getAttributeValue(ATTRIBUTE_NAME, ANDROID_URI);
if (VALUE_PARENT_ACTIVITY.equals(metaDataName)) {
value = data.getAttributeValue(ATTRIBUTE_VALUE, ANDROID_URI);
if (value != null) {
index = value.indexOf('.');
if (index <= 0 && packageName != null && !packageName.isEmpty()) {
value = packageName + (index == -1 ? "." : "") + value;
break;
}
}
}
}
}
if (value != null && value.length() > 0) {
myParentActivity = value;
} else {
myParentActivity = null;
}
// Get activity theme.
value = activity.getAttributeValue(ATTRIBUTE_THEME, ANDROID_URI);
if (value != null && value.length() > 0) {
myTheme = value;
} else {
myTheme = null;
}
// Get UI options.
value = activity.getAttributeValue(ATTRIBUTE_UI_OPTIONS, ANDROID_URI);
if (value != null && value.length() > 0) {
myUiOptions = value;
} else {
myUiOptions = null;
}
}
@Nullable
public String getIcon() {
return myIcon;
}
@Nullable
public String getLabel() {
return myLabel;
}
@NotNull
public String getName() {
return myName;
}
@Nullable
public String getParentActivity() {
return myParentActivity;
}
@Nullable
public String getTheme() {
return myTheme;
}
@Nullable
public String getUiOptions() {
return myUiOptions;
}
}
/** Key for the per-module non-persistent property storing the {@link ManifestInfo} for this module. */
@VisibleForTesting
final static Key<ManifestInfo> MANIFEST_FINDER = new Key<ManifestInfo>("adt-manifest-info"); //$NON-NLS-1$
/** Key for the per-module non-persistent property storing the merged {@link ManifestInfo} for this module. */
@VisibleForTesting
final static Key<ManifestInfo> MERGED_MANIFEST_FINDER = new Key<ManifestInfo>("adt-merged-manifest-info");
/**
* Returns the {@link ManifestInfo} for the given module.
*
* @param module the module the finder is associated with
* @return a {@ManifestInfo} for the given module, never null
* @deprecated Use {@link #get(com.intellij.openapi.module.Module, boolean)} which is explicit about
* whether a merged manifest should be used.
*/
@NotNull
public static ManifestInfo get(Module module) {
return get(module, false);
}
/**
* Returns the {@link ManifestInfo} for the given module.
*
* @param module the module the finder is associated with
* @param useMergedManifest if true, the merged manifest is used if available, otherwise the main source set's manifest
* is used
* @return a {@ManifestInfo} for the given module
*/
public static ManifestInfo get(Module module, boolean useMergedManifest) {
Key<ManifestInfo> key = useMergedManifest ? MERGED_MANIFEST_FINDER : MANIFEST_FINDER;
ManifestInfo finder = module.getUserData(key);
if (finder == null) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet == null) {
throw new IllegalArgumentException("Manifest information can only be obtained on modules with the Android facet.");
}
finder = useMergedManifest ? new MergedManifestInfo(module) : new PrimaryManifestInfo(module);
module.putUserData(key, finder);
}
return finder;
}
/**
* Clears the cached manifest information. The next get call on one of the
* properties will cause the information to be refreshed.
*/
public abstract void clear();
/**
* Returns the default package registered in the Android manifest
*
* @return the default package registered in the manifest
*/
@Nullable
public abstract String getPackage();
/**
* Returns a map from activity full class names to the corresponding {@link ActivityAttributes}
*
* @return a map from activity fqcn to ActivityAttributes
*/
@NotNull
public abstract Map<String, ActivityAttributes> getActivityAttributesMap();
/**
* Returns the attributes of an activity.
*/
@Nullable
public abstract ActivityAttributes getActivityAttributes(@NotNull String activity);
/**
* Returns the manifest theme registered on the application, if any
*
* @return a manifest theme, or null if none was registered
*/
@Nullable
public abstract String getManifestTheme();
/**
* Returns the default theme for this project, by looking at the manifest default
* theme registration, target SDK, rendering target, etc.
*
* @param renderingTarget the rendering target use to render the theme, or null
* @param screenSize the screen size to obtain a default theme for, or null if unknown
* @param device the device to obtain a default theme for, or null if unknown
* @return the theme to use for this project, never null
*/
@NotNull
public abstract String getDefaultTheme(@Nullable IAndroidTarget renderingTarget, @Nullable ScreenSize screenSize,
@Nullable Device device);
/**
* Returns the application icon, or null
*
* @return the application icon, or null
*/
@Nullable
public abstract String getApplicationIcon();
/**
* Returns the application label, or null
*
* @return the application label, or null
*/
@Nullable
public abstract String getApplicationLabel();
/**
* Returns true if the application has RTL support.
*
* @return true if the application has RTL support.
*/
public abstract boolean isRtlSupported();
/**
* Returns the value for the debuggable flag set in the manifest. Returns null if not set.
*/
@Nullable
public abstract Boolean getApplicationDebuggable();
/**
* Returns the target SDK version
*
* @return the target SDK version
*/
@NotNull
public abstract AndroidVersion getTargetSdkVersion();
/**
* Returns the minimum SDK version
*
* @return the minimum SDK version
*/
@NotNull
public abstract AndroidVersion getMinSdkVersion();
/** @return the list of activities defined in the manifest. */
@NotNull
public List<Activity> getActivities() {
return getApplicationComponents(new Function<Application, List<Activity>>() {
@Override
public List<Activity> fun(Application application) {
return application.getActivities();
}
});
}
/** @return the list of activity aliases defined in the manifest. */
@NotNull
public List<ActivityAlias> getActivityAliases() {
return getApplicationComponents(new Function<Application, List<ActivityAlias>>() {
@Override
public List<ActivityAlias> fun(Application application) {
return application.getActivityAliass();
}
});
}
/** @return the list of services defined in the manifest. */
@NotNull
public List<Service> getServices() {
return getApplicationComponents(new Function<Application, List<Service>>() {
@Override
public List<Service> fun(Application application) {
return application.getServices();
}
});
}
private <T> List<T> getApplicationComponents(final Function<Application, List<T>> accessor) {
final List<Manifest> manifests = getManifests();
if (manifests.isEmpty()) {
Logger.getInstance(ManifestInfo.class).warn("List of manifests is empty, possibly needs a gradle sync.");
}
return ApplicationManager.getApplication().runReadAction(new Computable<List<T>>() {
@Override
public List<T> compute() {
List<T> components = Lists.newArrayList();
for (Manifest m : manifests) {
Application application = m.getApplication();
if (application != null) {
components.addAll(accessor.fun(application));
}
}
return components;
}
});
}
@NotNull
public List<UsesFeature> getRequiredFeatures() {
final List<Manifest> manifests = getManifests();
if (manifests.isEmpty()) {
Logger.getInstance(ManifestInfo.class).warn("List of manifests is empty, possibly needs a gradle sync.");
}
return ApplicationManager.getApplication().runReadAction(new Computable<List<UsesFeature>>() {
@Override
public List<UsesFeature> compute() {
List<UsesFeature> usesFeatures = Lists.newArrayList();
for (Manifest m : manifests) {
usesFeatures.addAll(m.getUsesFeatures());
}
return usesFeatures;
}
});
}
@NotNull
protected abstract List<Manifest> getManifests();
}