/* * Copyright (C) 2012 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.motorola.studio.android.common.utilities; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.osgi.framework.Bundle; import com.motorola.studio.android.common.CommonPlugin; import com.motorola.studio.android.common.exception.AndroidException; import com.motorola.studio.android.common.log.StudioLogger; import com.motorola.studio.android.common.utilities.i18n.UtilitiesNLS; public class AndroidUtils { private static final String CLASS_COM_ANDROID_IDE_ECLIPSE_ADT_INTERNAL_SDK_ANDROID_TARGET_DATA = "com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData"; //$NON-NLS-1$ private static final String CLASS_COM_ANDROID_SDKLIB_I_ANDROID_TARGET = "com.android.sdklib.IAndroidTarget"; //$NON-NLS-1$ private static final String CLASS_COM_ANDROID_SDKLIB_ANDROID_VERSION = "com.android.sdklib.AndroidVersion"; //$NON-NLS-1$ private static final String CLASS_COM_ANDROID_IDE_ECLIPSE_ADT_INTERNAL_SDK_SDK = "com.android.ide.eclipse.adt.internal.sdk.Sdk"; //$NON-NLS-1$ //Constants private static final String ANDROID_VERSION_API_LEVEL = "AndroidVersion.ApiLevel"; //$NON-NLS-1$ private static final String SOURCE_PROPERTIES = "source.properties"; //$NON-NLS-1$ private static final String ANDROID_JAR = "android.jar"; //$NON-NLS-1$ private static final String PLATFORMS = "platforms"; //$NON-NLS-1$ private static final String ANDROID = "android-"; //$NON-NLS-1$ private static final String TARGET = "target"; //$NON-NLS-1$ private static final String DEFAULT_PROPERTIES = "default.properties"; //$NON-NLS-1$ private static final String PROJECT_PROPERTIES = "project.properties"; //$NON-NLS-1$ /* * Contains the exceptions to the general rule (that prepends android.permission. to the permissionName) */ private static final Map<String, String> permissionNameToPrefixToAppend = new HashMap<String, String>(); static { permissionNameToPrefixToAppend.put("SET_ALARM", "com.android.alarm.permission"); //$NON-NLS-1$ //$NON-NLS-2$ permissionNameToPrefixToAppend.put("READ_HISTORY_BOOKMARKS", //$NON-NLS-1$ "com.android.browser.permission"); //$NON-NLS-1$ permissionNameToPrefixToAppend.put("WRITE_HISTORY_BOOKMARKS", //$NON-NLS-1$ "com.android.browser.permission"); //$NON-NLS-1$ permissionNameToPrefixToAppend.put("ADD_VOICEMAIL", "com.android.voicemail.permission"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Gets Android Sdk set in the preference page * @return */ public static String getSDKPathByPreference() { IEclipsePreferences pref = InstanceScope.INSTANCE.getNode("com.android.ide.eclipse.adt"); //$NON-NLS-1$ return pref.get("com.android.ide.eclipse.adt.sdk", ""); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Gets android jar from the project * @param projectDir * @return path to android.jar * @throws AndroidException */ public static File getAndroidJar(File projectDir) throws AndroidException { File androidTargetFolder = getAndroidTargetPathForProject(projectDir); return new File(androidTargetFolder, "android.jar"); //$NON-NLS-1$ } /** * Returns all available intent filter permissions from a given project * * @return String array containing the available permissions */ public static String[] getIntentFilterPermissions(IProject project) { String[] attributeValues = new String[0]; if ((project != null) && project.isOpen()) { try { //reflection for: Sdk sdk = Sdk.getCurrent(); Class<?> clsSdk = Class.forName(CLASS_COM_ANDROID_IDE_ECLIPSE_ADT_INTERNAL_SDK_SDK); Method mtdGetCurrent = clsSdk.getMethod("getCurrent", (Class[]) null); //$NON-NLS-1$ Object sdk = mtdGetCurrent.invoke(null, (Object[]) null); //reflection for: IAndroidTarget target = sdk.getTarget(project); Method mtdGetTarget = clsSdk.getMethod("getTarget", IProject.class); //$NON-NLS-1$ Object target = mtdGetTarget.invoke(sdk, project); //reflection for: AndroidTargetData targetData = sdk.getTargetData(target); Class<?> interfaceIAndroidTarget = Class.forName(CLASS_COM_ANDROID_SDKLIB_I_ANDROID_TARGET); Method mtdGetTargetData = clsSdk.getMethod("getTargetData", interfaceIAndroidTarget); //$NON-NLS-1$ Object targetData = mtdGetTargetData.invoke(sdk, target); if (targetData != null) { //reflection for: attributeValues = targetData.getAttributeValues("uses-permission", "android:name"); //$NON-NLS-1$ //$NON-NLS-2$ Class<?> clsAndroidTargetData = Class.forName(CLASS_COM_ANDROID_IDE_ECLIPSE_ADT_INTERNAL_SDK_ANDROID_TARGET_DATA); Method mtdGetAttributeValues = clsAndroidTargetData.getMethod("getAttributeValues", String.class, //$NON-NLS-1$ String.class); attributeValues = (String[]) mtdGetAttributeValues.invoke(targetData, "uses-permission", //$NON-NLS-1$ "android:name"); //$NON-NLS-1$ } } catch (Exception e) { StudioLogger.warn("It was not possible to reach ADT methods (reflection break)", e.getMessage()); try { attributeValues = getIntentFilterPermissions().toArray(attributeValues); } catch (IOException e1) { StudioLogger.error( UtilitiesNLS.AndroidUtils_NotPossibleToReachPermissionsFile_Error, e1.getMessage()); } EclipseUtils.showWarningDialog( UtilitiesNLS.AndroidUtils_ERROR_GETINTENTPERMISSIONSBYREFLECTION_TITLE, UtilitiesNLS.AndroidUtils_ERROR_GETINTENTPERMISSIONSBYREFLECTION_MESSAGE); } } return attributeValues; } /** * Retrieves the path to platform/$target$, where * $target$ is defined inside default.properties/project.properties file * * @param projectDir * @return file with path to target folder * @throws AndroidException * problem to read default.properties/project.properties */ public static File getAndroidTargetPathForProject(File projectDir) throws AndroidException { File androidTarget = null; Properties properties = new Properties(); // changed from default.properties to project.properties after R14 File defaultPropertiesFile = new File(projectDir, PROJECT_PROPERTIES); if (!defaultPropertiesFile.exists()) { // WARNING: do not remove statement below assigning // default.properties file to keep compatibility with projects // created with ADTs before R14 defaultPropertiesFile = new File(projectDir, DEFAULT_PROPERTIES); } if (defaultPropertiesFile.exists()) { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(defaultPropertiesFile); properties.load(fileInputStream); } catch (IOException e) { throw new AndroidException( UtilitiesNLS.AndroidUtils_ErrorReadingDefaultPropertiesFile, e); } finally { if (fileInputStream != null) { try { fileInputStream.close(); } catch (Exception e) { //Do nothing. } } } if (properties.containsKey(TARGET)) { String targetValue = properties.getProperty(TARGET); if (targetValue != null) { if (!targetValue.startsWith(ANDROID)) { try { // add-on => <name>:<model>:<version> int colonIndex = targetValue.lastIndexOf(":"); //$NON-NLS-1$ if (colonIndex >= 0) { targetValue = ANDROID + targetValue.substring(colonIndex + 1); } } catch (Exception e) { //Do nothing. } } } boolean androidSdkPreferenceDefined = !getSDKPathByPreference().equals(""); //$NON-NLS-1$ String sdkPath = getSdkPath(androidSdkPreferenceDefined); if (sdkPath != null) { // found sdk path androidTarget = new File(sdkPath + File.separator + PLATFORMS + File.separator + targetValue); if (!androidTarget.exists()) { //if not found the exact version, then look for one version of android that is greater than target value (and retrieve platform folder) File baseFolder = new File(sdkPath + File.separator + PLATFORMS); File[] androidPlatforms = baseFolder.listFiles(); boolean foundJar = false; if (androidPlatforms.length > 0) { for (File androidPlatform : androidPlatforms) { File sourcePropsFile = new File(androidPlatform, SOURCE_PROPERTIES); File jar = new File(androidPlatform, ANDROID_JAR); if (sourcePropsFile.exists() && jar.exists()) { Properties sourceProperties = new Properties(); try { fileInputStream = new FileInputStream(sourcePropsFile); sourceProperties.load(fileInputStream); } catch (IOException e) { throw new AndroidException( UtilitiesNLS.AndroidUtils_ErrorReadingDefaultPropertiesFile, e); } finally { if (fileInputStream != null) { try { fileInputStream.close(); } catch (Exception e) { //Do nothing. } } } //platform api level String apiLevel = sourceProperties.getProperty(ANDROID_VERSION_API_LEVEL); int index = targetValue.indexOf("-"); //$NON-NLS-1$ if ((index >= 0) && (apiLevel != null)) { //project target declared String versionName = targetValue.substring(index + 1); try { Integer version = Integer.valueOf(versionName); Integer apiLevelVersion = Integer.valueOf(apiLevel); if (apiLevelVersion >= version) { //found a compatible platform foundJar = true; androidTarget = androidPlatform; break; } } catch (NumberFormatException nfe) { //ignore this folder (add-on or preview) } } } } if (!foundJar) { throw new AndroidException( androidTarget.getAbsolutePath() + UtilitiesNLS.AndroidUtils_ERROR_SDK_TARGETPLATFORM_NOTFOUND); } } } } else { throw new AndroidException(UtilitiesNLS.AndroidUtils_ERROR_SDKPATHNOTFOUND); } } } else { throw new AndroidException(UtilitiesNLS.AndroidUtils_ERROR_DEFAULTPROPERTIESNOTFOUND); } return androidTarget; } /** * Returns the path for Android SDK * * @param preferenceDefined * true if ADT preference for Android SDK is defined (take sdk path from it), * false otherwise (take sdk path from environment variable) * @return resolved path to Android SDK */ private static String getSdkPath(boolean preferenceDefined) { String sdkPath = null; if (!preferenceDefined) { // sdk was not defined - take from environment variable String pathVariable = System.getenv("PATH"); //$NON-NLS-1$ String pathSeparator = System.getProperty("path.separator"); //$NON-NLS-1$ String subPath = null; File checkedPath = null; String[] folderList = null; StringTokenizer token = new StringTokenizer(pathVariable, pathSeparator); while (token.hasMoreTokens()) { subPath = token.nextToken(); checkedPath = new File(subPath); if (checkedPath.isDirectory()) { folderList = checkedPath.list(); for (String s : folderList) { if (s.equals("emulator") || s.equals("emulator.exe") || s.equals("adb") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ || s.equals("adb.exe")) //$NON-NLS-1$ { File root = checkedPath.getParentFile(); sdkPath = root.getAbsolutePath(); break; } } } } } else { // sdk was defined on execution sdkPath = getSDKPathByPreference(); } return sdkPath; } /** * Retrieves all activity actions from a given {@link IProject}. * @param project The android project. * @return An {@link String} array containing all the activity actions from the given project. * @throws AndroidException if an error occurred while attempting to get the activity actions. */ public static String[] getActivityActions(IProject project) throws AndroidException { String[] activityActions = new String[1]; File androidTargetFile = AndroidUtils.getAndroidTargetPathForProject(project.getLocation().toFile()); TargetDataReader targetDataReader = new TargetDataReader(androidTargetFile); try { activityActions = targetDataReader.getActivityActions().toArray(activityActions); } catch (IOException e) { throw new AndroidException(e); } return activityActions; } /** * Retrieves all receiver actions from a given {@link IProject}. * @param project The android project. * @return An {@link String} array containing all the receiver actions from the given project. * @throws AndroidException if an error occurred while attempting to get the receiver actions. */ public static String[] getReceiverActions(IProject project) throws AndroidException { String[] receiverActions = new String[1]; File androidTargetFile = AndroidUtils.getAndroidTargetPathForProject(project.getLocation().toFile()); TargetDataReader targetDataReader = new TargetDataReader(androidTargetFile); try { receiverActions = targetDataReader.getReceiverActions().toArray(receiverActions); } catch (IOException e) { throw new AndroidException(e); } return receiverActions; } /** * Retrieves all intent filter categories from a given {@link IProject}. * @param project The android project. * @return An {@link String} array containing all the intent filter categories from the given project. * @throws AndroidException if an error occurred while attempting to get the intent filter categories. */ public static String[] getIntentFilterCategories(IProject project) throws AndroidException { String[] intentFilterCategories = new String[1]; File androidTargetFile = AndroidUtils.getAndroidTargetPathForProject(project.getLocation().toFile()); TargetDataReader targetDataReader = new TargetDataReader(androidTargetFile); try { intentFilterCategories = targetDataReader.getIntentFilterCategories().toArray(intentFilterCategories); } catch (IOException e) { throw new AndroidException(e); } return intentFilterCategories; } /** * Retrieves all service actions from a given {@link IProject}. * @param project The android project. * @return An {@link String} array containing all the service actions from the given project. * @throws AndroidException if an error occurred while attempting to get the service actions. */ public static String[] getServiceActions(IProject project) throws AndroidException { String[] serviceActions = new String[1]; File androidTargetFile = AndroidUtils.getAndroidTargetPathForProject(project.getLocation().toFile()); TargetDataReader targetDataReader = new TargetDataReader(androidTargetFile); try { serviceActions = targetDataReader.getServiceActions().toArray(serviceActions); } catch (IOException e) { throw new AndroidException(e); } return serviceActions; } /** * Get the api version number for a given project. * This method accesses ADT via reflection. If an error occurred an AndroidException will be thrown. * @param project: the project * @return the api version number or 0 if some error occurs * @throws Exception if any error occurred while attempting to access the ADT via reflection. */ public static int getApiVersionNumberForProject(IProject project) throws AndroidException { int api = 0; try { //reflection for: Sdk sdk = Sdk.getCurrent(); Class<?> clsSdk = Class.forName(CLASS_COM_ANDROID_IDE_ECLIPSE_ADT_INTERNAL_SDK_SDK); Method mtdGetCurrent = clsSdk.getMethod("getCurrent", (Class[]) null); //$NON-NLS-1$ Object sdk = mtdGetCurrent.invoke(null, (Object[]) null); //reflection for: IAndroidTarget target = sdk.getTarget(project); Method mtdGetTarget = clsSdk.getMethod("getTarget", IProject.class); //$NON-NLS-1$ Object target = mtdGetTarget.invoke(sdk, project); //reflection for: AndroidVersion version = target.getVersion(target); Class<?> clsAndroidVersion = Class.forName(CLASS_COM_ANDROID_SDKLIB_ANDROID_VERSION); Class<?> interfaceIAndroidTarget = Class.forName(CLASS_COM_ANDROID_SDKLIB_I_ANDROID_TARGET); Method mtdGetVersion = interfaceIAndroidTarget.getMethod("getVersion", (Class[]) null); //$NON-NLS-1$ Object version = mtdGetVersion.invoke(target, (Object[]) null); if (version != null) { //reflection for: version.getApiLevel(); Method mtdGetApiLevel = clsAndroidVersion.getMethod("getApiLevel", (Class[]) null); //$NON-NLS-1$ Object apiLevel = mtdGetApiLevel.invoke(version, (Object[]) null); api = (Integer) apiLevel; } } catch (Exception e) { StudioLogger.info("It was not possible to reach ADT methods (reflection break)", e.getMessage()); throw new AndroidException( UtilitiesNLS.AndroidUtils_NotPossibleToGetAPIVersionNumber_Error); } return api; } /** * Reads permissions.txt file inside plugin and creates the list of permissions available in the target * (the list is based on the class <code>Manifest.Permission</code> from Android) * * <br><br> * It appends android.permission. to the items, * the unique exceptions are: * -com.android.alarm.permission.SET_ALARM * -com.android.browser.permission.READ_HISTORY_BOOKMARKS * -com.android.browser.permission.WRITE_HISTORY_BOOKMARKS * -com.android.voicemail.permission.ADD_VOICEMAIL * * @see http://developer.android.com/reference/android/Manifest.permission.html * @return list of Intent Filters Permissions available * @throws IOException if file not found, or if there is any problem reading the permissions file */ private static List<String> getIntentFilterPermissions() throws IOException { //path inside <plugin>\files\permissions.txt URL permissionsURL = null; Bundle bundle = Platform.getBundle(CommonPlugin.PLUGIN_ID); permissionsURL = bundle.getEntry((new StringBuilder(IPath.SEPARATOR)).append( "files" + IPath.SEPARATOR + "permissions.txt") //$NON-NLS-1$ //$NON-NLS-2$ .toString()); List<String> items = new ArrayList<String>(); InputStream is = null; BufferedReader bufferedReader = null; try { if (permissionsURL != null) { is = permissionsURL.openStream(); } if (is != null) { bufferedReader = new BufferedReader(new InputStreamReader(is)); String line; while ((line = bufferedReader.readLine()) != null) { String prefix = ""; //$NON-NLS-1$ if (!permissionNameToPrefixToAppend.containsKey(line.trim())) { //general rule - append android.permission. prefix = "android.permission"; //$NON-NLS-1$ } else { //exception rule - append the prefix available in the map prefix = permissionNameToPrefixToAppend.get(line.trim()); } items.add(prefix + "." + line.trim()); //$NON-NLS-1$ } } } finally { if (is != null) { is.close(); } if (bufferedReader != null) { bufferedReader.close(); } } return items; } }