/* * Copyright (C) 2014 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.ide.common.rendering.HardwareConfigHelper; import com.android.sdklib.SdkVersionInfo; import com.android.resources.ScreenSize; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.devices.Device; import com.android.tools.idea.rendering.multi.CompatibilityRenderTarget; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import org.jetbrains.android.dom.manifest.Manifest; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidRootUtil; import org.jetbrains.android.util.AndroidUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.android.SdkConstants.*; import static com.android.xml.AndroidManifest.*; class PrimaryManifestInfo extends ManifestInfo { private static final Logger LOG = Logger.getInstance(PrimaryManifestInfo.class); private final Module myModule; private String myPackage; private String myManifestTheme; private Map<String, ActivityAttributes> myActivityAttributesMap; private ManifestFile myManifestFile; private long myLastChecked; private AndroidVersion myMinSdk; private AndroidVersion myTargetSdk; private String myApplicationIcon; private String myApplicationLabel; private boolean myApplicationSupportsRtl; private Manifest myManifest; private Boolean myApplicationDebuggable; PrimaryManifestInfo(Module module) { myModule = module; } @Override public void clear() { myLastChecked = 0; } @Nullable @Override public String getPackage() { sync(); return myPackage; } @NotNull @Override public Map<String, ActivityAttributes> getActivityAttributesMap() { sync(); if (myActivityAttributesMap == null) { return Collections.emptyMap(); } return myActivityAttributesMap; } @Nullable @Override public ActivityAttributes getActivityAttributes(@NotNull String activity) { int index = activity.indexOf('.'); if (index <= 0 && myPackage != null && !myPackage.isEmpty()) { activity = myPackage + (index == -1 ? "." : "") + activity; } return getActivityAttributesMap().get(activity); } @Nullable @Override public String getManifestTheme() { sync(); return myManifestTheme; } @NotNull @Override public String getDefaultTheme(@Nullable IAndroidTarget renderingTarget, @Nullable ScreenSize screenSize, @Nullable Device device) { sync(); if (myManifestTheme != null) { return myManifestTheme; } // For Android Wear and Android TV, the defaults differ if (device != null) { if (HardwareConfigHelper.isWear(device)) { return "@android:style/Theme.DeviceDefault.Light"; } else if (HardwareConfigHelper.isTv(device)) { //noinspection SpellCheckingInspection return "@style/Theme.Leanback"; } } // From manifest theme documentation: // "If that attribute is also not set, the default system theme is used." int targetSdk; AndroidModuleInfo info = AndroidModuleInfo.get(myModule); if (info != null) { targetSdk = info.getTargetSdkVersion().getApiLevel(); } else if (myTargetSdk != null) { targetSdk = myTargetSdk.getApiLevel(); } else { targetSdk = SdkVersionInfo.HIGHEST_KNOWN_API; } int renderingTargetSdk = targetSdk; if (renderingTarget instanceof CompatibilityRenderTarget) { renderingTargetSdk = renderingTarget.getVersion().getApiLevel(); //targetSdk = SdkVersionInfo.HIGHEST_KNOWN_API } else if (renderingTarget != null) { renderingTargetSdk = renderingTarget.getVersion().getApiLevel(); } int apiLevel = Math.min(targetSdk, renderingTargetSdk); // For now this theme works only on XLARGE screens. When it works for all sizes, // add that new apiLevel to this check. if (apiLevel >= 11 && screenSize == ScreenSize.XLARGE || apiLevel >= 14) { return ANDROID_STYLE_RESOURCE_PREFIX + "Theme.Holo"; //$NON-NLS-1$ } else { return ANDROID_STYLE_RESOURCE_PREFIX + "Theme"; //$NON-NLS-1$ } } @Nullable @Override public String getApplicationIcon() { sync(); return myApplicationIcon; } @Nullable @Override public String getApplicationLabel() { sync(); return myApplicationLabel; } @Override public boolean isRtlSupported() { sync(); return myApplicationSupportsRtl; } @Nullable @Override public Boolean getApplicationDebuggable() { sync(); return myApplicationDebuggable; } @NotNull @Override public AndroidVersion getTargetSdkVersion() { sync(); return myTargetSdk != null ? myTargetSdk : getMinSdkVersion(); } @NotNull @Override public AndroidVersion getMinSdkVersion() { sync(); return myMinSdk != null ? myMinSdk : AndroidVersion.DEFAULT; } @NotNull @Override protected List<Manifest> getManifests() { sync(); return myManifest != null ? Collections.singletonList(myManifest) : Collections.<Manifest>emptyList(); } /** * Ensure that the package, theme and activity maps are initialized and up to date * with respect to the manifest file */ private void sync() { // Since each of the accessors call sync(), allow a bunch of immediate // accessors to all bypass the file stat() below long now = System.currentTimeMillis(); if (now - myLastChecked < 50 && myManifestFile != null) { return; } myLastChecked = now; ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { syncWithReadPermission(); } }); } private void syncWithReadPermission() { if (myManifestFile == null) { myManifestFile = ManifestFile.create(myModule); if (myManifestFile == null) { return; } } // Check to see if our data is up to date boolean refresh = myManifestFile.refresh(); if (!refresh) { // Already have up to date data return; } myActivityAttributesMap = new HashMap<String, ActivityAttributes>(); myManifestTheme = null; myTargetSdk = AndroidVersion.DEFAULT; myMinSdk = AndroidVersion.DEFAULT; myPackage = ""; //$NON-NLS-1$ myApplicationIcon = null; myApplicationLabel = null; myApplicationSupportsRtl = false; try { XmlFile xmlFile = myManifestFile.getXmlFile(); if (xmlFile == null) { return; } XmlTag root = xmlFile.getRootTag(); if (root == null) { return; } myPackage = root.getAttributeValue(ATTRIBUTE_PACKAGE); XmlTag[] applications = root.findSubTags(NODE_APPLICATION); if (applications.length > 0) { assert applications.length == 1; XmlTag application = applications[0]; myApplicationIcon = application.getAttributeValue(ATTRIBUTE_ICON, ANDROID_URI); myApplicationLabel = application.getAttributeValue(ATTRIBUTE_LABEL, ANDROID_URI); myManifestTheme = application.getAttributeValue(ATTRIBUTE_THEME, ANDROID_URI); myApplicationSupportsRtl = VALUE_TRUE.equals(application.getAttributeValue(ATTRIBUTE_SUPPORTS_RTL, ANDROID_URI)); String debuggable = application.getAttributeValue(ATTRIBUTE_DEBUGGABLE, ANDROID_URI); myApplicationDebuggable = debuggable == null ? null : VALUE_TRUE.equals(debuggable); XmlTag[] activities = application.findSubTags(NODE_ACTIVITY); for (XmlTag activity : activities) { ActivityAttributes attributes = new ActivityAttributes(activity, myPackage); myActivityAttributesMap.put(attributes.getName(), attributes); } } // Look up target SDK XmlTag[] usesSdks = root.findSubTags(NODE_USES_SDK); if (usesSdks.length > 0) { XmlTag usesSdk = usesSdks[0]; myMinSdk = getApiVersion(usesSdk, ATTRIBUTE_MIN_SDK_VERSION, AndroidVersion.DEFAULT); myTargetSdk = getApiVersion(usesSdk, ATTRIBUTE_TARGET_SDK_VERSION, myMinSdk); } myManifest = AndroidUtils.loadDomElementWithReadPermission(myModule.getProject(), xmlFile, Manifest.class); } catch (Exception e) { LOG.error("Could not read Manifest data", e); } } private static AndroidVersion getApiVersion(XmlTag usesSdk, String attribute, AndroidVersion defaultApiLevel) { String valueString = usesSdk.getAttributeValue(attribute, ANDROID_URI); if (valueString != null) { // TODO: Pass in platforms if we have them AndroidVersion version = SdkVersionInfo.getVersion(valueString, null); if (version != null) { return version; } } return defaultApiLevel; } private static class ManifestFile { private final Module myModule; private VirtualFile myVFile; private XmlFile myXmlFile; private long myLastModified = 0; private ManifestFile(@NotNull Module module, @NotNull VirtualFile file) { myModule = module; myVFile = file; } @Nullable public static synchronized ManifestFile create(@NotNull Module module) { ApplicationManager.getApplication().assertReadAccessAllowed(); AndroidFacet facet = AndroidFacet.getInstance(module); if (facet == null) { return null; } VirtualFile manifestFile = AndroidRootUtil.getPrimaryManifestFile(facet); if (manifestFile == null) { return null; } return new ManifestFile(module, manifestFile); } @Nullable private XmlFile parseManifest() { if (myVFile == null || !myVFile.exists()) { return null; } Project project = myModule.getProject(); if (project.isDisposed()) { return null; } PsiFile psiFile = PsiManager.getInstance(project).findFile(myVFile); return (psiFile instanceof XmlFile) ? (XmlFile)psiFile : null; } public synchronized boolean refresh() { long lastModified = getLastModified(); if (myXmlFile == null || myLastModified < lastModified) { myXmlFile = parseManifest(); if (myXmlFile == null) { return false; } myLastModified = lastModified; return true; } else { return false; } } private long getLastModified() { if (myXmlFile != null) { return myXmlFile.getModificationStamp(); } else { return 0; } } @Nullable public synchronized XmlFile getXmlFile() { return myXmlFile; } } }