/* * 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.module.Module; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Key; import com.intellij.psi.xml.XmlTag; 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 activities defined in the manifest. */ @NotNull public List<Activity> getActivities() { return ApplicationManager.getApplication().runReadAction(new Computable<List<Activity>>() { @Override public List<Activity> compute() { List<Activity> activities = Lists.newArrayList(); for (Manifest m : getManifests()) { Application application = m.getApplication(); if (application != null) { activities.addAll(application.getActivities()); } } return activities; } }); } /** @return the list activity aliases defined in the manifest. */ @NotNull public List<ActivityAlias> getActivityAliases() { return ApplicationManager.getApplication().runReadAction(new Computable<List<ActivityAlias>>() { @Override public List<ActivityAlias> compute() { List<ActivityAlias> activityAliases = Lists.newArrayList(); for (Manifest m : getManifests()) { Application application = m.getApplication(); if (application != null) { activityAliases.addAll(application.getActivityAliass()); } } return activityAliases; } }); } @NotNull public List<UsesFeature> getRequiredFeatures() { return ApplicationManager.getApplication().runReadAction(new Computable<List<UsesFeature>>() { @Override public List<UsesFeature> compute() { List<UsesFeature> usesFeatures = Lists.newArrayList(); for (Manifest m : getManifests()) { usesFeatures.addAll(m.getUsesFeatures()); } return usesFeatures; } }); } @NotNull protected abstract List<Manifest> getManifests(); }