auto import from //depot/cupcake/@137055
diff --git a/apps/CustomLocale/Android.mk b/apps/CustomLocale/Android.mk
new file mode 100644
index 0000000..275207f
--- /dev/null
+++ b/apps/CustomLocale/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := CustomLocale
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/apps/CustomLocale/AndroidManifest.xml b/apps/CustomLocale/AndroidManifest.xml
new file mode 100644
index 0000000..9dfa896
--- /dev/null
+++ b/apps/CustomLocale/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:versionCode="1"
+ android:versionName="1.0" package="com.android.customlocale">
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name">
+ <activity
+ android:label="@string/app_name" android:name="CustomLocaleActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name="NewLocaleDialog"
+ android:theme="@android:style/Theme.Dialog" />
+ </application>
+ <uses-sdk android:minSdkVersion="3" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+</manifest>
diff --git a/apps/CustomLocale/res/drawable/icon.png b/apps/CustomLocale/res/drawable/icon.png
new file mode 100644
index 0000000..cb40a19
--- /dev/null
+++ b/apps/CustomLocale/res/drawable/icon.png
Binary files differ
diff --git a/apps/CustomLocale/res/layout/list_item.xml b/apps/CustomLocale/res/layout/list_item.xml
new file mode 100644
index 0000000..8c59f92
--- /dev/null
+++ b/apps/CustomLocale/res/layout/list_item.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="5dip">
+ <TextView
+ android:id="@+id/locale_code"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:textAppearanceLarge"
+ android:layout_weight="1"
+ android:text="@string/locale_default" />
+ <TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/locale_name"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:textAppearance"
+ android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CustomLocale/res/layout/main.xml b/apps/CustomLocale/res/layout/main.xml
new file mode 100644
index 0000000..b1eaa51
--- /dev/null
+++ b/apps/CustomLocale/res/layout/main.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:text="@string/header_current_locale"
+ android:textAppearance="@style/TextAppearance.header"
+ android:gravity="center_horizontal"
+ android:background="@color/header_background" />
+ <TextView
+ android:id="@+id/current_locale"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:padding="5dip" />
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:text="@string/header_locale_list"
+ android:textAppearance="@style/TextAppearance.header"
+ android:gravity="center_horizontal"
+ android:background="@color/header_background" />
+ <ListView
+ android:id="@id/android:list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:padding="8dip" />
+ <TextView
+ android:id="@id/android:empty"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:text="@string/no_data_label" />
+ <Button
+ android:id="@+id/new_locale"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip"
+ android:text="@string/add_new_locale_button" />
+</LinearLayout>
diff --git a/apps/CustomLocale/res/layout/new_locale.xml b/apps/CustomLocale/res/layout/new_locale.xml
new file mode 100644
index 0000000..d0d9d6c
--- /dev/null
+++ b/apps/CustomLocale/res/layout/new_locale.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/new_locale_label" />
+ <EditText
+ android:id="@+id/value"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/locale_default" android:inputType="text"/>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <Button
+ android:id="@+id/add"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/add_button" android:layout_gravity="center_vertical"/>
+ <Button
+ android:id="@+id/add_and_select"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/add_select_button" android:layout_gravity="center_vertical"/>
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CustomLocale/res/values-fr/strings.xml b/apps/CustomLocale/res/values-fr/strings.xml
new file mode 100644
index 0000000..8852a9a
--- /dev/null
+++ b/apps/CustomLocale/res/values-fr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources>
+ <string name="app_name">Locales Personalisées</string>
+ <string name="add_new_locale_button">Ajouter une nouvelle locale</string>
+ <string name="new_locale_label">Code de la nouvelle locale:</string>
+ <string name="add_button">Ajouter</string>
+ <string name="no_data_label">Aucune locale</string>
+ <string name="header_current_locale">Locale courrante</string>
+ <string name="header_locale_list">Liste des locales</string>
+ <string name="add_select_button">Ajouter et sélectionner</string>
+</resources>
diff --git a/apps/CustomLocale/res/values/colors.xml b/apps/CustomLocale/res/values/colors.xml
new file mode 100644
index 0000000..b9b5ad2
--- /dev/null
+++ b/apps/CustomLocale/res/values/colors.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources>
+ <color name="header_background">#888</color>
+</resources>
diff --git a/apps/CustomLocale/res/values/strings.xml b/apps/CustomLocale/res/values/strings.xml
new file mode 100644
index 0000000..313fb87
--- /dev/null
+++ b/apps/CustomLocale/res/values/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources>
+ <string name="app_name">Custom Locale</string>
+ <string name="locale_default">ex_EX</string>
+ <string name="add_new_locale_button">Add New Locale</string>
+ <string name="new_locale_label">New locale code:</string>
+ <string name="add_button">Add</string>
+ <string name="no_data_label">No data</string>
+ <string name="header_current_locale">Current Locale</string>
+ <string name="header_locale_list">Locale List</string>
+ <string name="add_select_button">Add and Select</string>
+</resources>
diff --git a/apps/CustomLocale/res/values/styles.xml b/apps/CustomLocale/res/values/styles.xml
new file mode 100644
index 0000000..8ccc11c
--- /dev/null
+++ b/apps/CustomLocale/res/values/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+<resources>
+ <style name="TextAppearance"
+ parent="android:TextAppearance" />
+
+ <style name="TextAppearance.header">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textStyle">bold</item>
+ </style>
+
+</resources>
diff --git a/apps/CustomLocale/src/com/android/customlocale/CustomLocaleActivity.java b/apps/CustomLocale/src/com/android/customlocale/CustomLocaleActivity.java
new file mode 100644
index 0000000..768f910
--- /dev/null
+++ b/apps/CustomLocale/src/com/android/customlocale/CustomLocaleActivity.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2009 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.customlocale;
+
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.ListActivity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.Button;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Displays the list of system locales as well as maintain a custom list of user
+ * locales. The user can select a locale and apply it or it can create or remove
+ * a custom locale.
+ */
+public class CustomLocaleActivity extends ListActivity {
+
+ private static final String CUSTOM_LOCALES_SEP = " ";
+ private static final String CUSTOM_LOCALES = "custom_locales";
+ private static final String KEY_CUSTOM = "custom";
+ private static final String KEY_NAME = "name";
+ private static final String KEY_CODE = "code";
+
+ private static final String TAG = "LocaleSetup";
+ private static final boolean DEBUG = true;
+
+ /** Request code returned when the NewLocaleDialog activity finishes. */
+ private static final int UPDATE_LIST = 42;
+ /** Menu item id for applying a locale */
+ private static final int MENU_APPLY = 43;
+ /** Menu item id for removing a custom locale */
+ private static final int MENU_REMOVE = 44;
+
+ /** List view displaying system and custom locales. */
+ private ListView mListView;
+ /** Textview used to display current locale */
+ private TextView mCurrentLocaleTextView;
+ /** Private shared preferences of this activity. */
+ private SharedPreferences mPrefs;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mPrefs = getPreferences(MODE_PRIVATE);
+
+ Button newLocaleButton = (Button) findViewById(R.id.new_locale);
+
+ newLocaleButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ Intent i = new Intent(CustomLocaleActivity.this, NewLocaleDialog.class);
+ startActivityForResult(i, UPDATE_LIST);
+ }
+ });
+
+ mListView = (ListView) findViewById(android.R.id.list);
+ mListView.setFocusable(true);
+ mListView.setFocusableInTouchMode(true);
+ mListView.requestFocus();
+ registerForContextMenu(mListView);
+ setupLocaleList();
+
+ mCurrentLocaleTextView = (TextView) findViewById(R.id.current_locale);
+ displayCurrentLocale();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == UPDATE_LIST && resultCode == RESULT_OK && data != null) {
+ String locale = data.getExtras().getString(NewLocaleDialog.INTENT_EXTRA_LOCALE);
+ if (locale != null && locale.length() > 0) {
+ // Get current custom locale list
+ String customLocales = mPrefs.getString(CUSTOM_LOCALES, null);
+
+ // Update
+ if (customLocales == null) {
+ customLocales = locale;
+ } else {
+ customLocales += CUSTOM_LOCALES_SEP + locale;
+ }
+
+ // Save prefs
+ if (DEBUG) {
+ Log.d(TAG, "add/customLocales: " + customLocales);
+ }
+ mPrefs.edit().putString(CUSTOM_LOCALES, customLocales).commit();
+
+ Toast.makeText(this, "Added custom locale: " + locale, Toast.LENGTH_SHORT).show();
+
+ // Update list view
+ setupLocaleList();
+
+ // Find the item to select it in the list view
+ ListAdapter a = mListView.getAdapter();
+ for (int i = 0; i < a.getCount(); i++) {
+ Object o = a.getItem(i);
+ if (o instanceof Map<?, ?>) {
+ String code = ((Map<String, String>) o).get(KEY_CODE);
+ if (code != null && code.equals(locale)) {
+ mListView.setSelection(i);
+ break;
+ }
+ }
+ }
+
+ if (data.getExtras().getBoolean(NewLocaleDialog.INTENT_EXTRA_SELECT)) {
+ selectLocale(locale);
+ }
+ }
+ }
+ }
+
+ private void setupLocaleList() {
+ if (DEBUG) {
+ Log.d(TAG, "Update locate list");
+ }
+
+ ArrayList<Map<String, String>> data = new ArrayList<Map<String, String>>();
+
+ // Insert all system locales
+ String[] locales = getAssets().getLocales();
+ for (String locale : locales) {
+ Locale loc = new Locale(locale);
+
+ Map<String, String> map = new HashMap<String, String>(1);
+ map.put(KEY_CODE, locale);
+ map.put(KEY_NAME, loc.getDisplayName());
+ data.add(map);
+ }
+ locales = null;
+
+ // Insert all custom locales
+ String customLocales = mPrefs.getString(CUSTOM_LOCALES, "");
+ if (DEBUG) {
+ Log.d(TAG, "customLocales: " + customLocales);
+ }
+ for (String locale : customLocales.split(CUSTOM_LOCALES_SEP)) {
+ if (locale != null && locale.length() > 0) {
+ Locale loc = new Locale(locale);
+
+ Map<String, String> map = new HashMap<String, String>(1);
+ map.put(KEY_CODE, locale);
+ map.put(KEY_NAME, loc.getDisplayName() + " [Custom]");
+ // the presence of the "custom" key marks it as custom.
+ map.put(KEY_CUSTOM, "");
+ data.add(map);
+ }
+ }
+
+ // Sort all locales by code
+ Collections.sort(data, new Comparator<Map<String, String>>() {
+ public int compare(Map<String, String> lhs, Map<String, String> rhs) {
+ return lhs.get(KEY_CODE).compareTo(rhs.get(KEY_CODE));
+ }
+ });
+
+ // Update the list view adapter
+ mListView.setAdapter(new SimpleAdapter(this, data, R.layout.list_item, new String[] {
+ KEY_CODE, KEY_NAME}, new int[] {R.id.locale_code, R.id.locale_name}));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+
+ if (menuInfo instanceof AdapterContextMenuInfo) {
+ int position = ((AdapterContextMenuInfo) menuInfo).position;
+ Object o = mListView.getItemAtPosition(position);
+ if (o instanceof Map<?, ?>) {
+ String locale = ((Map<String, String>) o).get(KEY_CODE);
+ String custom = ((Map<String, String>) o).get(KEY_CUSTOM);
+
+ if (custom == null) {
+ menu.setHeaderTitle("System Locale");
+ menu.add(0, MENU_APPLY, 0, "Apply");
+ } else {
+ menu.setHeaderTitle("Custom Locale");
+ menu.add(0, MENU_APPLY, 0, "Apply");
+ menu.add(0, MENU_REMOVE, 0, "Remove");
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+
+ String pendingLocale = null;
+ boolean is_custom = false;
+
+ ContextMenuInfo menuInfo = item.getMenuInfo();
+ if (menuInfo instanceof AdapterContextMenuInfo) {
+ int position = ((AdapterContextMenuInfo) menuInfo).position;
+ Object o = mListView.getItemAtPosition(position);
+ if (o instanceof Map<?, ?>) {
+ pendingLocale = ((Map<String, String>) o).get(KEY_CODE);
+ is_custom = ((Map<String, String>) o).get(KEY_CUSTOM) != null;
+ }
+ }
+
+ if (pendingLocale == null) {
+ // should never happen
+ return super.onContextItemSelected(item);
+ }
+
+ if (item.getItemId() == MENU_REMOVE) {
+ // Get current custom locale list
+ String customLocales = mPrefs.getString(CUSTOM_LOCALES, "");
+
+ if (DEBUG) {
+ Log.d(TAG, "Remove " + pendingLocale + " from custom locales: " + customLocales);
+ }
+
+ // Update
+ StringBuilder sb = new StringBuilder();
+ for (String locale : customLocales.split(CUSTOM_LOCALES_SEP)) {
+ if (locale != null && locale.length() > 0 && !locale.equals(pendingLocale)) {
+ if (sb.length() > 0) {
+ sb.append(CUSTOM_LOCALES_SEP);
+ }
+ sb.append(locale);
+ }
+ }
+ String newLocales = sb.toString();
+ if (!newLocales.equals(customLocales)) {
+ // Save prefs
+ mPrefs.edit().putString(CUSTOM_LOCALES, customLocales).commit();
+
+ Toast.makeText(this, "Removed custom locale: " + pendingLocale, Toast.LENGTH_SHORT)
+ .show();
+ }
+
+ } else if (item.getItemId() == MENU_APPLY) {
+ selectLocale(pendingLocale);
+ }
+
+ return super.onContextItemSelected(item);
+ }
+
+ private void selectLocale(String locale) {
+ if (DEBUG) {
+ Log.d(TAG, "Select locale " + locale);
+ }
+
+ try {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ Configuration config = am.getConfiguration();
+
+ Locale loc = new Locale(locale);
+ config.locale = loc;
+
+ // indicate this isn't some passing default - the user wants this
+ // remembered
+ config.userSetLocale = true;
+
+ am.updateConfiguration(config);
+
+ Toast.makeText(this, "Select locale: " + locale, Toast.LENGTH_SHORT).show();
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Log.e(TAG, "Select locale failed", e);
+ }
+ }
+ }
+
+ private void displayCurrentLocale() {
+ try {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ Configuration config = am.getConfiguration();
+
+ if (config.locale != null) {
+ String text = String.format("%s - %s",
+ config.locale.toString(),
+ config.locale.getDisplayName());
+ mCurrentLocaleTextView.setText(text);
+ }
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Log.e(TAG, "get current locale failed", e);
+ }
+ }
+ }
+}
diff --git a/apps/CustomLocale/src/com/android/customlocale/NewLocaleDialog.java b/apps/CustomLocale/src/com/android/customlocale/NewLocaleDialog.java
new file mode 100644
index 0000000..3626f73
--- /dev/null
+++ b/apps/CustomLocale/src/com/android/customlocale/NewLocaleDialog.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2009 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.customlocale;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+/**
+ * Dialog to ask the user for a new locale. <p/> Returns the locale code (e.g.
+ * "en_US") via an intent with a "locale" extra string and a "select" extra
+ * boolean.
+ */
+public class NewLocaleDialog extends Activity
+ implements View.OnClickListener, View.OnKeyListener {
+
+ public static final String INTENT_EXTRA_LOCALE = "locale";
+ public static final String INTENT_EXTRA_SELECT = "select";
+
+ private static final String TAG = "NewLocale";
+ private static final boolean DEBUG = true;
+ private Button mButtonAdd;
+ private Button mButtonAddSelect;
+ private EditText mEditText;
+ private boolean mWasEmpty;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.new_locale);
+
+ mEditText = (EditText) findViewById(R.id.value);
+ mWasEmpty = true;
+
+ mButtonAdd = (Button) findViewById(R.id.add);
+ mButtonAdd.setOnClickListener(this);
+ mButtonAdd.setEnabled(false);
+
+ mButtonAddSelect = (Button) findViewById(R.id.add_and_select);
+ mButtonAddSelect.setOnClickListener(this);
+ mButtonAddSelect.setEnabled(false);
+
+ mEditText.setOnKeyListener(this);
+ }
+
+ public void onClick(View v) {
+ String locale = mEditText.getText().toString();
+ boolean select = v == mButtonAddSelect;
+
+ if (DEBUG) {
+ Log.d(TAG, "New Locale: " + locale + (select ? " + select" : ""));
+ }
+
+ Intent data = new Intent(NewLocaleDialog.this, NewLocaleDialog.class);
+ data.putExtra(INTENT_EXTRA_LOCALE, locale);
+ data.putExtra(INTENT_EXTRA_SELECT, select);
+ setResult(RESULT_OK, data);
+
+ finish();
+ }
+
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ boolean isEmpty = TextUtils.isEmpty(mEditText.getText());
+ if (isEmpty != mWasEmpty) {
+ mWasEmpty = isEmpty;
+
+ mButtonAdd.setEnabled(!isEmpty);
+ mButtonAddSelect.setEnabled(!isEmpty);
+ }
+ return false;
+ }
+}
diff --git a/apps/SdkSetup/Android.mk b/apps/SdkSetup/Android.mk
index b89baaf..538c2cb 100644
--- a/apps/SdkSetup/Android.mk
+++ b/apps/SdkSetup/Android.mk
@@ -1,7 +1,7 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := foo
+LOCAL_MODULE_TAGS := user
LOCAL_SRC_FILES := $(call all-subdir-java-files)
diff --git a/apps/SpareParts/res/values/strings.xml b/apps/SpareParts/res/values/strings.xml
index 42234c1..21c2df9 100644
--- a/apps/SpareParts/res/values/strings.xml
+++ b/apps/SpareParts/res/values/strings.xml
@@ -20,6 +20,17 @@
<resources>
<string name="app_label">Spare Parts</string>
+ <string name="device_info_title">Device info</string>
+
+ <string name="title_battery_history">Battery history</string>
+ <string name="summary_battery_history">Summary of how battery has been used</string>
+
+ <string name="title_battery_information">Battery information</string>
+ <string name="summary_battery_information">Current battery status information</string>
+
+ <string name="title_usage_statistics">Usage statistics</string>
+ <string name="summary_usage_statistics">Summary of application usage</string>
+
<string name="general_title">General</string>
<string name="title_window_animations">Window animations</string>
@@ -48,7 +59,7 @@
<string name="title_accelerometer">Display rotation</string>
<string name="summary_on_accelerometer">Display rotates from orientation</string>
- <string name="summary_off_accelerometer">Display rotates when lid is open</string>
+ <string name="summary_off_accelerometer">Display rotates from orientation</string>
<string name="applications_title">Applications</string>
diff --git a/apps/SpareParts/res/xml/spare_parts.xml b/apps/SpareParts/res/xml/spare_parts.xml
index 87525c9..5141e22 100644
--- a/apps/SpareParts/res/xml/spare_parts.xml
+++ b/apps/SpareParts/res/xml/spare_parts.xml
@@ -19,6 +19,35 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
+ android:title="@string/device_info_title">
+
+ <PreferenceScreen android:key="battery_history_settings"
+ android:title="@string/title_battery_history"
+ android:summary="@string/summary_battery_history">
+ <intent android:action="android.intent.action.MAIN"
+ android:targetPackage="com.android.settings"
+ android:targetClass="com.android.settings.battery_history.BatteryHistory" />
+ </PreferenceScreen>
+
+ <PreferenceScreen android:key="battery_information_settings"
+ android:title="@string/title_battery_information"
+ android:summary="@string/summary_battery_information">
+ <intent android:action="android.intent.action.MAIN"
+ android:targetPackage="com.android.settings"
+ android:targetClass="com.android.settings.BatteryInfo" />
+ </PreferenceScreen>
+
+ <PreferenceScreen android:key="usage_statistics_settings"
+ android:title="@string/title_usage_statistics"
+ android:summary="@string/summary_usage_statistics">
+ <intent android:action="android.intent.action.MAIN"
+ android:targetPackage="com.android.settings"
+ android:targetClass="com.android.settings.UsageStats" />
+ </PreferenceScreen>
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
android:title="@string/general_title">
<ListPreference
diff --git a/apps/SpareParts/src/com/android/spare_parts/SpareParts.java b/apps/SpareParts/src/com/android/spare_parts/SpareParts.java
index 0f2347e..4852ec2 100644
--- a/apps/SpareParts/src/com/android/spare_parts/SpareParts.java
+++ b/apps/SpareParts/src/com/android/spare_parts/SpareParts.java
@@ -19,7 +19,11 @@
import android.app.ActivityManagerNative;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.os.RemoteException;
@@ -28,6 +32,7 @@
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -35,11 +40,17 @@
import android.util.Log;
import android.view.IWindowManager;
+import java.util.List;
+
public class SpareParts extends PreferenceActivity
implements Preference.OnPreferenceChangeListener,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "SpareParts";
+ private static final String BATTERY_HISTORY_PREF = "battery_history_settings";
+ private static final String BATTERY_INFORMATION_PREF = "battery_information_settings";
+ private static final String USAGE_STATISTICS_PREF = "usage_statistics_settings";
+
private static final String WINDOW_ANIMATIONS_PREF = "window_animations";
private static final String TRANSITION_ANIMATIONS_PREF = "transition_animations";
private static final String FANCY_IME_ANIMATIONS_PREF = "fancy_ime_animations";
@@ -62,6 +73,41 @@
private IWindowManager mWindowManager;
+ public static boolean updatePreferenceToSpecificActivityOrRemove(Context context,
+ PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) {
+
+ Preference preference = parentPreferenceGroup.findPreference(preferenceKey);
+ if (preference == null) {
+ return false;
+ }
+
+ Intent intent = preference.getIntent();
+ if (intent != null) {
+ // Find the activity that is in the system image
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+ int listSize = list.size();
+ for (int i = 0; i < listSize; i++) {
+ ResolveInfo resolveInfo = list.get(i);
+ if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ != 0) {
+
+ // Replace the intent with this specific activity
+ preference.setIntent(new Intent().setClassName(
+ resolveInfo.activityInfo.packageName,
+ resolveInfo.activityInfo.name));
+
+ return true;
+ }
+ }
+ }
+
+ // Did not find a matching activity, so remove the preference
+ parentPreferenceGroup.removePreference(preference);
+
+ return true;
+ }
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -84,7 +130,15 @@
mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
- getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ final PreferenceGroup parentPreference = getPreferenceScreen();
+ updatePreferenceToSpecificActivityOrRemove(this, parentPreference,
+ BATTERY_HISTORY_PREF, 0);
+ updatePreferenceToSpecificActivityOrRemove(this, parentPreference,
+ BATTERY_INFORMATION_PREF, 0);
+ updatePreferenceToSpecificActivityOrRemove(this, parentPreference,
+ USAGE_STATISTICS_PREF, 0);
+
+ parentPreference.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
private void updateToggles() {
diff --git a/build/sdk.atree b/build/sdk.atree
index 4fb768b..0f62ea0 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -66,6 +66,7 @@
development/samples/ApiDemos platforms/${PLATFORM_NAME}/samples/ApiDemos
development/samples/SkeletonApp platforms/${PLATFORM_NAME}/samples/SkeletonApp
development/samples/Snake platforms/${PLATFORM_NAME}/samples/Snake
+development/samples/SoftKeyboard platforms/${PLATFORM_NAME}/samples/SoftKeyboard
# dx
bin/dx platforms/${PLATFORM_NAME}/tools/dx
diff --git a/build/tools/make_windows_sdk.sh b/build/tools/make_windows_sdk.sh
index a32546c..dedf3a7 100755
--- a/build/tools/make_windows_sdk.sh
+++ b/build/tools/make_windows_sdk.sh
@@ -146,6 +146,11 @@
echo "Done"
echo
echo "Resulting SDK is in $DIST_DIR/$DEST_NAME_ZIP"
+
+ # We want fastboot and adb next to the new SDK
+ for i in fastboot.exe adb.exe AdbWinApi.dll; do
+ mv -vf out/host/windows-x86/bin/$i "$DIST_DIR"/$i
+ done
}
check
diff --git a/pdk/docs/templates/footer.cs b/pdk/docs/templates/footer.cs
new file mode 100755
index 0000000..bb82c8d
--- /dev/null
+++ b/pdk/docs/templates/footer.cs
@@ -0,0 +1,19 @@
+<div id="footer">
+
+<?cs if:reference||guide ?>
+ <div id="copyright">
+ <?cs call:custom_copyright() ?>
+ </div>
+ <div id="build_info">
+ <?cs call:custom_buildinfo() ?>
+ </div>
+<?cs elif:!hide_license_footer ?>
+ <div id="copyright">
+ <?cs call:custom_cc_copyright() ?>
+ </div>
+<?cs /if ?>
+ <div id="footerlinks">
+ <?cs call:custom_footerlinks() ?>
+ </div>
+
+</div> <!-- end footer -->
diff --git a/pdk/docs/templates/head_tag.cs b/pdk/docs/templates/head_tag.cs
new file mode 100755
index 0000000..55d0225
--- /dev/null
+++ b/pdk/docs/templates/head_tag.cs
@@ -0,0 +1,37 @@
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<link rel="shortcut icon" type="image/x-icon" href="<?cs var:toroot ?>favicon.ico" />
+<title><?cs
+ if:page.title ?><?cs
+ var:page.title ?><?cs
+ if:sdk.version ?> (<?cs
+ var:sdk.version ?>)<?cs
+ /if ?> | <?cs
+ /if ?>Android Developers</title><?cs
+if:guide||sdk ?>
+<link href="<?cs var:toroot ?>assets/android-developer-docs-devguide.css" rel="stylesheet" type="text/css" /><?cs
+else ?>
+<link href="<?cs var:toroot ?>assets/android-developer-docs.css" rel="stylesheet" type="text/css" /><?cs
+/if ?>
+<script src="<?cs var:toroot ?>assets/search_autocomplete.js" type="text/javascript"></script>
+<script src="<?cs var:toroot ?>reference/lists.js" type="text/javascript"></script>
+<script src="<?cs var:toroot ?>assets/jquery-resizable.min.js" type="text/javascript"></script>
+<script src="<?cs var:toroot ?>assets/android-developer-docs.js" type="text/javascript"></script>
+<script type="text/javascript">
+ setToRoot("<?cs var:toroot ?>");
+</script><?cs
+if:reference ?>
+<script src="<?cs var:toroot ?>navtree_data.js" type="text/javascript"></script>
+<script src="<?cs var:toroot ?>assets/navtree.js" type="text/javascript"></script><?cs
+/if ?>
+<noscript>
+ <style type="text/css">
+ body{overflow:auto;}
+ #body-content{position:relative; top:0;}
+ #doc-content{overflow:visible;border-left:3px solid #666;}
+ #side-nav{padding:0;}
+ #side-nav .toggle-list ul {display:block;}
+ #resize-packages-nav{border-bottom:3px solid #666;}
+ </style>
+</noscript>
+</head>
diff --git a/pdk/docs/templates/header.cs b/pdk/docs/templates/header.cs
new file mode 100755
index 0000000..e8301be
--- /dev/null
+++ b/pdk/docs/templates/header.cs
@@ -0,0 +1,3 @@
+<?cs call:custom_masthead() ?>
+<?cs call:custom_left_nav() ?>
+
diff --git a/pdk/docs/templates/index.cs b/pdk/docs/templates/index.cs
new file mode 100755
index 0000000..15a6a59
--- /dev/null
+++ b/pdk/docs/templates/index.cs
@@ -0,0 +1,8 @@
+<html>
+<head>
+<meta http-equiv="refresh" content="0;url=packages.html">
+</head>
+<body>
+<?cs include:"analytics.cs" ?>
+</body>
+</html>
\ No newline at end of file
diff --git a/pdk/docs/templates/trailer.cs b/pdk/docs/templates/trailer.cs
new file mode 100755
index 0000000..155ba58
--- /dev/null
+++ b/pdk/docs/templates/trailer.cs
@@ -0,0 +1,11 @@
+</div> <!-- end body-content --> <?cs # normally opened by header.cs ?>
+
+<script type="text/javascript">
+init(); /* initialize android-developer-docs.js */
+var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+</script>
+<script type="text/javascript">
+var pageTracker = _gat._getTracker("UA-5831155-1");
+pageTracker._trackPageview();
+</script>
\ No newline at end of file
diff --git a/samples/Home/src/com/example/android/home/ApplicationsStackLayout.java b/samples/Home/src/com/example/android/home/ApplicationsStackLayout.java
index a2073fa..ccc1f43 100644
--- a/samples/Home/src/com/example/android/home/ApplicationsStackLayout.java
+++ b/samples/Home/src/com/example/android/home/ApplicationsStackLayout.java
@@ -105,7 +105,7 @@
a.recycle();
- mIconSize = (int) getResources().getDimension(android.R.dimen.app_icon_size);
+ mIconSize = 42; //(int) getResources().getDimension(android.R.dimen.app_icon_size);
initLayout();
}
diff --git a/samples/Home/src/com/example/android/home/Home.java b/samples/Home/src/com/example/android/home/Home.java
index de67ef4..7e94cc3 100644
--- a/samples/Home/src/com/example/android/home/Home.java
+++ b/samples/Home/src/com/example/android/home/Home.java
@@ -21,28 +21,26 @@
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.ColorFilter;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.os.Bundle;
-import android.os.Handler;
+import android.os.Environment;
import android.util.Log;
+import android.util.Xml;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -57,14 +55,19 @@
import android.widget.CheckBox;
import android.widget.GridView;
import android.widget.TextView;
-import android.net.Uri;
import java.io.IOException;
+import java.io.FileReader;
+import java.io.File;
+import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
public class Home extends Activity {
/**
* Tag used for logging errors.
@@ -76,6 +79,13 @@
*/
private static final String KEY_SAVE_GRID_OPENED = "grid.opened";
+ private static final String DEFAULT_FAVORITES_PATH = "etc/favorites.xml";
+
+ private static final String TAG_FAVORITES = "favorites";
+ private static final String TAG_FAVORITE = "favorite";
+ private static final String TAG_PACKAGE = "package";
+ private static final String TAG_CLASS = "class";
+
// Identifiers for option menu items
private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1;
private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
@@ -90,11 +100,8 @@
private static ArrayList<ApplicationInfo> mApplications;
private static LinkedList<ApplicationInfo> mFavorites;
- private Handler mHandler = new Handler();
-
private final BroadcastReceiver mWallpaperReceiver = new WallpaperIntentReceiver();
private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver();
- private final ContentObserver mObserver = new FavoritesChangeObserver();
private GridView mGrid;
@@ -120,7 +127,6 @@
setContentView(R.layout.home);
registerIntentReceivers();
- registerContentObservers();
setDefaultWallpaper();
@@ -156,7 +162,6 @@
mApplications.get(i).icon.setCallback(null);
}
- getContentResolver().unregisterContentObserver(mObserver);
unregisterReceiver(mWallpaperReceiver);
unregisterReceiver(mApplicationsReceiver);
}
@@ -199,16 +204,6 @@
}
/**
- * Registers various content observers. The current implementation registers
- * only a favorites observer to keep track of the favorites applications.
- */
- private void registerContentObservers() {
- ContentResolver resolver = getContentResolver();
- resolver.registerContentObserver(Uri.parse("content://" +
- android.provider.Settings.AUTHORITY + "/favorites?notify=true"), true, mObserver);
- }
-
- /**
* Creates a new appplications adapter for the grid view and registers it.
*/
private void bindApplications() {
@@ -247,7 +242,7 @@
Log.e(LOG_TAG, "Failed to clear wallpaper " + e);
}
} else {
- getWindow().setBackgroundDrawable(wallpaper);
+ getWindow().setBackgroundDrawable(new ClippedDrawable(wallpaper));
}
mWallpaperChecked = true;
}
@@ -259,18 +254,17 @@
*/
private void bindFavorites(boolean isLaunching) {
if (!isLaunching || mFavorites == null) {
- final Cursor c = getContentResolver().query(Uri.parse("content://" +
- android.provider.Settings.AUTHORITY + "/favorites?notify=true"),
- null, null, null, "cellX");
- final int intentIndex = c.getColumnIndexOrThrow("intent");
- final int titleIndex = c.getColumnIndexOrThrow("title");
- final int typeIndex = c.getColumnIndexOrThrow("itemType");
+ FileReader favReader;
- final PackageManager manager = getPackageManager();
-
- ApplicationInfo info;
- String intentDescription;
+ // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
+ final File favFile = new File(Environment.getRootDirectory(), DEFAULT_FAVORITES_PATH);
+ try {
+ favReader = new FileReader(favFile);
+ } catch (FileNotFoundException e) {
+ Log.e(LOG_TAG, "Couldn't find or open favorites file " + favFile);
+ return;
+ }
if (mFavorites == null) {
mFavorites = new LinkedList<ApplicationInfo>();
@@ -278,38 +272,77 @@
mFavorites.clear();
}
- while (c.moveToNext()) {
- final int itemType = c.getInt(typeIndex);
+ final Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
- if (itemType == 0 || // 0 == application
- itemType == 1) { // 1 == shortcut
+ final PackageManager packageManager = getPackageManager();
- intentDescription = c.getString(intentIndex);
- if (intentDescription == null) {
- continue;
+ try {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(favReader);
+
+ beginDocument(parser, TAG_FAVORITES);
+
+ ApplicationInfo info;
+
+ while (true) {
+ nextElement(parser);
+
+ String name = parser.getName();
+ if (!TAG_FAVORITE.equals(name)) {
+ break;
}
- Intent intent;
- try {
- intent = Intent.getIntent(intentDescription);
- } catch (java.net.URISyntaxException e) {
- continue;
- }
- info = getApplicationInfo(manager, intent);
+ final String favoritePackage = parser.getAttributeValue(null, TAG_PACKAGE);
+ final String favoriteClass = parser.getAttributeValue(null, TAG_CLASS);
+
+ final ComponentName cn = new ComponentName(favoritePackage, favoriteClass);
+ intent.setComponent(cn);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ info = getApplicationInfo(packageManager, intent);
if (info != null) {
- info.title = c.getString(titleIndex);
info.intent = intent;
mFavorites.addFirst(info);
}
}
+ } catch (XmlPullParserException e) {
+ Log.w(LOG_TAG, "Got exception parsing favorites.", e);
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Got exception parsing favorites.", e);
}
-
- c.close();
}
mApplicationsStack.setFavorites(mFavorites);
}
+ private static void beginDocument(XmlPullParser parser, String firstElementName)
+ throws XmlPullParserException, IOException {
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ // Empty
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ if (!parser.getName().equals(firstElementName)) {
+ throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
+ ", expected " + firstElementName);
+ }
+ }
+
+ private static void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException {
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ // Empty
+ }
+ }
+
/**
* Refreshes the recently launched applications stacked over the favorites. The number
* of recents depends on how many favorites are present.
@@ -361,14 +394,6 @@
return info;
}
- /**
- * When the notification that favorites have changed is received, requests
- * a favorites list refresh.
- */
- private void onFavoritesChanged() {
- bindFavorites(false);
- }
-
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -530,7 +555,7 @@
private class WallpaperIntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- getWindow().setBackgroundDrawable(getWallpaper());
+ getWindow().setBackgroundDrawable(new ClippedDrawable(getWallpaper()));
}
}
@@ -548,20 +573,6 @@
}
/**
- * Receives notifications whenever the user favorites have changed.
- */
- private class FavoritesChangeObserver extends ContentObserver {
- public FavoritesChangeObserver() {
- super(mHandler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- onFavoritesChanged();
- }
- }
-
- /**
* GridView adapter to show the list of all installed applications.
*/
private class ApplicationsAdapter extends ArrayAdapter<ApplicationInfo> {
@@ -580,13 +591,12 @@
convertView = inflater.inflate(R.layout.application, parent, false);
}
- //final ImageView imageView = (ImageView) convertView.findViewById(R.id.icon);
Drawable icon = info.icon;
if (!info.filtered) {
- final Resources resources = getContext().getResources();
- int width = (int) resources.getDimension(android.R.dimen.app_icon_size);
- int height = (int) resources.getDimension(android.R.dimen.app_icon_size);
+ //final Resources resources = getContext().getResources();
+ int width = 42;//(int) resources.getDimension(android.R.dimen.app_icon_size);
+ int height = 42;//(int) resources.getDimension(android.R.dimen.app_icon_size);
final int iconWidth = icon.getIntrinsicWidth();
final int iconHeight = icon.getIntrinsicHeight();
@@ -687,4 +697,47 @@
startActivity(app.intent);
}
}
+
+ /**
+ * When a drawable is attached to a View, the View gives the Drawable its dimensions
+ * by calling Drawable.setBounds(). In this application, the View that draws the
+ * wallpaper has the same size as the screen. However, the wallpaper might be larger
+ * that the screen which means it will be automatically stretched. Because stretching
+ * a bitmap while drawing it is very expensive, we use a ClippedDrawable instead.
+ * This drawable simply draws another wallpaper but makes sure it is not stretched
+ * by always giving it its intrinsic dimensions. If the wallpaper is larger than the
+ * screen, it will simply get clipped but it won't impact performance.
+ */
+ private class ClippedDrawable extends Drawable {
+ private final Drawable mWallpaper;
+
+ public ClippedDrawable(Drawable wallpaper) {
+ mWallpaper = wallpaper;
+ }
+
+ @Override
+ public void setBounds(int left, int top, int right, int bottom) {
+ super.setBounds(left, top, right, bottom);
+ // Ensure the wallpaper is as large as it really is, to avoid stretching it
+ // at drawing time
+ mWallpaper.setBounds(left, top, left + mWallpaper.getIntrinsicWidth(),
+ top + mWallpaper.getIntrinsicHeight());
+ }
+
+ public void draw(Canvas canvas) {
+ mWallpaper.draw(canvas);
+ }
+
+ public void setAlpha(int alpha) {
+ mWallpaper.setAlpha(alpha);
+ }
+
+ public void setColorFilter(ColorFilter cf) {
+ mWallpaper.setColorFilter(cf);
+ }
+
+ public int getOpacity() {
+ return mWallpaper.getOpacity();
+ }
+ }
}
diff --git a/samples/SoftKeyboard/res/drawable/sym_keyboard_search.png b/samples/SoftKeyboard/res/drawable/sym_keyboard_search.png
new file mode 100755
index 0000000..127755d
--- /dev/null
+++ b/samples/SoftKeyboard/res/drawable/sym_keyboard_search.png
Binary files differ
diff --git a/samples/SoftKeyboard/res/values/strings.xml b/samples/SoftKeyboard/res/values/strings.xml
index c1e306d..bc645b2 100644
--- a/samples/SoftKeyboard/res/values/strings.xml
+++ b/samples/SoftKeyboard/res/values/strings.xml
@@ -25,9 +25,7 @@
<string name="word_separators">\u0020.,;:!?\n()[]*&@{}/<>_+=|"</string>
<!-- Labels on soft keys -->
- <string name="label_done">Done</string>
- <string name="label_search">Search</string>
- <string name="label_enter">Enter</string>
- <string name="label_next">Next</string>
- <string name="label_previous">Previous</string>
+ <string name="label_go_key">Go</string>
+ <string name="label_next_key">Next</string>
+ <string name="label_send_key">Send</string>
</resources>
diff --git a/samples/SoftKeyboard/src/com/example/android/softkeyboard/LatinKeyboard.java b/samples/SoftKeyboard/src/com/example/android/softkeyboard/LatinKeyboard.java
index 944cefb..1798442 100644
--- a/samples/SoftKeyboard/src/com/example/android/softkeyboard/LatinKeyboard.java
+++ b/samples/SoftKeyboard/src/com/example/android/softkeyboard/LatinKeyboard.java
@@ -20,9 +20,14 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.Keyboard.Key;
+import android.inputmethodservice.Keyboard.Row;
+import android.view.inputmethod.EditorInfo;
public class LatinKeyboard extends Keyboard {
+ private Key mEnterKey;
+
public LatinKeyboard(Context context, int xmlLayoutResId) {
super(context, xmlLayoutResId);
}
@@ -35,7 +40,49 @@
@Override
protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
XmlResourceParser parser) {
- return new LatinKey(res, parent, x, y, parser);
+ Key key = new LatinKey(res, parent, x, y, parser);
+ if (key.codes[0] == 10) {
+ mEnterKey = key;
+ }
+ return key;
+ }
+
+ /**
+ * This looks at the ime options given by the current editor, to set the
+ * appropriate label on the keyboard's enter key (if it has one).
+ */
+ void setImeOptions(Resources res, int options) {
+ if (mEnterKey == null) {
+ return;
+ }
+
+ switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) {
+ case EditorInfo.IME_ACTION_GO:
+ mEnterKey.iconPreview = null;
+ mEnterKey.icon = null;
+ mEnterKey.label = res.getText(R.string.label_go_key);
+ break;
+ case EditorInfo.IME_ACTION_NEXT:
+ mEnterKey.iconPreview = null;
+ mEnterKey.icon = null;
+ mEnterKey.label = res.getText(R.string.label_next_key);
+ break;
+ case EditorInfo.IME_ACTION_SEARCH:
+ mEnterKey.icon = res.getDrawable(
+ R.drawable.sym_keyboard_search);
+ mEnterKey.label = null;
+ break;
+ case EditorInfo.IME_ACTION_SEND:
+ mEnterKey.iconPreview = null;
+ mEnterKey.icon = null;
+ mEnterKey.label = res.getText(R.string.label_send_key);
+ break;
+ default:
+ mEnterKey.icon = res.getDrawable(
+ R.drawable.sym_keyboard_return);
+ mEnterKey.label = null;
+ break;
+ }
}
static class LatinKey extends Keyboard.Key {
diff --git a/samples/SoftKeyboard/src/com/example/android/softkeyboard/SoftKeyboard.java b/samples/SoftKeyboard/src/com/example/android/softkeyboard/SoftKeyboard.java
index 4df4853..656efdf 100644
--- a/samples/SoftKeyboard/src/com/example/android/softkeyboard/SoftKeyboard.java
+++ b/samples/SoftKeyboard/src/com/example/android/softkeyboard/SoftKeyboard.java
@@ -65,11 +65,11 @@
private long mLastShiftTime;
private long mMetaState;
- private Keyboard mSymbolsKeyboard;
- private Keyboard mSymbolsShiftedKeyboard;
- private Keyboard mQwertyKeyboard;
+ private LatinKeyboard mSymbolsKeyboard;
+ private LatinKeyboard mSymbolsShiftedKeyboard;
+ private LatinKeyboard mQwertyKeyboard;
- private Keyboard mCurKeyboard;
+ private LatinKeyboard mCurKeyboard;
private String mWordSeparators;
@@ -208,6 +208,10 @@
// keyboard with no special features.
mCurKeyboard = mQwertyKeyboard;
}
+
+ // Update the label on the enter key, depending on what the application
+ // says it will do.
+ mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions);
}
/**
@@ -504,6 +508,18 @@
}
}
+ public void onText(CharSequence text) {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+ ic.beginBatchEdit();
+ if (mComposing.length() > 0) {
+ commitTyped(ic);
+ }
+ ic.commitText(text, 0);
+ ic.endBatchEdit();
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ }
+
/**
* Update the list of available candidates from the current composing
* text. This will need to be filled in by however you are determining
diff --git a/testrunner/tests.xml b/testrunner/tests.xml
new file mode 100644
index 0000000..8d9c0ab
--- /dev/null
+++ b/testrunner/tests.xml
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!--
+This file contains standard test definitions for the Android platform
+
+Tests are defined by <test> tags with the following attributes
+
+name package [class runner build_path coverage_target continuous]
+
+Where:
+name: Self-descriptive name used to uniquely identify the test
+build_path: File system path, relative to Android build root, to this package's
+ Android.mk file. If omitted, build/sync step for this test will be skipped
+package: Android application package that contains the tests
+class: Optional. Fully qualified Java test class to run.
+runner: Fully qualified InstrumentationTestRunner to execute. If omitted,
+ will default to android.test.InstrumentationTestRunner
+coverage_target: Build name of Android package this test targets - these targets
+ are defined in the coverage_targets.xml file. Used as basis for code
+ coverage metrics. If omitted, code coverage will not be supported for this
+ test
+continuous: Optional boolean. Default is false. Set to true if tests are known
+ to be reliable, and should be included in a continuous test system. false if
+ they are under development.
+
+These attributes map to the following commands:
+(if class is defined)
+ adb shell am instrument -w <package>/<runner>
+(else)
+ adb shell am instrument -w -e class <class> <package>/<runner>
+
+-->
+
+<test-definitions version="1">
+
+<!-- system-wide tests -->
+<test name="framework"
+ build_path="frameworks/base/tests/FrameworkTest"
+ package="com.android.frameworktest.tests"
+ class="com.android.frameworktest.AllTests"
+ coverage_target="framework"
+ continuous="true" />
+
+<test name="android"
+ build_path="frameworks/base/tests/AndroidTests"
+ package="com.android.unit_tests"
+ class="com.android.unit_tests.AndroidTests"
+ coverage_target="framework"
+ continuous="true" />
+
+<test name="smoke"
+ build_path="frameworks/base/tests/SmokeTest"
+ package="com.android.smoketest.tests"
+ coverage_target="framework"
+ continuous="true" />
+
+<test name="core"
+ build_path="frameworks/base/tests/CoreTests"
+ package="android.core"
+ class="android.core.CoreTests"
+ coverage_target="framework"
+ continuous="true" />
+
+<test name="libcore"
+ build_path="frameworks/base/tests/CoreTests"
+ package="android.core"
+ class="android.core.JavaTests"
+ coverage_target="framework" />
+
+<test name="apidemos"
+ build_path="development/samples/ApiDemos"
+ package="com.example.android.apis.tests"
+ coverage_target="ApiDemos"
+ continuous="true" />
+
+<!-- targeted framework tests -->
+<test name="heap"
+ build_path="frameworks/base/tests/AndroidTests"
+ package="com.android.unit_tests"
+ class="com.android.unit_tests.HeapTest"
+ coverage_target="framework" />
+
+<test name="activity"
+ build_path="frameworks/base/tests/AndroidTests"
+ package="com.android.unit_tests"
+ class="com.android.unit_tests.activity.ActivityTests"
+ coverage_target="framework" />
+
+<!-- obsolete?
+<test name="deadlock"
+ build_path="frameworks/base/tests/Deadlock"
+ package="com.android.deadlock.tests"
+ coverage_target="framework" />
+ -->
+
+
+<test name="tablemerger"
+ build_path="frameworks/base/tests/FrameworkTest"
+ package="com.android.frameworktest.tests"
+ class="android.content.AbstractTableMergerTest"
+ coverage_target="framework" />
+
+
+<!-- selected app tests -->
+<test name="browser"
+ build_path="packages/apps/Browser"
+ package="com.android.browser"
+ runner=".BrowserTestRunner"
+ coverage_target="Browser" />
+
+<test name="browserfunc"
+ build_path="packages/apps/Browser"
+ package="com.android.browser"
+ runner=".BrowserFunctionalTestRunner"
+ coverage_target="Browser" />
+
+<test name="calendar"
+ build_path="packages/apps/Calendar/tests"
+ package="com.android.calendar.tests"
+ coverage_target="Calendar"
+ continuous="true" />
+
+<test name="calprov"
+ build_path="packages/providers/CalendarProvider/tests"
+ package="com.android.providers.calendar.tests"
+ coverage_target="CalendarProvider"
+ continuous="true" />
+
+<test name="camera"
+ build_path="packages/apps/Camera/tests"
+ package="com.android.cameratests"
+ runner="CameraInstrumentationTestRunner"
+ coverage_target="Camera" />
+
+<test name="contactsprov"
+ build_path="packages/providers/GoogleContactsProvider/tests"
+ package="com.android.providers.contactstests"
+ coverage_target="ContactsProvider" />
+
+<test name="email"
+ build_path="packages/apps/Email"
+ package="com.android.email.tests"
+ coverage_target="Email"
+ continuous="true" />
+
+<test name="emailsmall"
+ build_path="packages/apps/Email"
+ package="com.android.email.tests"
+ class="com.android.email.SmallTests"
+ coverage_target="Email" />
+
+<test name="media"
+ build_path="frameworks/base/media/tests/MediaFrameworkTest"
+ package="com.android.mediaframeworktest"
+ runner=".MediaFrameworkTestRunner"
+ coverage_target="framework"
+ continuous="true" />
+
+<test name="mediaunit"
+ build_path="frameworks/base/media/tests/MediaFrameworkTest"
+ package="com.android.mediaframeworktest"
+ runner=".MediaFrameworkUnitTestRunner"
+ coverage_target="framework" />
+
+<!-- obsolete?
+<test name="mediaprov"
+ build_path="tests/MediaProvider"
+ package="com.android.mediaprovidertests"
+ runner=".MediaProviderTestsInstrumentation"
+ coverage_target="MediaProvider" />
+ -->
+
+<test name="mms"
+ build_path="packages/apps/Mms"
+ package="com.android.mms.tests"
+ runner="com.android.mms.ui.MMSInstrumentationTestRunner"
+ coverage_target="Mms" />
+
+<test name="mmslaunch"
+ build_path="packages/apps/Mms"
+ package="com.android.mms.tests"
+ runner="com.android.mms.SmsLaunchPerformance"
+ coverage_target="Mms" />
+
+
+<!-- obsolete?
+<test name="ringtone"
+ build_path="tests/RingtoneSettings"
+ package="com.android.ringtonesettingstests"
+ runner=".RingtoneSettingsInstrumentationTestRunner"
+ coverage_target="Settings" />
+-->
+
+</test-definitions>
diff --git a/tools/apkbuilder/etc/apkbuilder.bat b/tools/apkbuilder/etc/apkbuilder.bat
index 7ab3e6c..c4689c6 100755
--- a/tools/apkbuilder/etc/apkbuilder.bat
+++ b/tools/apkbuilder/etc/apkbuilder.bat
@@ -20,9 +20,9 @@
rem and set up progdir to be the fully-qualified pathname of its directory.
set prog=%~f0
-rem Change current directory to where ddms is, to avoid issues with directories
-rem containing whitespaces.
-cd %~dp0
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
set jarfile=apkbuilder.jar
set frameworkdir=
diff --git a/tools/ddms/app/etc/ddms.bat b/tools/ddms/app/etc/ddms.bat
index 8d941b9..5da9fb5 100755
--- a/tools/ddms/app/etc/ddms.bat
+++ b/tools/ddms/app/etc/ddms.bat
@@ -20,9 +20,9 @@
rem and set up progdir to be the fully-qualified pathname of its directory.
set prog=%~f0
-rem Change current directory to where ddms is, to avoid issues with directories
-rem containing whitespaces.
-cd %~dp0
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
set jarfile=ddms.jar
set frameworkdir=
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
index a4576aa..866d578 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
@@ -94,7 +94,7 @@
* is only used for data generated within Client.
*/
private static final int INITIAL_BUF_SIZE = 2*1024;
- private static final int MAX_BUF_SIZE = 2*1024*1024;
+ private static final int MAX_BUF_SIZE = 200*1024*1024;
private ByteBuffer mReadBuffer;
private static final int WRITE_BUF_SIZE = 256;
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java
index 34ef432..0e7f0bb 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java
@@ -30,7 +30,7 @@
/**
* A Device. It can be a physical device or an emulator.
- *
+ *
* TODO: make this class package-protected, and shift all callers to use IDevice
*/
public final class Device implements IDevice {
@@ -62,10 +62,10 @@
return null;
}
}
-
+
/** Emulator Serial Number regexp. */
final static String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$
-
+
/** Serial number of the device */
String serialNumber = null;
@@ -74,7 +74,7 @@
/** State of the device. */
DeviceState state = null;
-
+
/** Device properties. */
private final Map<String, String> mProperties = new HashMap<String, String>();
@@ -85,29 +85,29 @@
* Socket for the connection monitoring client connection/disconnection.
*/
private SocketChannel mSocketChannel;
-
- /*
+
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getSerialNumber()
*/
public String getSerialNumber() {
return serialNumber;
}
-
+
public String getAvdName() {
return mAvdName;
}
-
- /*
+
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getState()
*/
public DeviceState getState() {
return state;
}
-
- /*
+
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getProperties()
*/
@@ -115,7 +115,7 @@
return Collections.unmodifiableMap(mProperties);
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getPropertyCount()
*/
@@ -123,21 +123,21 @@
return mProperties.size();
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getProperty(java.lang.String)
*/
public String getProperty(String name) {
return mProperties.get(name);
}
-
+
@Override
public String toString() {
return serialNumber;
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#isOnline()
*/
@@ -145,7 +145,7 @@
return state == DeviceState.ONLINE;
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#isEmulator()
*/
@@ -153,7 +153,7 @@
return serialNumber.matches(RE_EMULATOR_SN);
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#isOffline()
*/
@@ -161,7 +161,7 @@
return state == DeviceState.OFFLINE;
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#isBootLoader()
*/
@@ -169,7 +169,7 @@
return state == DeviceState.BOOTLOADER;
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#hasClients()
*/
@@ -177,7 +177,7 @@
return mClients.size() > 0;
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getClients()
*/
@@ -186,8 +186,8 @@
return mClients.toArray(new Client[mClients.size()]);
}
}
-
- /*
+
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getClient(java.lang.String)
*/
@@ -204,7 +204,7 @@
return null;
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getSyncService()
*/
@@ -217,7 +217,7 @@
return null;
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getFileListingService()
*/
@@ -225,7 +225,7 @@
return new FileListingService(this);
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getScreenshot()
*/
@@ -233,7 +233,7 @@
return AdbHelper.getFrameBuffer(AndroidDebugBridge.sSocketAddr, this);
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#executeShellCommand(java.lang.String, com.android.ddmlib.IShellOutputReceiver)
*/
@@ -242,16 +242,25 @@
AdbHelper.executeRemoteCommand(AndroidDebugBridge.sSocketAddr, command, this,
receiver);
}
-
- /*
+
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#runEventLogService(com.android.ddmlib.log.LogReceiver)
*/
public void runEventLogService(LogReceiver receiver) throws IOException {
AdbHelper.runEventLogService(AndroidDebugBridge.sSocketAddr, this, receiver);
}
-
- /*
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ddmlib.IDevice#runLogService(com.android.ddmlib.log.LogReceiver)
+ */
+ public void runLogService(String logname,
+ LogReceiver receiver) throws IOException {
+ AdbHelper.runLogService(AndroidDebugBridge.sSocketAddr, this, logname, receiver);
+ }
+
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#createForward(int, int)
*/
@@ -265,7 +274,7 @@
}
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#removeForward(int, int)
*/
@@ -279,7 +288,7 @@
}
}
- /*
+ /*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getClientName(int)
*/
@@ -325,7 +334,7 @@
return false;
}
-
+
void clearClientList() {
synchronized (mClients) {
mClients.clear();
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java
index 664b0c9..5dbce92 100755
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java
@@ -44,7 +44,7 @@
* Returns the serial number of the device.
*/
public String getSerialNumber();
-
+
/**
* Returns the name of the AVD the emulator is running.
* <p/>This is only valid if {@link #isEmulator()} returns true.
@@ -152,6 +152,14 @@
public void runEventLogService(LogReceiver receiver) throws IOException;
/**
+ * Runs the log service for the given log and outputs the log to the {@link LogReceiver}.
+ * @param logname the logname of the log to read from.
+ * @param receiver the receiver to receive the event log entries.
+ * @throws IOException
+ */
+ public void runLogService(String logname, LogReceiver receiver) throws IOException;
+
+ /**
* Creates a port forwarding between a local and a remote port.
* @param localPort the local port to forward
* @param remotePort the remote port.
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/ITestRunListener.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/ITestRunListener.java
index 4c0d9de..b61a698 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/ITestRunListener.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/ITestRunListener.java
@@ -17,56 +17,69 @@
package com.android.ddmlib.testrunner;
/**
- * Listener for instrumentation test runs
- *
- * Modeled after junit.runner.TestRunListener
+ * Receives event notifications during instrumentation test runs.
+ * Patterned after {@link junit.runner.TestRunListener}.
*/
public interface ITestRunListener {
- public static final int STATUS_ERROR = 1;
- public static final int STATUS_FAILURE = 2;
+
+ /**
+ * Types of test failures.
+ */
+ enum TestFailure {
+ /** Test failed due to unanticipated uncaught exception. */
+ ERROR,
+ /** Test failed due to a false assertion. */
+ FAILURE
+ }
/**
- * Reports the start of a test run
- * @param testCount - total number of tests in test run
- * */
+ * Reports the start of a test run.
+ *
+ * @param testCount total number of tests in test run
+ */
public void testRunStarted(int testCount);
/**
- * Reports end of test run
- * @param elapsedTime - device reported elapsed time, in milliseconds
+ * Reports end of test run.
+ *
+ * @param elapsedTime device reported elapsed time, in milliseconds
*/
public void testRunEnded(long elapsedTime);
/**
- * Reports test run stopped before completion
- * @param elapsedTime - device reported elapsed time, in milliseconds
+ * Reports test run stopped before completion.
+ *
+ * @param elapsedTime device reported elapsed time, in milliseconds
*/
public void testRunStopped(long elapsedTime);
/**
- * Reports the start of an individual test case
- */
- public void testStarted(String className, String testName);
-
- /**
- * Reports the execution end of an individual test case
- * If no testFailed has been reported, this is a passed test
- */
- public void testEnded(String className, String testName);
-
- /**
- * Reports the failure of a individual test case
- * Will be called between testStarted and testEnded
+ * Reports the start of an individual test case.
*
- * @param status - one of STATUS_ERROR, STATUS_FAILURE
- * @param className - name of test class
- * @param testName - name of test method
- * @param trace - stack trace of failure
+ * @param test identifies the test
*/
- public void testFailed(int status, String className, String testName, String trace);
+ public void testStarted(TestIdentifier test);
+
+ /**
+ * Reports the execution end of an individual test case.
+ * If {@link #testFailed} was not invoked, this test passed.
+ *
+ * @param test identifies the test
+ */
+ public void testEnded(TestIdentifier test);
+
+ /**
+ * Reports the failure of a individual test case.
+ * Will be called between testStarted and testEnded.
+ *
+ * @param status failure type
+ * @param test identifies the test
+ * @param trace stack trace of failure
+ */
+ public void testFailed(TestFailure status, TestIdentifier test, String trace);
/**
- * Reports test run failed to execute due to a fatal error
+ * Reports test run failed to execute due to a fatal error.
*/
public void testRunFailed(String errorMessage);
}
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java
index d47bd56..bc1834f 100755
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/InstrumentationResultParser.java
@@ -20,24 +20,21 @@
import com.android.ddmlib.Log;
import com.android.ddmlib.MultiLineReceiver;
-import java.util.Hashtable;
-import java.util.Map;
-
/**
- * Parses the 'raw output mode' results of an instrument test run from shell, and informs a
- * ITestRunListener of the results
+ * Parses the 'raw output mode' results of an instrumentation test run from shell and informs a
+ * ITestRunListener of the results.
*
- * Expects the following output:
+ * <p>Expects the following output:
*
- * If fatal error occurred when attempted to run the tests:
- * <i> INSTRUMENTATION_FAILED: </i>
+ * <p>If fatal error occurred when attempted to run the tests:
+ * <pre> INSTRUMENTATION_FAILED: </pre>
*
- * Otherwise, expect a series of test results, each one containing a set of status key/value
+ * <p>Otherwise, expect a series of test results, each one containing a set of status key/value
* pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test
* run, expects that the elapsed test time in seconds will be displayed
*
- * i.e.
- * <i>
+ * <p>For example:
+ * <pre>
* INSTRUMENTATION_STATUS_CODE: 1
* INSTRUMENTATION_STATUS: class=com.foo.FooTest
* INSTRUMENTATION_STATUS: test=testFoo
@@ -48,64 +45,85 @@
* ...
*
* Time: X
- * </i>
- *
- * Note that the "value" portion of the key-value pair may wrap over several text lines
+ * </pre>
+ * <p>Note that the "value" portion of the key-value pair may wrap over several text lines
*/
public class InstrumentationResultParser extends MultiLineReceiver {
- // relevant test status keys
- private static final String CODE_KEY = "code";
- private static final String TEST_KEY = "test";
- private static final String CLASS_KEY = "class";
- private static final String STACK_KEY = "stack";
- private static final String NUMTESTS_KEY = "numtests";
+ /** Relevant test status keys. */
+ private static class StatusKeys {
+ private static final String TEST = "test";
+ private static final String CLASS = "class";
+ private static final String STACK = "stack";
+ private static final String NUMTESTS = "numtests";
+ }
- // test result status codes
- private static final int FAILURE_STATUS_CODE = -2;
- private static final int START_STATUS_CODE = 1;
- private static final int ERROR_STATUS_CODE = -1;
- private static final int OK_STATUS_CODE = 0;
+ /** Test result status codes. */
+ private static class StatusCodes {
+ private static final int FAILURE = -2;
+ private static final int START = 1;
+ private static final int ERROR = -1;
+ private static final int OK = 0;
+ }
- // recognized output patterns
- private static final String STATUS_PREFIX = "INSTRUMENTATION_STATUS: ";
- private static final String STATUS_PREFIX_CODE = "INSTRUMENTATION_STATUS_CODE: ";
- private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: ";
- private static final String TIME_REPORT = "Time: ";
+ /** Prefixes used to identify output. */
+ private static class Prefixes {
+ private static final String STATUS = "INSTRUMENTATION_STATUS: ";
+ private static final String STATUS_CODE = "INSTRUMENTATION_STATUS_CODE: ";
+ private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: ";
+ private static final String TIME_REPORT = "Time: ";
+ }
private final ITestRunListener mTestListener;
- /** key-value map for current test */
- private Map<String, String> mStatusValues;
- /** stores the current "key" portion of the status key-value being parsed */
- private String mCurrentKey;
- /** stores the current "value" portion of the status key-value being parsed */
- private StringBuilder mCurrentValue;
- /** true if start of test has already been reported to listener */
- private boolean mTestStartReported;
- /** the elapsed time of the test run, in ms */
- private long mTestTime;
- /** true if current test run has been canceled by user */
- private boolean mIsCancelled;
+
+ /**
+ * Test result data
+ */
+ private static class TestResult {
+ private Integer mCode = null;
+ private String mTestName = null;
+ private String mTestClass = null;
+ private String mStackTrace = null;
+ private Integer mNumTests = null;
+
+ /** Returns true if all expected values have been parsed */
+ boolean isComplete() {
+ return mCode != null && mTestName != null && mTestClass != null;
+ }
+ }
+
+ /** Stores the status values for the test result currently being parsed */
+ private TestResult mCurrentTestResult = null;
+
+ /** Stores the current "key" portion of the status key-value being parsed. */
+ private String mCurrentKey = null;
+
+ /** Stores the current "value" portion of the status key-value being parsed. */
+ private StringBuilder mCurrentValue = null;
+
+ /** True if start of test has already been reported to listener. */
+ private boolean mTestStartReported = false;
+
+ /** The elapsed time of the test run, in milliseconds. */
+ private long mTestTime = 0;
+
+ /** True if current test run has been canceled by user. */
+ private boolean mIsCancelled = false;
private static final String LOG_TAG = "InstrumentationResultParser";
/**
- * Creates the InstrumentationResultParser
- * @param listener - listener to report results to. will be informed of test results as the
- * tests are executing
+ * Creates the InstrumentationResultParser.
+ *
+ * @param listener informed of test results as the tests are executing
*/
public InstrumentationResultParser(ITestRunListener listener) {
- mStatusValues = new Hashtable<String, String>();
- mCurrentKey = null;
- setTrimLine(false);
mTestListener = listener;
- mTestStartReported = false;
- mTestTime = 0;
- mIsCancelled = false;
}
/**
- * Processes the instrumentation test output from shell
+ * Processes the instrumentation test output from shell.
+ *
* @see MultiLineReceiver#processNewLines
*/
@Override
@@ -116,31 +134,37 @@
}
/**
- * Parse an individual output line. Expects a line that either is:
- * a) the start of a new status line (ie. starts with STATUS_PREFIX or STATUS_PREFIX_CODE),
- * and thus there is a new key=value pair to parse, and the previous key-value pair is
- * finished
- * b) a continuation of the previous status (ie the "value" portion of the key has wrapped
- * to the next line.
- * c) a line reporting a fatal error in the test run (STATUS_FAILED)
- * d) a line reporting the total elapsed time of the test run.
+ * Parse an individual output line. Expects a line that is one of:
+ * <ul>
+ * <li>
+ * The start of a new status line (starts with Prefixes.STATUS or Prefixes.STATUS_CODE),
+ * and thus there is a new key=value pair to parse, and the previous key-value pair is
+ * finished.
+ * </li>
+ * <li>
+ * A continuation of the previous status (the "value" portion of the key has wrapped
+ * to the next line).
+ * </li>
+ * <li> A line reporting a fatal error in the test run (Prefixes.STATUS_FAILED) </li>
+ * <li> A line reporting the total elapsed time of the test run. (Prefixes.TIME_REPORT) </li>
+ * </ul>
*
- * @param line - text output line
+ * @param line Text output line
*/
private void parse(String line) {
- if (line.startsWith(STATUS_PREFIX_CODE)) {
+ if (line.startsWith(Prefixes.STATUS_CODE)) {
// Previous status key-value has been collected. Store it.
submitCurrentKeyValue();
parseStatusCode(line);
- } else if (line.startsWith(STATUS_PREFIX)) {
+ } else if (line.startsWith(Prefixes.STATUS)) {
// Previous status key-value has been collected. Store it.
submitCurrentKeyValue();
- parseKey(line, STATUS_PREFIX.length());
- } else if (line.startsWith(STATUS_FAILED)) {
+ parseKey(line, Prefixes.STATUS.length());
+ } else if (line.startsWith(Prefixes.STATUS_FAILED)) {
Log.e(LOG_TAG, "test run failed " + line);
mTestListener.testRunFailed(line);
- } else if (line.startsWith(TIME_REPORT)) {
- parseTime(line, TIME_REPORT.length());
+ } else if (line.startsWith(Prefixes.TIME_REPORT)) {
+ parseTime(line, Prefixes.TIME_REPORT.length());
} else {
if (mCurrentValue != null) {
// this is a value that has wrapped to next line.
@@ -153,21 +177,53 @@
}
/**
- * Stores the currently parsed key-value pair in the status map
+ * Stores the currently parsed key-value pair into mCurrentTestInfo.
*/
private void submitCurrentKeyValue() {
if (mCurrentKey != null && mCurrentValue != null) {
- mStatusValues.put(mCurrentKey, mCurrentValue.toString());
+ TestResult testInfo = getCurrentTestInfo();
+ String statusValue = mCurrentValue.toString();
+
+ if (mCurrentKey.equals(StatusKeys.CLASS)) {
+ testInfo.mTestClass = statusValue.trim();
+ }
+ else if (mCurrentKey.equals(StatusKeys.TEST)) {
+ testInfo.mTestName = statusValue.trim();
+ }
+ else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) {
+ try {
+ testInfo.mNumTests = Integer.parseInt(statusValue);
+ }
+ catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "Unexpected integer number of tests, received " + statusValue);
+ }
+ }
+ else if (mCurrentKey.equals(StatusKeys.STACK)) {
+ testInfo.mStackTrace = statusValue;
+ }
+
mCurrentKey = null;
mCurrentValue = null;
}
}
+ private TestResult getCurrentTestInfo() {
+ if (mCurrentTestResult == null) {
+ mCurrentTestResult = new TestResult();
+ }
+ return mCurrentTestResult;
+ }
+
+ private void clearCurrentTestInfo() {
+ mCurrentTestResult = null;
+ }
+
/**
- * Parses the key from the current line
- * Expects format of "key=value",
- * @param line - full line of text to parse
- * @param keyStartPos - the starting position of the key in the given line
+ * Parses the key from the current line.
+ * Expects format of "key=value".
+ *
+ * @param line full line of text to parse
+ * @param keyStartPos the starting position of the key in the given line
*/
private void parseKey(String line, int keyStartPos) {
int endKeyPos = line.indexOf('=', keyStartPos);
@@ -178,7 +234,8 @@
}
/**
- * Parses the start of a key=value pair.
+ * Parses the start of a key=value pair.
+ *
* @param line - full line of text to parse
* @param valueStartPos - the starting position of the value in the given line
*/
@@ -188,20 +245,25 @@
}
/**
- * Parses out a status code result. For consistency, stores the result as a CODE entry in
- * key-value status map
+ * Parses out a status code result.
*/
private void parseStatusCode(String line) {
- String value = line.substring(STATUS_PREFIX_CODE.length()).trim();
- mStatusValues.put(CODE_KEY, value);
+ String value = line.substring(Prefixes.STATUS_CODE.length()).trim();
+ TestResult testInfo = getCurrentTestInfo();
+ try {
+ testInfo.mCode = Integer.parseInt(value);
+ }
+ catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "Expected integer status code, received: " + value);
+ }
// this means we're done with current test result bundle
- reportResult(mStatusValues);
- mStatusValues.clear();
+ reportResult(testInfo);
+ clearCurrentTestInfo();
}
/**
- * Returns true if test run canceled
+ * Returns true if test run canceled.
*
* @see IShellOutputReceiver#isCancelled()
*/
@@ -210,7 +272,7 @@
}
/**
- * Requests cancellation of test result parsing
+ * Requests cancellation of test run.
*/
public void cancel() {
mIsCancelled = true;
@@ -219,82 +281,62 @@
/**
* Reports a test result to the test run listener. Must be called when a individual test
* result has been fully parsed.
- * @param statusMap - key-value status pairs of test result
+ *
+ * @param statusMap key-value status pairs of test result
*/
- private void reportResult(Map<String, String> statusMap) {
- String className = statusMap.get(CLASS_KEY);
- String testName = statusMap.get(TEST_KEY);
- String statusCodeString = statusMap.get(CODE_KEY);
-
- if (className == null || testName == null || statusCodeString == null) {
- Log.e(LOG_TAG, "invalid instrumentation status bundle " + statusMap.toString());
+ private void reportResult(TestResult testInfo) {
+ if (!testInfo.isComplete()) {
+ Log.e(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString());
return;
}
- className = className.trim();
- testName = testName.trim();
+ reportTestRunStarted(testInfo);
+ TestIdentifier testId = new TestIdentifier(testInfo.mTestClass, testInfo.mTestName);
- reportTestStarted(statusMap);
-
- try {
- int statusCode = Integer.parseInt(statusCodeString);
-
- switch (statusCode) {
- case START_STATUS_CODE:
- mTestListener.testStarted(className, testName);
- break;
- case FAILURE_STATUS_CODE:
- mTestListener.testFailed(ITestRunListener.STATUS_FAILURE, className, testName,
- getTrace(statusMap));
- mTestListener.testEnded(className, testName);
- break;
- case ERROR_STATUS_CODE:
- mTestListener.testFailed(ITestRunListener.STATUS_ERROR, className, testName,
- getTrace(statusMap));
- mTestListener.testEnded(className, testName);
- break;
- case OK_STATUS_CODE:
- mTestListener.testEnded(className, testName);
- break;
- default:
- Log.e(LOG_TAG, "Expected status code, received: " + statusCodeString);
- mTestListener.testEnded(className, testName);
- break;
- }
+ switch (testInfo.mCode) {
+ case StatusCodes.START:
+ mTestListener.testStarted(testId);
+ break;
+ case StatusCodes.FAILURE:
+ mTestListener.testFailed(ITestRunListener.TestFailure.FAILURE, testId,
+ getTrace(testInfo));
+ mTestListener.testEnded(testId);
+ break;
+ case StatusCodes.ERROR:
+ mTestListener.testFailed(ITestRunListener.TestFailure.ERROR, testId,
+ getTrace(testInfo));
+ mTestListener.testEnded(testId);
+ break;
+ case StatusCodes.OK:
+ mTestListener.testEnded(testId);
+ break;
+ default:
+ Log.e(LOG_TAG, "Unknown status code received: " + testInfo.mCode);
+ mTestListener.testEnded(testId);
+ break;
}
- catch (NumberFormatException e) {
- Log.e(LOG_TAG, "Expected integer status code, received: " + statusCodeString);
- }
+
}
/**
* Reports the start of a test run, and the total test count, if it has not been previously
- * reported
- * @param statusMap - key-value status pairs
+ * reported.
+ *
+ * @param testInfo current test status values
*/
- private void reportTestStarted(Map<String, String> statusMap) {
+ private void reportTestRunStarted(TestResult testInfo) {
// if start test run not reported yet
- if (!mTestStartReported) {
- String numTestsString = statusMap.get(NUMTESTS_KEY);
- if (numTestsString != null) {
- try {
- int numTests = Integer.parseInt(numTestsString);
- mTestListener.testRunStarted(numTests);
- mTestStartReported = true;
- }
- catch (NumberFormatException e) {
- Log.e(LOG_TAG, "Unexpected numTests format " + numTestsString);
- }
- }
+ if (!mTestStartReported && testInfo.mNumTests != null) {
+ mTestListener.testRunStarted(testInfo.mNumTests);
+ mTestStartReported = true;
}
}
/**
- * Returns the stack trace of the current failed test, from the provided key-value status map
+ * Returns the stack trace of the current failed test, from the provided testInfo.
*/
- private String getTrace(Map<String, String> statusMap) {
- String stackTrace = statusMap.get(STACK_KEY);
- if (stackTrace != null) {
- return stackTrace;
+ private String getTrace(TestResult testInfo) {
+ if (testInfo.mStackTrace != null) {
+ return testInfo.mStackTrace;
}
else {
Log.e(LOG_TAG, "Could not find stack trace for failed test ");
@@ -303,7 +345,7 @@
}
/**
- * Parses out and store the elapsed time
+ * Parses out and store the elapsed time.
*/
private void parseTime(String line, int startPos) {
String timeString = line.substring(startPos);
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
index 5de632e..4edbbbb 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunner.java
@@ -23,7 +23,7 @@
import java.io.IOException;
/**
- * Runs a Android test command remotely and reports results
+ * Runs a Android test command remotely and reports results.
*/
public class RemoteAndroidTestRunner {
@@ -43,11 +43,12 @@
"android.test.InstrumentationTestRunner";
/**
- * Creates a remote android test runner.
- * @param packageName - the Android application package that contains the tests to run
- * @param runnerName - the instrumentation test runner to execute. If null, will use default
+ * Creates a remote Android test runner.
+ *
+ * @param packageName the Android application package that contains the tests to run
+ * @param runnerName the instrumentation test runner to execute. If null, will use default
* runner
- * @param remoteDevice - the Android device to execute tests on
+ * @param remoteDevice the Android device to execute tests on
*/
public RemoteAndroidTestRunner(String packageName,
String runnerName,
@@ -62,9 +63,10 @@
}
/**
- * Alternate constructor. Uses default instrumentation runner
- * @param packageName - the Android application package that contains the tests to run
- * @param remoteDevice - the Android device to execute tests on
+ * Alternate constructor. Uses default instrumentation runner.
+ *
+ * @param packageName the Android application package that contains the tests to run
+ * @param remoteDevice the Android device to execute tests on
*/
public RemoteAndroidTestRunner(String packageName,
IDevice remoteDevice) {
@@ -72,14 +74,14 @@
}
/**
- * Returns the application package name
+ * Returns the application package name.
*/
public String getPackageName() {
return mPackageName;
}
/**
- * Returns the runnerName
+ * Returns the runnerName.
*/
public String getRunnerName() {
if (mRunnerName == null) {
@@ -89,7 +91,7 @@
}
/**
- * Returns the complete instrumentation component path
+ * Returns the complete instrumentation component path.
*/
private String getRunnerPath() {
return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
@@ -97,8 +99,9 @@
/**
* Sets to run only tests in this class
- * Must be called before 'run'
- * @param className - fully qualified class name (eg x.y.z)
+ * Must be called before 'run'.
+ *
+ * @param className fully qualified class name (eg x.y.z)
*/
public void setClassName(String className) {
mClassArg = className;
@@ -106,10 +109,12 @@
/**
* Sets to run only tests in the provided classes
- * Must be called before 'run'
+ * Must be called before 'run'.
+ * <p>
* If providing more than one class, requires a InstrumentationTestRunner that supports
- * the multiple class argument syntax
- * @param classNames - array of fully qualified class name (eg x.y.z)
+ * the multiple class argument syntax.
+ *
+ * @param classNames array of fully qualified class names (eg x.y.z)
*/
public void setClassNames(String[] classNames) {
StringBuilder classArgBuilder = new StringBuilder();
@@ -125,9 +130,10 @@
/**
* Sets to run only specified test method
- * Must be called before 'run'
- * @param className - fully qualified class name (eg x.y.z)
- * @param testName - method name
+ * Must be called before 'run'.
+ *
+ * @param className fully qualified class name (eg x.y.z)
+ * @param testName method name
*/
public void setMethodName(String className, String testName) {
mClassArg = className + METHOD_SEPARATOR + testName;
@@ -135,8 +141,9 @@
/**
* Sets extra arguments to include in instrumentation command.
- * Must be called before 'run'
- * @param instrumentationArgs - must not be null
+ * Must be called before 'run'.
+ *
+ * @param instrumentationArgs must not be null
*/
public void setExtraArgs(String instrumentationArgs) {
if (instrumentationArgs == null) {
@@ -146,23 +153,23 @@
}
/**
- * Returns the extra instrumentation arguments
+ * Returns the extra instrumentation arguments.
*/
public String getExtraArgs() {
return mExtraArgs;
}
/**
- * Sets this test run to log only mode - skips test execution
+ * Sets this test run to log only mode - skips test execution.
*/
public void setLogOnly(boolean logOnly) {
mLogOnlyMode = logOnly;
}
/**
- * Execute this test run
+ * Execute this test run.
*
- * @param listener - listener to report results to
+ * @param listener listens for test results
*/
public void run(ITestRunListener listener) {
final String runCaseCommandStr = "am instrument -w -r "
@@ -179,7 +186,7 @@
}
/**
- * Requests cancellation of this test run
+ * Requests cancellation of this test run.
*/
public void cancel() {
if (mParser != null) {
@@ -188,7 +195,7 @@
}
/**
- * Returns the test class argument
+ * Returns the test class argument.
*/
private String getClassArg() {
return mClassArg;
@@ -196,7 +203,7 @@
/**
* Returns the full instrumentation command which specifies the test classes to execute.
- * Returns an empty string if no classes were specified
+ * Returns an empty string if no classes were specified.
*/
private String getClassCmd() {
String classArg = getClassArg();
@@ -208,7 +215,7 @@
/**
* Returns the full command to enable log only mode - if specified. Otherwise returns an
- * empty string
+ * empty string.
*/
private String getLogCmd() {
if (mLogOnlyMode) {
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestIdentifier.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestIdentifier.java
new file mode 100644
index 0000000..4d3b108
--- /dev/null
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/testrunner/TestIdentifier.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2008 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.ddmlib.testrunner;
+
+/**
+ * Identifies a parsed instrumentation test
+ */
+public class TestIdentifier {
+
+ private final String mClassName;
+ private final String mTestName;
+
+ /**
+ * Creates a test identifier
+ *
+ * @param className fully qualified class name of the test. Cannot be null.
+ * @param testName name of the test. Cannot be null.
+ */
+ public TestIdentifier(String className, String testName) {
+ if (className == null || testName == null) {
+ throw new IllegalArgumentException("className and testName must " +
+ "be non-null");
+ }
+ mClassName = className;
+ mTestName = testName;
+ }
+
+ /**
+ * Returns the fully qualified class name of the test
+ */
+ public String getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Returns the name of the test
+ */
+ public String getTestName() {
+ return mTestName;
+ }
+
+ /**
+ * Tests equality by comparing class and method name
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof TestIdentifier)) {
+ return false;
+ }
+ TestIdentifier otherTest = (TestIdentifier)other;
+ return getClassName().equals(otherTest.getClassName()) &&
+ getTestName().equals(otherTest.getTestName());
+ }
+
+ /**
+ * Generates hashCode based on class and method name.
+ */
+ @Override
+ public int hashCode() {
+ return getClassName().hashCode() * 31 + getTestName().hashCode();
+ }
+}
diff --git a/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java b/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java
index 67f6198..77d10c1 100644
--- a/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java
+++ b/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/InstrumentationResultParserTest.java
@@ -20,7 +20,7 @@
/**
- * Tests InstrumentationResultParser
+ * Tests InstrumentationResultParser.
*/
public class InstrumentationResultParserTest extends TestCase {
@@ -51,7 +51,7 @@
/**
* Tests that the test run started and test start events is sent on first
- * bundle received
+ * bundle received.
*/
public void testTestStarted() {
StringBuilder output = buildCommonResult();
@@ -63,7 +63,7 @@
}
/**
- * Tests that a single successful test execution
+ * Tests that a single successful test execution.
*/
public void testTestSuccess() {
StringBuilder output = buildCommonResult();
@@ -74,11 +74,11 @@
injectTestString(output.toString());
assertCommonAttributes();
assertEquals(1, mTestResult.mNumTestsRun);
- assertEquals(0, mTestResult.mTestStatus);
+ assertEquals(null, mTestResult.mTestStatus);
}
/**
- * Test basic parsing of failed test case
+ * Test basic parsing of failed test case.
*/
public void testTestFailed() {
StringBuilder output = buildCommonResult();
@@ -91,12 +91,12 @@
assertCommonAttributes();
assertEquals(1, mTestResult.mNumTestsRun);
- assertEquals(ITestRunListener.STATUS_FAILURE, mTestResult.mTestStatus);
+ assertEquals(ITestRunListener.TestFailure.FAILURE, mTestResult.mTestStatus);
assertEquals(STACK_TRACE, mTestResult.mTrace);
}
/**
- * Test basic parsing and conversion of time from output
+ * Test basic parsing and conversion of time from output.
*/
public void testTimeParsing() {
final String timeString = "Time: 4.9";
@@ -105,7 +105,7 @@
}
/**
- * builds a common test result using TEST_NAME and TEST_CLASS
+ * builds a common test result using TEST_NAME and TEST_CLASS.
*/
private StringBuilder buildCommonResult() {
StringBuilder output = new StringBuilder();
@@ -118,7 +118,7 @@
}
/**
- * Adds common status results to the provided output
+ * Adds common status results to the provided output.
*/
private void addCommonStatus(StringBuilder output) {
addStatusKey(output, "stream", "\r\n" + CLASS_NAME);
@@ -130,7 +130,7 @@
}
/**
- * Adds a stack trace status bundle to output
+ * Adds a stack trace status bundle to output.
*/
private void addStackTrace(StringBuilder output) {
addStatusKey(output, "stack", STACK_TRACE);
@@ -138,7 +138,7 @@
}
/**
- * Helper method to add a status key-value bundle
+ * Helper method to add a status key-value bundle.
*/
private void addStatusKey(StringBuilder outputBuilder, String key,
String value) {
@@ -168,7 +168,7 @@
}
/**
- * inject a test string into the result parser
+ * inject a test string into the result parser.
*
* @param result
*/
@@ -185,7 +185,7 @@
}
/**
- * A specialized test listener that stores a single test events
+ * A specialized test listener that stores a single test events.
*/
private class VerifyingTestResult implements ITestRunListener {
@@ -194,29 +194,28 @@
int mNumTestsRun;
String mTestName;
long mTestTime;
- int mTestStatus;
+ TestFailure mTestStatus;
String mTrace;
boolean mStopped;
VerifyingTestResult() {
mNumTestsRun = 0;
- mTestStatus = 0;
+ mTestStatus = null;
mStopped = false;
}
- public void testEnded(String className, String testName) {
+ public void testEnded(TestIdentifier test) {
mNumTestsRun++;
- assertEquals("Unexpected class name", mSuiteName, className);
- assertEquals("Unexpected test ended", mTestName, testName);
+ assertEquals("Unexpected class name", mSuiteName, test.getClassName());
+ assertEquals("Unexpected test ended", mTestName, test.getTestName());
}
- public void testFailed(int status, String className, String testName,
- String trace) {
+ public void testFailed(TestFailure status, TestIdentifier test, String trace) {
mTestStatus = status;
mTrace = trace;
- assertEquals("Unexpected class name", mSuiteName, className);
- assertEquals("Unexpected test ended", mTestName, testName);
+ assertEquals("Unexpected class name", mSuiteName, test.getClassName());
+ assertEquals("Unexpected test ended", mTestName, test.getTestName());
}
public void testRunEnded(long elapsedTime) {
@@ -233,9 +232,9 @@
mStopped = true;
}
- public void testStarted(String className, String testName) {
- mSuiteName = className;
- mTestName = testName;
+ public void testStarted(TestIdentifier test) {
+ mSuiteName = test.getClassName();
+ mTestName = test.getTestName();
}
public void testRunFailed(String errorMessage) {
diff --git a/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java b/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
index 54ffae8..9acaaf9 100644
--- a/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
+++ b/tools/ddms/libs/ddmlib/tests/src/com/android/ddmlib/testrunner/RemoteAndroidTestRunnerTest.java
@@ -31,16 +31,16 @@
import java.util.Map;
/**
- * Test RemoteAndroidTestRunner.
+ * Tests RemoteAndroidTestRunner.
*/
public class RemoteAndroidTestRunnerTest extends TestCase {
private RemoteAndroidTestRunner mRunner;
private MockDevice mMockDevice;
-
+
private static final String TEST_PACKAGE = "com.test";
private static final String TEST_RUNNER = "com.test.InstrumentationTestRunner";
-
+
/**
* @see junit.framework.TestCase#setUp()
*/
@@ -49,40 +49,40 @@
mMockDevice = new MockDevice();
mRunner = new RemoteAndroidTestRunner(TEST_PACKAGE, TEST_RUNNER, mMockDevice);
}
-
+
/**
- * Test the basic case building of the instrumentation runner command with no arguments
+ * Test the basic case building of the instrumentation runner command with no arguments.
*/
public void testRun() {
mRunner.run(new EmptyListener());
- assertStringsEquals(String.format("am instrument -w -r %s/%s", TEST_PACKAGE, TEST_RUNNER),
+ assertStringsEquals(String.format("am instrument -w -r %s/%s", TEST_PACKAGE, TEST_RUNNER),
mMockDevice.getLastShellCommand());
}
/**
- * Test the building of the instrumentation runner command with log set
+ * Test the building of the instrumentation runner command with log set.
*/
public void testRunWithLog() {
mRunner.setLogOnly(true);
mRunner.run(new EmptyListener());
- assertStringsEquals(String.format("am instrument -w -r -e log true %s/%s", TEST_PACKAGE,
+ assertStringsEquals(String.format("am instrument -w -r -e log true %s/%s", TEST_PACKAGE,
TEST_RUNNER), mMockDevice.getLastShellCommand());
}
/**
- * Test the building of the instrumentation runner command with method set
+ * Test the building of the instrumentation runner command with method set.
*/
public void testRunWithMethod() {
final String className = "FooTest";
final String testName = "fooTest";
mRunner.setMethodName(className, testName);
mRunner.run(new EmptyListener());
- assertStringsEquals(String.format("am instrument -w -r -e class %s#%s %s/%s", className,
+ assertStringsEquals(String.format("am instrument -w -r -e class %s#%s %s/%s", className,
testName, TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand());
}
-
+
/**
- * Test the building of the instrumentation runner command with extra args set
+ * Test the building of the instrumentation runner command with extra args set.
*/
public void testRunWithExtraArgs() {
final String extraArgs = "blah";
@@ -94,37 +94,37 @@
/**
- * Assert two strings are equal ignoring whitespace
+ * Assert two strings are equal ignoring whitespace.
*/
private void assertStringsEquals(String str1, String str2) {
String strippedStr1 = str1.replaceAll(" ", "");
String strippedStr2 = str2.replaceAll(" ", "");
assertEquals(strippedStr1, strippedStr2);
}
-
+
/**
- * A dummy device that does nothing except store the provided executed shell command for
- * later retrieval
+ * A dummy device that does nothing except store the provided executed shell command for
+ * later retrieval.
*/
private static class MockDevice implements IDevice {
private String mLastShellCommand;
-
+
/**
- * Stores the provided command for later retrieval from getLastShellCommand
+ * Stores the provided command for later retrieval from getLastShellCommand.
*/
public void executeShellCommand(String command,
IShellOutputReceiver receiver) throws IOException {
mLastShellCommand = command;
}
-
+
/**
- * Get the last command provided to executeShellCommand
+ * Get the last command provided to executeShellCommand.
*/
public String getLastShellCommand() {
return mLastShellCommand;
}
-
+
public boolean createForward(int localPort, int remotePort) {
throw new UnsupportedOperationException();
}
@@ -201,22 +201,26 @@
throw new UnsupportedOperationException();
}
+ public void runLogService(String logname, LogReceiver receiver) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
public String getAvdName() {
return "";
}
}
-
- /** An empty implementation of TestRunListener
+
+ /**
+ * An empty implementation of ITestRunListener.
*/
private static class EmptyListener implements ITestRunListener {
- public void testEnded(String className, String testName) {
+ public void testEnded(TestIdentifier test) {
// ignore
}
- public void testFailed(int status, String className, String testName,
- String trace) {
+ public void testFailed(TestFailure status, TestIdentifier test, String trace) {
// ignore
}
@@ -236,9 +240,9 @@
// ignore
}
- public void testStarted(String className, String testName) {
+ public void testStarted(TestIdentifier test) {
// ignore
}
-
+
}
}
diff --git a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/NativeHeapPanel.java b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/NativeHeapPanel.java
index 149d689..46461bf 100644
--- a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/NativeHeapPanel.java
+++ b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/NativeHeapPanel.java
@@ -237,7 +237,7 @@
*/
private HashMap<Long, NativeStackCallInfo> mSourceCache =
new HashMap<Long,NativeStackCallInfo>();
- private int mTotalSize;
+ private long mTotalSize;
private Button mSaveButton;
private Button mSymbolsButton;
diff --git a/tools/draw9patch/etc/draw9patch.bat b/tools/draw9patch/etc/draw9patch.bat
index 1d56d85..e267b06 100755
--- a/tools/draw9patch/etc/draw9patch.bat
+++ b/tools/draw9patch/etc/draw9patch.bat
@@ -20,9 +20,9 @@
rem and set up progdir to be the fully-qualified pathname of its directory.
set prog=%~f0
-rem Change current directory to where ddms is, to avoid issues with directories
-rem containing whitespaces.
-cd %~dp0
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
set jarfile=draw9patch.jar
set frameworkdir=
diff --git a/tools/eclipse/changes.txt b/tools/eclipse/changes.txt
index 5cb8245..781930c 100644
--- a/tools/eclipse/changes.txt
+++ b/tools/eclipse/changes.txt
@@ -1,14 +1,18 @@
0.9.0 (work in progress)
-- Support for SDK with multiple versions of the Android platform and vendor supplied add-ons.
+- Support for the new Android SDK with support for multiple versions of the Android platform and for vendor supplied add-ons.
+ * New Project Wizard lets you choose which platform/add-on to target.
+ * Project properties (right click project in Package Explorer, then "Properties"), lets you edit project target.
+ * New Launch configuration option to choose debug deployment target.
+- Ability to export multiple apk from one project, using resource filters. See the 'android' property for Android projects.
0.8.1:
-- Alternate Layout wizard. In the layout editor, the "create" button is now enabled, and allows to easily create alternate versions.
+- Alternate Layout wizard. In the layout editor, the "create" button is now enabled to easily create alternate versions of the current layout.
- Fixed issue with custom themes/styles in the layout editor.
-- Export Wizard: To export an application for release, sign with a non debug key. Accessible from the export menu, from the Android Tools contextual menu, or from the overview page of the manifest editor.
+- Export Wizard: To export an application for release, and sign it with a non debug key. Accessible from the export menu, from the Android Tools contextual menu, or from the overview page of the manifest editor.
- New XML File Wizard: To easily create new XML resources file in the /res directory.
- New checks on launch when attempting to debug on a device.
-- Basic support for drag'n'drop in Graphical layout editor. You can add new items by drag'n'drop from the palette. There's is no support for moving/resizing yet.
+- Basic support for drag'n'drop in Graphical layout editor. You can add new items by drag'n'drop from the palette. There is no support for moving/resizing yet.
- Undo/redo support in all XML form editors and Graphical layout editor.
0.8.0:
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_adt_project.png b/tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_adt_project.png
new file mode 100644
index 0000000..0f0e883
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_adt_project.png
Binary files differ
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_xml.png b/tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_xml.png
new file mode 100644
index 0000000..8273185
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_xml.png
Binary files differ
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index f7c366d..d6c9ac1 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -18,6 +18,14 @@
<persistent value="true"/>
</extension>
<extension
+ id="com.android.ide.eclipse.common.aapt2Problem"
+ name="Android AAPT Problem"
+ point="org.eclipse.core.resources.markers">
+ <super type="org.eclipse.core.resources.problemmarker"/>
+ <super type="org.eclipse.core.resources.textmarker"/>
+ <persistent value="true"/>
+ </extension>
+ <extension
id="com.android.ide.eclipse.common.aidlProblem"
name="Android AIDL Problem"
point="org.eclipse.core.resources.markers">
@@ -464,4 +472,31 @@
</enabledWhen>
</page>
</extension>
+ <extension
+ point="org.eclipse.ui.actionSets">
+ <actionSet
+ description="Android Wizards"
+ id="adt.actionSet1"
+ label="Android Wizards"
+ visible="true">
+ <action
+ class="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
+ icon="icons/new_adt_project.png"
+ id="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
+ label="New Android Project"
+ style="push"
+ toolbarPath="android_project"
+ tooltip="Opens a wizard to help create a new Android project">
+ </action>
+ <action
+ class="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction"
+ icon="icons/new_xml.png"
+ id="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction"
+ label="New Android XML File"
+ style="push"
+ toolbarPath="android_project"
+ tooltip="Opens a wizard to help create a new Android XML file">
+ </action>
+ </actionSet>
+ </extension>
</plugin>
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index 9aa9354..61be3e5 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -28,6 +28,7 @@
import com.android.ide.eclipse.adt.sdk.AndroidTargetParser;
import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.EclipseUiHelper;
import com.android.ide.eclipse.common.SdkStatsHelper;
@@ -173,7 +174,8 @@
private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>();
private ResourceMonitor mResourceMonitor;
- private ArrayList<Runnable> mResourceRefreshListener = new ArrayList<Runnable>();
+ private ArrayList<ITargetChangeListener> mTargetChangeListeners =
+ new ArrayList<ITargetChangeListener>();
/**
* Custom PrintStream for Dx output. This class overrides the method
@@ -861,7 +863,6 @@
/**
* Returns the lock object for SDK loading. If you wish to do things while the SDK is loading,
* you must synchronize on this object.
- * @return
*/
public final Object getSdkLockObject() {
return mPostLoadProjectsToResolve;
@@ -986,7 +987,7 @@
Constants.BUNDLE_VERSION);
Version version = new Version(versionString);
- SdkStatsHelper.pingUsageServer("editors", version); //$NON-NLS-1$
+ SdkStatsHelper.pingUsageServer("adt", version); //$NON-NLS-1$
return Status.OK_STATUS;
} catch (Throwable t) {
@@ -1019,24 +1020,27 @@
progress.setTaskName(Messages.AdtPlugin_Parsing_Resources);
- for (IAndroidTarget target : sdk.getTargets()) {
- IStatus status = new AndroidTargetParser(target).run(progress);
- if (status.getCode() != IStatus.OK) {
- synchronized (getSdkLockObject()) {
- mSdkIsLoaded = LoadStatus.FAILED;
- mPostLoadProjectsToResolve.clear();
+ int n = sdk.getTargets().length;
+ if (n > 0) {
+ int w = 60 / n;
+ for (IAndroidTarget target : sdk.getTargets()) {
+ SubMonitor p2 = progress.newChild(w);
+ IStatus status = new AndroidTargetParser(target).run(p2);
+ if (status.getCode() != IStatus.OK) {
+ synchronized (getSdkLockObject()) {
+ mSdkIsLoaded = LoadStatus.FAILED;
+ mPostLoadProjectsToResolve.clear();
+ }
+ return status;
}
- return status;
}
}
- // FIXME: move this per platform, or somewhere else.
- progress = SubMonitor.convert(monitor,
- Messages.AdtPlugin_Parsing_Resources, 20);
-
synchronized (getSdkLockObject()) {
mSdkIsLoaded = LoadStatus.LOADED;
+ progress.setTaskName("Check Projects");
+
// check the projects that need checking.
// The method modifies the list (it removes the project that
// do not need to be resolved again).
@@ -1052,25 +1056,33 @@
AndroidClasspathContainerInitializer.updateProjects(array);
mPostLoadProjectsToResolve.clear();
}
+
+ progress.worked(10);
}
}
// Notify resource changed listeners
- progress.subTask("Refresh UI");
- progress.setWorkRemaining(mResourceRefreshListener.size());
+ progress.setTaskName("Refresh UI");
+ progress.setWorkRemaining(mTargetChangeListeners.size());
// Clone the list before iterating, to avoid Concurrent Modification
// exceptions
- List<Runnable> listeners = (List<Runnable>)mResourceRefreshListener.clone();
- for (Runnable listener : listeners) {
- try {
- AdtPlugin.getDisplay().syncExec(listener);
- } catch (Exception e) {
- AdtPlugin.log(e, "ResourceRefreshListener Failed"); //$NON-NLS-1$
- } finally {
- progress.worked(1);
+ final List<ITargetChangeListener> listeners =
+ (List<ITargetChangeListener>)mTargetChangeListeners.clone();
+ final SubMonitor progress2 = progress;
+ AdtPlugin.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ for (ITargetChangeListener listener : listeners) {
+ try {
+ listener.onTargetsLoaded();
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$
+ } finally {
+ progress2.worked(1);
+ }
+ }
}
- }
+ });
} finally {
if (monitor != null) {
monitor.done();
@@ -1316,12 +1328,42 @@
}, IResourceDelta.ADDED | IResourceDelta.CHANGED);
}
- public void addResourceChangedListener(Runnable resourceRefreshListener) {
- mResourceRefreshListener.add(resourceRefreshListener);
+ /**
+ * Adds a new {@link ITargetChangeListener} to be notified when a new SDK is loaded, or when
+ * a project has its target changed.
+ */
+ public void addTargetListener(ITargetChangeListener listener) {
+ mTargetChangeListeners.add(listener);
}
- public void removeResourceChangedListener(Runnable resourceRefreshListener) {
- mResourceRefreshListener.remove(resourceRefreshListener);
+ /**
+ * Removes an existing {@link ITargetChangeListener}.
+ * @see #addTargetListener(ITargetChangeListener)
+ */
+ public void removeTargetListener(ITargetChangeListener listener) {
+ mTargetChangeListeners.remove(listener);
+ }
+
+ /**
+ * Updates all the {@link ITargetChangeListener} that a target has changed for a given project.
+ * <p/>Only editors related to that project should reload.
+ */
+ @SuppressWarnings("unchecked")
+ public void updateTargetListener(final IProject project) {
+ final List<ITargetChangeListener> listeners =
+ (List<ITargetChangeListener>)mTargetChangeListeners.clone();
+
+ AdtPlugin.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ for (ITargetChangeListener listener : listeners) {
+ try {
+ listener.onProjectTargetChange(project);
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$
+ }
+ }
+ }
+ });
}
public static synchronized OutputStream getErrorStream() {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
index 96068c2..e71ae47 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
@@ -203,6 +203,9 @@
// get a project object
IProject project = getProject();
+ // Top level check to make sure the build can move forward.
+ abortOnBadSetup(project);
+
// get the list of referenced projects.
IProject[] referencedProjects = ProjectHelper.getReferencedProjects(project);
IJavaProject[] referencedJavaProjects = getJavaProjects(referencedProjects);
@@ -262,69 +265,12 @@
}
}
}
-
- // do some extra check, in case the output files are not present. This
- // will force to recreate them.
- IResource tmp = null;
-
- if (mPackageResources == false && outputFolder != null) {
- tmp = outputFolder.findMember(AndroidConstants.FN_RESOURCES_AP_);
- if (tmp == null || tmp.exists() == false) {
- mPackageResources = true;
- mBuildFinalPackage = true;
- }
- }
- if (mConvertToDex == false && outputFolder != null) {
- tmp = outputFolder.findMember(AndroidConstants.FN_CLASSES_DEX);
- if (tmp == null || tmp.exists() == false) {
- mConvertToDex = true;
- mBuildFinalPackage = true;
- }
- }
-
- // get the extra configs for the project. This will give us a list of custom apk
- // to build based on a restricted set of resources.
- Map<String, String> configs = Sdk.getCurrent().getProjectApkConfigs(project);
-
- // also check the final file(s)!
- String finalPackageName = getFileName(project, null /*config*/);
- if (mBuildFinalPackage == false && outputFolder != null) {
- tmp = outputFolder.findMember(finalPackageName);
- if (tmp == null || (tmp instanceof IFile &&
- tmp.exists() == false)) {
- String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
- mBuildFinalPackage = true;
- }
-
- if (configs != null) {
- Set<Entry<String, String>> entrySet = configs.entrySet();
-
- for (Entry<String, String> entry : entrySet) {
- String filename = getFileName(project, entry.getKey());
-
- tmp = outputFolder.findMember(filename);
- if (tmp == null || (tmp instanceof IFile &&
- tmp.exists() == false)) {
- String msg = String.format(Messages.s_Missing_Repackaging,
- finalPackageName);
- AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
- mBuildFinalPackage = true;
- break;
- }
- }
- }
- }
-
+
// store the build status in the persistent storage
saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex);
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
- // At this point, we can abort the build if we have to, as we have computed
- // our resource delta and stored the result.
- abortOnBadSetup(project);
-
if (dv != null && dv.mXmlError) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.Xml_Error);
@@ -351,6 +297,82 @@
return referencedProjects;
}
+ // get the extra configs for the project.
+ // The map contains (name, filter) where 'name' is a name to be used in the apk filename,
+ // and filter is the resource filter to be used in the aapt -c parameters to restrict
+ // which resource configurations to package in the apk.
+ Map<String, String> configs = Sdk.getCurrent().getProjectApkConfigs(project);
+
+ // do some extra check, in case the output files are not present. This
+ // will force to recreate them.
+ IResource tmp = null;
+
+ if (mPackageResources == false) {
+ // check the full resource package
+ tmp = outputFolder.findMember(AndroidConstants.FN_RESOURCES_AP_);
+ if (tmp == null || tmp.exists() == false) {
+ mPackageResources = true;
+ mBuildFinalPackage = true;
+ } else {
+ // if the full package is present, we check the filtered resource packages as well
+ if (configs != null) {
+ Set<Entry<String, String>> entrySet = configs.entrySet();
+
+ for (Entry<String, String> entry : entrySet) {
+ String filename = String.format(AndroidConstants.FN_RESOURCES_S_AP_,
+ entry.getKey());
+
+ tmp = outputFolder.findMember(filename);
+ if (tmp == null || (tmp instanceof IFile &&
+ tmp.exists() == false)) {
+ String msg = String.format(Messages.s_Missing_Repackaging, filename);
+ AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
+ mPackageResources = true;
+ mBuildFinalPackage = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // check classes.dex is present. If not we force to recreate it.
+ if (mConvertToDex == false) {
+ tmp = outputFolder.findMember(AndroidConstants.FN_CLASSES_DEX);
+ if (tmp == null || tmp.exists() == false) {
+ mConvertToDex = true;
+ mBuildFinalPackage = true;
+ }
+ }
+
+ // also check the final file(s)!
+ String finalPackageName = getFileName(project, null /*config*/);
+ if (mBuildFinalPackage == false) {
+ tmp = outputFolder.findMember(finalPackageName);
+ if (tmp == null || (tmp instanceof IFile &&
+ tmp.exists() == false)) {
+ String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
+ AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
+ mBuildFinalPackage = true;
+ } else if (configs != null) {
+ // if the full apk is present, we check the filtered apk as well
+ Set<Entry<String, String>> entrySet = configs.entrySet();
+
+ for (Entry<String, String> entry : entrySet) {
+ String filename = getFileName(project, entry.getKey());
+
+ tmp = outputFolder.findMember(filename);
+ if (tmp == null || (tmp instanceof IFile &&
+ tmp.exists() == false)) {
+ String msg = String.format(Messages.s_Missing_Repackaging, filename);
+ AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
+ mBuildFinalPackage = true;
+ break;
+ }
+ }
+ }
+ }
+
// at this point we know if we need to recreate the temporary apk
// or the dex file, but we don't know if we simply need to recreate them
// because they are missing
@@ -396,6 +418,9 @@
// first we check if we need to package the resources.
if (mPackageResources) {
+ // remove some aapt_package only markers.
+ removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_PACKAGE);
+
// need to figure out some path before we can execute aapt;
// resource to the AndroidManifest.xml file
@@ -552,7 +577,8 @@
* @param osAssetsPath The path to the assets folder. This can be null.
* @param osOutFilePath The path to the temporary resource file to create.
* @param configFilter The configuration filter for the resources to include
- * (used with -c option)
+ * (used with -c option, for example "port,en,fr" to include portrait, English and French
+ * resources.)
* @return true if success, false otherwise.
*/
private boolean executeAapt(IProject project, String osManifestPath,
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java
index 6b0810a..e2e9728 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java
@@ -149,6 +149,15 @@
private final static Pattern sPattern8Line1 = Pattern.compile(
"^(invalid resource directory name): (.*)$"); //$NON-NLS-1$
+ /**
+ * 2 line aapt error<br>
+ * "ERROR: Invalid configuration: foo"<br>
+ * " ^^^"<br>
+ * There's no need to parse the 2nd line.
+ */
+ private final static Pattern sPattern9Line1 = Pattern.compile(
+ "^Invalid configuration: (.+)$"); //$NON-NLS-1$
+
/** SAX Parser factory. */
private SAXParserFactory mParserFactory;
@@ -440,8 +449,8 @@
String location = m.group(1);
// check the values and attempt to mark the file.
- if (checkAndMark(location, lineStr, msg, osRoot,
- project, IMarker.SEVERITY_ERROR) == false) {
+ if (checkAndMark(location, lineStr, msg, osRoot, project,
+ AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
continue;
@@ -465,7 +474,7 @@
// display the error
if (checkAndMark(location, null, msg, osRoot, project,
- IMarker.SEVERITY_ERROR) == false) {
+ AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
@@ -488,8 +497,8 @@
String lineStr = m.group(2);
// check the values and attempt to mark the file.
- if (checkAndMark(location, lineStr, msg, osRoot,
- project, IMarker.SEVERITY_ERROR) == false) {
+ if (checkAndMark(location, lineStr, msg, osRoot, project,
+ AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
continue;
@@ -502,8 +511,8 @@
String msg = m.group(3);
// check the values and attempt to mark the file.
- if (checkAndMark(location, lineStr, msg, osRoot,
- project, IMarker.SEVERITY_ERROR) == false) {
+ if (checkAndMark(location, lineStr, msg, osRoot, project,
+ AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
@@ -526,8 +535,8 @@
String lineStr = m.group(2);
// check the values and attempt to mark the file.
- if (checkAndMark(location, lineStr, msg, osRoot,
- project, IMarker.SEVERITY_ERROR) == false) {
+ if (checkAndMark(location, lineStr, msg, osRoot, project,
+ AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
@@ -542,8 +551,8 @@
String msg = m.group(3);
// check the values and attempt to mark the file.
- if (checkAndMark(location, lineStr, msg, osRoot,
- project,IMarker.SEVERITY_WARNING) == false) {
+ if (checkAndMark(location, lineStr, msg, osRoot, project,
+ AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) {
return true;
}
@@ -558,8 +567,8 @@
String msg = m.group(3);
// check the values and attempt to mark the file.
- if (checkAndMark(location, lineStr, msg, osRoot,
- project, IMarker.SEVERITY_ERROR) == false) {
+ if (checkAndMark(location, lineStr, msg, osRoot, project,
+ AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
@@ -574,7 +583,25 @@
// check the values and attempt to mark the file.
if (checkAndMark(location, null, msg, osRoot, project,
- IMarker.SEVERITY_ERROR) == false) {
+ AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
+ return true;
+ }
+
+ // success, go to the next line
+ continue;
+ }
+
+ m = sPattern9Line1.matcher(p);
+ if (m.matches()) {
+ String badConfig = m.group(1);
+ String msg = String.format("APK Configuration filter '%1$s' is invalid", badConfig);
+
+ // skip the next line
+ i++;
+
+ // check the values and attempt to mark the file.
+ if (checkAndMark(null /*location*/, null, msg, osRoot, project,
+ AndroidConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
@@ -659,23 +686,25 @@
/**
* Check if the parameters gotten from the error output are valid, and mark
* the file with an AAPT marker.
- * @param location
+ * @param location the full OS path of the error file. If null, the project is marked
* @param lineStr
* @param message
* @param root The root directory of the project, in OS specific format.
* @param project
+ * @param markerId The marker id to put.
* @param severity The severity of the marker to put (IMarker.SEVERITY_*)
- * @return true if the parameters were valid and the file was marked
- * sucessfully.
+ * @return true if the parameters were valid and the file was marked successfully.
*
* @see IMarker
*/
private final boolean checkAndMark(String location, String lineStr,
- String message, String root, IProject project, int severity) {
+ String message, String root, IProject project, String markerId, int severity) {
// check this is in fact a file
- File f = new File(location);
- if (f.exists() == false) {
- return false;
+ if (location != null) {
+ File f = new File(location);
+ if (f.exists() == false) {
+ return false;
+ }
}
// get the line number
@@ -692,16 +721,18 @@
}
// add the marker
- IResource f2 = getResourceFromFullPath(location, root, project);
- if (f2 == null) {
- return false;
+ IResource f2 = project;
+ if (location != null) {
+ f2 = getResourceFromFullPath(location, root, project);
+ if (f2 == null) {
+ return false;
+ }
}
// check if there's a similar marker already, since aapt is launched twice
boolean markerAlreadyExists = false;
try {
- IMarker[] markers = f2.findMarkers(AndroidConstants.MARKER_AAPT, true,
- IResource.DEPTH_ZERO);
+ IMarker[] markers = f2.findMarkers(markerId, true, IResource.DEPTH_ZERO);
for (IMarker marker : markers) {
int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1);
@@ -732,10 +763,10 @@
if (markerAlreadyExists == false) {
if (line != -1) {
- BaseProjectHelper.addMarker(f2, AndroidConstants.MARKER_AAPT, message, line,
+ BaseProjectHelper.addMarker(f2, markerId, message, line,
severity);
} else {
- BaseProjectHelper.addMarker(f2, AndroidConstants.MARKER_AAPT, message, severity);
+ BaseProjectHelper.addMarker(f2, markerId, message, severity);
}
}
@@ -851,7 +882,7 @@
* Aborts the build if the SDK/project setups are broken. This does not
* display any errors.
*
- * @param javaProject The {@link IJavaProject} being compiled.
+ * @param project The {@link IJavaProject} being compiled.
* @throws CoreException
*/
protected final void abortOnBadSetup(IProject project) throws CoreException {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java
index 26d96d7..65ad4f5 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java
@@ -57,8 +57,11 @@
private Field mConsoleErr;
/**
- * Loads the dex library from a file path. The loaded library can be used with the
- * {@link DexWrapper} object returned by {@link #getWrapper()}
+ * Loads the dex library from a file path.
+ *
+ * The loaded library can be used via
+ * {@link DexWrapper#run(String, String[], boolean, PrintStream, PrintStream)}.
+ *
* @param osFilepath the location of the dex.jar file.
* @return an IStatus indicating the result of the load.
*/
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
index 958cac2..a0e446c 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
@@ -234,6 +234,10 @@
// get the project objects
IProject project = getProject();
+
+ // Top level check to make sure the build can move forward.
+ abortOnBadSetup(project);
+
IJavaProject javaProject = JavaCore.create(project);
IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
@@ -284,10 +288,6 @@
saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mCompileResources);
// TODO also needs to store the list of aidl to compile/remove
- // At this point we have stored what needs to be build, so we can
- // do some high level test and abort if needed.
- abortOnBadSetup(project);
-
// if there was some XML errors, we just return w/o doing
// anything since we've put some markers in the files anyway.
if (dv != null && dv.mXmlError) {
@@ -381,7 +381,7 @@
// mark the manifest file
String message = String.format(Messages.Package_s_Doesnt_Exist_Error,
mManifestPackage);
- BaseProjectHelper.addMarker(manifest, AndroidConstants.MARKER_AAPT, message,
+ BaseProjectHelper.addMarker(manifest, AndroidConstants.MARKER_AAPT_COMPILE, message,
IMarker.SEVERITY_ERROR);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, message);
@@ -409,8 +409,8 @@
String osManifestPath = manifestLocation.toOSString();
// remove the aapt markers
- removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT);
- removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT);
+ removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT_COMPILE);
+ removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT_COMPILE);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.Preparing_Generated_Files);
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/AndroidPreferencePage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/AndroidPreferencePage.java
index c27c106..458f78e 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/AndroidPreferencePage.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/AndroidPreferencePage.java
@@ -17,11 +17,19 @@
package com.android.ide.eclipse.adt.preferences;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdkuilib.SdkTargetSelector;
+import org.eclipse.core.resources.IProject;
import org.eclipse.jface.preference.DirectoryFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
@@ -80,6 +88,9 @@
*/
private static class SdkDirectoryFieldEditor extends DirectoryFieldEditor {
+ private SdkTargetSelector mTargetSelector;
+ private TargetChangedListener mTargetChangeListener;
+
public SdkDirectoryFieldEditor(String name, String labelText, Composite parent) {
super(name, labelText, parent);
setEmptyStringAllowed(false);
@@ -131,5 +142,68 @@
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
return super.getTextControl(parent);
}
+
+ /* (non-Javadoc)
+ * Method declared on StringFieldEditor (and FieldEditor).
+ */
+ @Override
+ protected void doFillIntoGrid(Composite parent, int numColumns) {
+ super.doFillIntoGrid(parent, numColumns);
+
+ GridData gd;
+ Label l = new Label(parent, SWT.NONE);
+ l.setText("Note: The list of SDK Targets below is only reloaded once you hit 'Apply' or 'OK'.");
+ gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalSpan = numColumns;
+ l.setLayoutData(gd);
+
+ try {
+ // We may not have an sdk if the sdk path pref is empty or not valid.
+ Sdk sdk = Sdk.getCurrent();
+ IAndroidTarget[] targets = sdk != null ? sdk.getTargets() : null;
+
+ mTargetSelector = new SdkTargetSelector(parent,
+ targets,
+ false, /*allowSelection*/
+ false /*multipleSelection*/);
+ gd = (GridData) mTargetSelector.getLayoutData();
+ gd.horizontalSpan = numColumns;
+
+ if (mTargetChangeListener == null) {
+ mTargetChangeListener = new TargetChangedListener();
+ AdtPlugin.getDefault().addTargetListener(mTargetChangeListener);
+ }
+ } catch (Exception e) {
+ // We need to catch *any* exception that arises here, otherwise it disables
+ // the whole pref panel. We can live without the Sdk target selector but
+ // not being able to actually set an sdk path.
+ AdtPlugin.log(e, "SdkTargetSelector failed");
+ }
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ if (mTargetChangeListener != null) {
+ AdtPlugin.getDefault().removeTargetListener(mTargetChangeListener);
+ mTargetChangeListener = null;
+ }
+ }
+
+ private class TargetChangedListener implements ITargetChangeListener {
+ public void onProjectTargetChange(IProject changedProject) {
+ // do nothing.
+ }
+
+ public void onTargetsLoaded() {
+ if (mTargetSelector != null) {
+ // We may not have an sdk if the sdk path pref is empty or not valid.
+ Sdk sdk = Sdk.getCurrent();
+ IAndroidTarget[] targets = sdk != null ? sdk.getTargets() : null;
+
+ mTargetSelector.setTargets(targets);
+ }
+ }
+ }
}
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java
index 339dcd0..d686830 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java
@@ -266,7 +266,6 @@
// We schedule a new job to put the marker after.
final String fmessage = markerMessage;
Job markerJob = new Job("Android SDK: Resolving error markers") {
- @SuppressWarnings("unchecked")
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
@@ -296,7 +295,6 @@
// In some cases, the workspace may be locked for modification when we pass
// here, so we schedule a new job to put the marker after.
Job markerJob = new Job("Android SDK: Resolving error markers") {
- @SuppressWarnings("unchecked")
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java
index 584dd0d..a4c019f 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java
@@ -18,6 +18,7 @@
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.sdklib.IAndroidTarget;
+import com.android.sdkuilib.ApkConfigWidget;
import com.android.sdkuilib.SdkTargetSelector;
import org.eclipse.core.resources.IProject;
@@ -32,6 +33,8 @@
import org.eclipse.ui.IWorkbenchPropertyPage;
import org.eclipse.ui.dialogs.PropertyPage;
+import java.util.Map;
+
/**
* Property page for "Android" project.
* This is accessible from the Package Explorer when right clicking a project and choosing
@@ -42,6 +45,7 @@
private IProject mProject;
private SdkTargetSelector mSelector;
+ private ApkConfigWidget mApkConfigWidget;
public AndroidPropertyPage() {
// pass
@@ -51,7 +55,14 @@
protected Control createContents(Composite parent) {
// get the element (this is not yet valid in the constructor).
mProject = (IProject)getElement();
-
+
+ // get the targets from the sdk
+ IAndroidTarget[] targets = null;
+ if (Sdk.getCurrent() != null) {
+ targets = Sdk.getCurrent().getTargets();
+ }
+
+ // build the UI.
Composite top = new Composite(parent, SWT.NONE);
top.setLayoutData(new GridData(GridData.FILL_BOTH));
top.setLayout(new GridLayout(1, false));
@@ -59,20 +70,28 @@
Label l = new Label(top, SWT.NONE);
l.setText("Project Target");
- // get the targets from the sdk
- IAndroidTarget[] targets = null;
- if (Sdk.getCurrent() != null) {
- targets = Sdk.getCurrent().getTargets();
- }
-
- // build the UI.
mSelector = new SdkTargetSelector(top, targets, false /*allowMultipleSelection*/);
- if (Sdk.getCurrent() != null) {
- IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
+ l = new Label(top, SWT.SEPARATOR | SWT.HORIZONTAL);
+ l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ l = new Label(top, SWT.NONE);
+ l.setText("Project APK Configurations");
+
+ mApkConfigWidget = new ApkConfigWidget(top);
+
+ // fill the ui
+ Sdk currentSdk = Sdk.getCurrent();
+ if (currentSdk != null && mProject.isOpen()) {
+ // get the target
+ IAndroidTarget target = currentSdk.getTarget(mProject);
if (target != null) {
mSelector.setSelection(target);
}
+
+ // get the apk configurations
+ Map<String, String> configs = currentSdk.getProjectApkConfigs(mProject);
+ mApkConfigWidget.fillTable(configs);
}
mSelector.setSelectionListener(new SelectionAdapter() {
@@ -83,14 +102,20 @@
setValid(target != null);
}
});
+
+ if (mProject.isOpen() == false) {
+ // disable the ui.
+ }
return top;
}
@Override
public boolean performOk() {
- if (Sdk.getCurrent() != null) {
- Sdk.getCurrent().setProject(mProject, mSelector.getFirstSelected());
+ Sdk currentSdk = Sdk.getCurrent();
+ if (currentSdk != null) {
+ currentSdk.setProject(mProject, mSelector.getFirstSelected(),
+ mApkConfigWidget.getApkConfigs());
}
return true;
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java
index 2309181..a8852e7 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java
@@ -27,6 +27,7 @@
import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors;
import com.android.layoutlib.api.ILayoutBridge;
import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
import java.util.Hashtable;
import java.util.Map;
@@ -43,6 +44,7 @@
public final static int DESCRIPTOR_RESOURCES = 5;
public final static int DESCRIPTOR_SEARCHABLE = 6;
public final static int DESCRIPTOR_PREFERENCES = 7;
+ public final static int DESCRIPTOR_GADGET_PROVIDER = 8;
public final static class LayoutBridge {
/** Link to the layout bridge */
@@ -51,6 +53,8 @@
public LoadStatus status = LoadStatus.LOADING;
public ClassLoader classLoader;
+
+ public int apiLevel;
}
private final IAndroidTarget mTarget;
@@ -93,6 +97,7 @@
/**
* Creates an AndroidTargetData object.
+ * @param optionalLibraries
*/
void setExtraData(IResourceRepository systemResourceRepository,
AndroidManifestDescriptors manifestDescriptors,
@@ -105,6 +110,7 @@
String[] broadcastIntentActionValues,
String[] serviceIntentActionValues,
String[] intentCategoryValues,
+ IOptionalLibrary[] optionalLibraries,
ProjectResources resources,
LayoutBridge layoutBridge) {
@@ -120,8 +126,9 @@
setPermissions(permissionValues);
setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues,
serviceIntentActionValues, intentCategoryValues);
+ setOptionalLibraries(optionalLibraries);
}
-
+
public DexWrapper getDexWrapper() {
return mDexWrapper;
}
@@ -151,6 +158,8 @@
return ResourcesDescriptors.getInstance();
case DESCRIPTOR_PREFERENCES:
return mXmlDescriptors.getPreferencesProvider();
+ case DESCRIPTOR_GADGET_PROVIDER:
+ return mXmlDescriptors.getGadgetProvider();
case DESCRIPTOR_SEARCHABLE:
return mXmlDescriptors.getSearchableProvider();
default :
@@ -283,6 +292,20 @@
setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$
setValues("(category,android:name)", intentCategoryValues); //$NON-NLS-1$
}
+
+ private void setOptionalLibraries(IOptionalLibrary[] optionalLibraries) {
+ String[] values;
+
+ if (optionalLibraries == null) {
+ values = new String[0];
+ } else {
+ values = new String[optionalLibraries.length];
+ for (int i = 0; i < optionalLibraries.length; i++) {
+ values[i] = optionalLibraries[i].getName();
+ }
+ }
+ setValues("(uses-library,android:name)", values);
+ }
/**
* Sets a (name, values) pair in the hash map.
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java
index aab660d..04baeba 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java
@@ -92,7 +92,7 @@
try {
SubMonitor progress = SubMonitor.convert(monitor,
String.format("Parsing SDK %1$s", mAndroidTarget.getName()),
- 200);
+ 14);
AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget);
@@ -107,15 +107,14 @@
// we have loaded dx.
targetData.setDexWrapper(dexWrapper);
+ progress.worked(1);
// parse the rest of the data.
- progress.setWorkRemaining(120);
AndroidJarLoader classLoader =
new AndroidJarLoader(mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
preload(classLoader, progress.newChild(40, SubMonitor.SUPPRESS_NONE));
- progress.setWorkRemaining(80);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
@@ -124,7 +123,7 @@
// get the resource Ids.
progress.subTask("Resource IDs");
IResourceRepository frameworkRepository = collectResourceIds(classLoader);
- progress.worked(5);
+ progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
@@ -133,7 +132,7 @@
// get the permissions
progress.subTask("Permissions");
String[] permissionValues = collectPermissions(classLoader);
- progress.worked(5);
+ progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
@@ -147,7 +146,7 @@
ArrayList<String> categories = new ArrayList<String>();
collectIntentFilterActionsAndCategories(activity_actions, broadcast_actions,
service_actions, categories);
- progress.worked(5);
+ progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
@@ -158,12 +157,14 @@
AttrsXmlParser attrsXmlParser = new AttrsXmlParser(
mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES));
attrsXmlParser.preload();
+ progress.worked(1);
progress.subTask("Manifest definitions");
AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser(
mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES),
attrsXmlParser);
attrsManifestXmlParser.preload();
+ progress.worked(1);
Collection<ViewClassInfo> mainList = new ArrayList<ViewClassInfo>();
Collection<ViewClassInfo> groupList = new ArrayList<ViewClassInfo>();
@@ -171,7 +172,7 @@
// collect the layout/widgets classes
progress.subTask("Widgets and layouts");
collectLayoutClasses(classLoader, attrsXmlParser, mainList, groupList,
- progress.newChild(40));
+ progress.newChild(1));
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
@@ -185,7 +186,7 @@
mainList.clear();
groupList.clear();
collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList,
- progress.newChild(5));
+ progress.newChild(1));
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
@@ -202,6 +203,11 @@
attrsManifestXmlParser);
Map<String, Map<String, Integer>> enumValueMap = attrsXmlParser.getEnumFlagValues();
+ Map<String, DeclareStyleableInfo> xmlGadgetMap = null;
+ if (mAndroidTarget.getApiVersionNumber() >= 3) {
+ xmlGadgetMap = collectGadgetDefinitions(attrsXmlParser);
+ }
+
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
@@ -210,7 +216,7 @@
// the PlatformData object.
AndroidManifestDescriptors manifestDescriptors = new AndroidManifestDescriptors();
manifestDescriptors.updateDescriptors(manifestMap);
- progress.worked(10);
+ progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
@@ -218,7 +224,7 @@
LayoutDescriptors layoutDescriptors = new LayoutDescriptors();
layoutDescriptors.updateDescriptors(layoutViewsInfo, layoutGroupsInfo);
- progress.worked(10);
+ progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
@@ -226,25 +232,28 @@
MenuDescriptors menuDescriptors = new MenuDescriptors();
menuDescriptors.updateDescriptors(xmlMenuMap);
- progress.worked(10);
+ progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
XmlDescriptors xmlDescriptors = new XmlDescriptors();
- xmlDescriptors.updateDescriptors(xmlSearchableMap, preferencesInfo,
+ xmlDescriptors.updateDescriptors(
+ xmlSearchableMap,
+ xmlGadgetMap,
+ preferencesInfo,
preferenceGroupsInfo);
- progress.worked(10);
+ progress.worked(1);
// load the framework resources.
ProjectResources resources = ResourceManager.getInstance().loadFrameworkResources(
mAndroidTarget);
- progress.worked(10);
+ progress.worked(1);
// now load the layout lib bridge
LayoutBridge layoutBridge = loadLayoutBridge();
- progress.worked(10);
+ progress.worked(1);
// and finally create the PlatformData with all that we loaded.
targetData.setExtraData(frameworkRepository,
@@ -258,6 +267,7 @@
broadcast_actions.toArray(new String[broadcast_actions.size()]),
service_actions.toArray(new String[service_actions.size()]),
categories.toArray(new String[categories.size()]),
+ mAndroidTarget.getOptionalLibraries(),
resources,
layoutBridge);
@@ -268,10 +278,6 @@
AdtPlugin.logAndPrintError(e, TAG, "SDK parser failed"); //$NON-NLS-1$
AdtPlugin.printToConsole("SDK parser failed", e.getMessage());
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "SDK parser failed", e);
- } finally {
- if (monitor != null) {
- monitor.done();
- }
}
}
@@ -605,6 +611,31 @@
}
/**
+ * Collects all gadgetProviderInfo definition information from the attrs.xml and returns it.
+ *
+ * @param attrsXmlParser The parser of the attrs.xml file
+ */
+ private Map<String, DeclareStyleableInfo> collectGadgetDefinitions(
+ AttrsXmlParser attrsXmlParser) {
+ Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList();
+ Map<String, DeclareStyleableInfo> map2 = new HashMap<String, DeclareStyleableInfo>();
+ for (String key : new String[] { "GadgetProviderInfo" }) { //$NON-NLS-1$
+ if (map.containsKey(key)) {
+ map2.put(key, map.get(key));
+ } else {
+ AdtPlugin.log(IStatus.WARNING,
+ "Gadget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
+ key, attrsXmlParser.getOsAttrsXmlPath());
+ AdtPlugin.printErrorToConsole("Android Framework Parser",
+ String.format("Gadget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
+ key, attrsXmlParser.getOsAttrsXmlPath()));
+ }
+ }
+
+ return Collections.unmodifiableMap(map2);
+ }
+
+ /**
* Collects all manifest definition information from the attrs_manifest.xml and returns it.
*/
private Map<String, DeclareStyleableInfo> collectManifestDefinitions(
@@ -650,6 +681,15 @@
layoutBridge.status = LoadStatus.FAILED;
AdtPlugin.log(IStatus.ERROR, "Failed to load " + AndroidConstants.CLASS_BRIDGE); //$NON-NLS-1$
} else {
+ // get the api level
+ try {
+ layoutBridge.apiLevel = layoutBridge.bridge.getApiLevel();
+ } catch (AbstractMethodError e) {
+ // the first version of the api did not have this method
+ layoutBridge.apiLevel = 1;
+ }
+
+ // and mark the lib as loaded.
layoutBridge.status = LoadStatus.LOADED;
}
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java
index c7773cc..ba0b568 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java
@@ -18,6 +18,7 @@
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
+import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge;
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor;
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener;
import com.android.prefs.AndroidLocation.AndroidLocationException;
@@ -33,6 +34,7 @@
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
@@ -69,6 +71,22 @@
private final String mDocBaseUrl;
/**
+ * Classes implementing this interface will receive notification when targets are changed.
+ */
+ public interface ITargetChangeListener {
+ /**
+ * Sent when project has its target changed.
+ */
+ void onProjectTargetChange(IProject changedProject);
+
+ /**
+ * Called when the targets are loaded (either the SDK finished loading when Eclipse starts,
+ * or the SDK is changed).
+ */
+ void onTargetsLoaded();
+ }
+
+ /**
* Loads an SDK and returns an {@link Sdk} object if success.
* @param sdkLocation the OS path to the SDK.
*/
@@ -163,24 +181,87 @@
}
/**
- * Associates an {@link IProject} and an {@link IAndroidTarget}.
+ * Sets a new target and a new list of Apk configuration for a given project.
+ *
+ * @param project the project to receive the new apk configurations
+ * @param target The new target to set, or <code>null</code> to not change the current target.
+ * @param apkConfigMap a map of apk configurations. The map contains (name, filter) where name
+ * is the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
+ * resource configuration to include in the apk (see aapt -c). Can be <code>null</code> if the
+ * apk configurations should not be updated.
*/
- public void setProject(IProject project, IAndroidTarget target) {
+ public void setProject(IProject project, IAndroidTarget target,
+ Map<String, String> apkConfigMap) {
synchronized (mProjectTargetMap) {
- // look for the current target of the project
- IAndroidTarget previousTarget = mProjectTargetMap.get(project);
-
- if (target != previousTarget) {
- // save the target hash string in the project persistent property
- setProjectTargetHashString(project, target.hashString());
-
- // put it in a local map for easy access.
- mProjectTargetMap.put(project, target);
+ boolean resolveProject = false;
+ boolean compileProject = false;
+ boolean cleanProject = false;
- // recompile the project if needed.
+ ProjectProperties properties = ProjectProperties.load(
+ project.getLocation().toOSString(), PropertyType.DEFAULT);
+ if (properties == null) {
+ // doesn't exist yet? we create it.
+ properties = ProjectProperties.create(project.getLocation().toOSString(),
+ PropertyType.DEFAULT);
+ }
+
+ if (target != null) {
+ // look for the current target of the project
+ IAndroidTarget previousTarget = mProjectTargetMap.get(project);
+
+ if (target != previousTarget) {
+ // save the target hash string in the project persistent property
+ properties.setAndroidTarget(target);
+
+ // put it in a local map for easy access.
+ mProjectTargetMap.put(project, target);
+
+ resolveProject = true;
+ }
+ }
+
+ if (apkConfigMap != null) {
+ // save the apk configs in the project persistent property
+ cleanProject = ApkConfigurationHelper.setConfigs(properties, apkConfigMap);
+
+ // put it in a local map for easy access.
+ mProjectApkConfigMap.put(project, apkConfigMap);
+
+ compileProject = true;
+ }
+
+ // we are done with the modification. Save the property file.
+ try {
+ properties.save();
+ } catch (IOException e) {
+ AdtPlugin.log(e, "Failed to save default.properties for project '%s'",
+ project.getName());
+ }
+
+ if (resolveProject) {
+ // force a resolve of the project by updating the classpath container.
IJavaProject javaProject = JavaCore.create(project);
AndroidClasspathContainerInitializer.updateProjects(
new IJavaProject[] { javaProject });
+ } else if (compileProject) {
+ // If there was removed configs, we clean instead of build
+ // (to remove the obsolete ap_ and apk file from removed configs).
+ try {
+ project.build(cleanProject ?
+ IncrementalProjectBuilder.CLEAN_BUILD :
+ IncrementalProjectBuilder.FULL_BUILD,
+ null);
+ } catch (CoreException e) {
+ // failed to build? force resolve instead.
+ IJavaProject javaProject = JavaCore.create(project);
+ AndroidClasspathContainerInitializer.updateProjects(
+ new IJavaProject[] { javaProject });
+ }
+ }
+
+ // finally, update the opened editors.
+ if (resolveProject) {
+ AdtPlugin.getDefault().updateTargetListener(project);
}
}
}
@@ -218,7 +299,12 @@
*/
private static String loadProjectProperties(IProject project, Sdk sdkStorage) {
// load the default.properties from the project folder.
- ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString(),
+ IPath location = project.getLocation();
+ if (location == null) { // can return null when the project is being deleted.
+ // do nothing and return null;
+ return null;
+ }
+ ProjectProperties properties = ProjectProperties.load(location.toOSString(),
PropertyType.DEFAULT);
if (properties == null) {
AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'",
@@ -229,7 +315,7 @@
if (sdkStorage != null) {
Map<String, String> configMap = ApkConfigurationHelper.getConfigs(properties);
- if (configMap.size() > 0) {
+ if (configMap != null) {
sdkStorage.mProjectApkConfigMap.put(project, configMap);
}
}
@@ -296,40 +382,6 @@
return mProjectApkConfigMap.get(project);
}
- public void setProjectApkConfigs(IProject project, Map<String, String> configMap)
- throws CoreException {
- // first set the new map
- mProjectApkConfigMap.put(project, configMap);
-
- // Now we write this in default.properties.
- // Because we don't want to erase other properties from default.properties, we first load
- // them
- ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString(),
- PropertyType.DEFAULT);
- if (properties == null) {
- // doesn't exist yet? we create it.
- properties = ProjectProperties.create(project.getLocation().toOSString(),
- PropertyType.DEFAULT);
- }
-
- // sets the configs in the property file.
- boolean hasRemovedConfig = ApkConfigurationHelper.setConfigs(properties, configMap);
-
- // and rewrite the file.
- try {
- properties.save();
- } catch (IOException e) {
- AdtPlugin.log(e, "Failed to save default.properties for project '%s'",
- project.getName());
- }
-
- // we're done, force a rebuild. If there was removed config, we clean instead of build
- // (to remove the obsolete ap_ and apk file from removed configs).
- project.build(hasRemovedConfig ?
- IncrementalProjectBuilder.CLEAN_BUILD : IncrementalProjectBuilder.FULL_BUILD,
- null);
- }
-
/**
* Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could
* be <code>null</code>.
@@ -402,8 +454,24 @@
}
public void projectClosed(IProject project) {
- mProjectTargetMap.remove(project);
- mProjectApkConfigMap.remove(project);
+ // get the target project
+ synchronized (mProjectTargetMap) {
+ IAndroidTarget target = mProjectTargetMap.get(project);
+ if (target != null) {
+ // get the bridge for the target, and clear the cache for this project.
+ AndroidTargetData data = mTargetDataMap.get(target);
+ if (data != null) {
+ LayoutBridge bridge = data.getLayoutBridge();
+ if (bridge != null && bridge.status == LoadStatus.LOADED) {
+ bridge.bridge.clearCaches(project);
+ }
+ }
+ }
+
+ // now remove the project for the maps.
+ mProjectTargetMap.remove(project);
+ mProjectApkConfigMap.remove(project);
+ }
}
public void projectDeleted(IProject project) {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java
new file mode 100644
index 0000000..e0d0d5e
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.wizards.actions;
+
+import com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.ui.IWorkbenchWizard;
+
+/**
+ * Delegate for the toolbar action "Android Project".
+ * It displays the Android New Project wizard.
+ */
+public class NewProjectAction extends OpenWizardAction {
+
+ @Override
+ protected IWorkbenchWizard instanciateWizard(IAction action) {
+ return new NewProjectWizard();
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java
new file mode 100644
index 0000000..8c4a115
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.wizards.actions;
+
+import com.android.ide.eclipse.editors.wizards.NewXmlFileWizard;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.ui.IWorkbenchWizard;
+
+/**
+ * Delegate for the toolbar action "Android Project".
+ * It displays the Android New XML file wizard.
+ */
+public class NewXmlFileAction extends OpenWizardAction {
+
+ @Override
+ protected IWorkbenchWizard instanciateWizard(IAction action) {
+ return new NewXmlFileWizard();
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java
new file mode 100644
index 0000000..4fc9dee
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.wizards.actions;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.wizard.WizardDialog;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.IWorkbenchWizard;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
+import org.eclipse.ui.internal.LegacyResourceSupport;
+import org.eclipse.ui.internal.actions.NewWizardShortcutAction;
+import org.eclipse.ui.internal.util.Util;
+
+/**
+ * An abstract action that displays one of our wizards.
+ * Derived classes must provide the actual wizard to display.
+ */
+/*package*/ abstract class OpenWizardAction implements IWorkbenchWindowActionDelegate {
+
+ /**
+ * The wizard dialog width, extracted from {@link NewWizardShortcutAction}
+ */
+ private static final int SIZING_WIZARD_WIDTH = 500;
+
+ /**
+ * The wizard dialog height, extracted from {@link NewWizardShortcutAction}
+ */
+ private static final int SIZING_WIZARD_HEIGHT = 500;
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose()
+ */
+ public void dispose() {
+ // pass
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#init(org.eclipse.ui.IWorkbenchWindow)
+ */
+ public void init(IWorkbenchWindow window) {
+ // pass
+ }
+
+ /**
+ * Opens and display the Android New Project Wizard.
+ * <p/>
+ * Most of this implementation is extracted from {@link NewWizardShortcutAction#run()}.
+ *
+ * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
+ */
+ public void run(IAction action) {
+
+ // get the workbench and the current window
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
+
+ // This code from NewWizardShortcutAction#run() gets the current window selection
+ // and converts it to a workbench structured selection for the wizard, if possible.
+ ISelection selection = window.getSelectionService().getSelection();
+ IStructuredSelection selectionToPass = StructuredSelection.EMPTY;
+ if (selection instanceof IStructuredSelection) {
+ selectionToPass = (IStructuredSelection) selection;
+ } else {
+ // Build the selection from the IFile of the editor
+ IWorkbenchPart part = window.getPartService().getActivePart();
+ if (part instanceof IEditorPart) {
+ IEditorInput input = ((IEditorPart) part).getEditorInput();
+ Class<?> fileClass = LegacyResourceSupport.getFileClass();
+ if (input != null && fileClass != null) {
+ Object file = Util.getAdapter(input, fileClass);
+ if (file != null) {
+ selectionToPass = new StructuredSelection(file);
+ }
+ }
+ }
+ }
+
+ // Create the wizard and initialize it with the selection
+ IWorkbenchWizard wizard = instanciateWizard(action);
+ wizard.init(workbench, selectionToPass);
+
+ // It's not visible yet until a dialog is created and opened
+ Shell parent = window.getShell();
+ WizardDialog dialog = new WizardDialog(parent, wizard);
+ dialog.create();
+
+ // This code comes straight from NewWizardShortcutAction#run()
+ Point defaultSize = dialog.getShell().getSize();
+ dialog.getShell().setSize(
+ Math.max(SIZING_WIZARD_WIDTH, defaultSize.x),
+ Math.max(SIZING_WIZARD_HEIGHT, defaultSize.y));
+ window.getWorkbench().getHelpSystem().setHelp(dialog.getShell(),
+ IWorkbenchHelpContextIds.NEW_WIZARD_SHORTCUT);
+
+ dialog.open();
+ }
+
+ /**
+ * Called by {@link #run(IAction)} to instantiate the actual wizard.
+ *
+ * @param action The action parameter from {@link #run(IAction)}.
+ * @return A new wizard instance. Must not be null.
+ */
+ protected abstract IWorkbenchWizard instanciateWizard(IAction action);
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection)
+ */
+ public void selectionChanged(IAction action, ISelection selection) {
+ // pass
+ }
+
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
index 607159a..cb79796 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
@@ -105,6 +105,8 @@
SdkConstants.FD_LAYOUT + AndroidConstants.WS_SEP;
private static final String VALUES_DIRECTORY =
SdkConstants.FD_VALUES + AndroidConstants.WS_SEP;
+ private static final String GEN_SRC_DIRECTORY =
+ SdkConstants.FD_GEN_SOURCES + AndroidConstants.WS_SEP;
private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$
private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY
@@ -114,7 +116,7 @@
private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY
+ "uses-sdk.template"; //$NON-NLS-1$
private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY
- + "launcher_intent_filter.template"; //$NON-NLS-1$
+ + "launcher_intent_filter.template"; //$NON-NLS-1$
private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY
+ "strings.template"; //$NON-NLS-1$
@@ -341,15 +343,20 @@
// Create folders in the project if they don't already exist
addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
- String[] sourceFolder = new String[] { (String) parameters.get(PARAM_SRC_FOLDER) };
- addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolder, monitor);
+ String[] sourceFolders = new String[] {
+ (String) parameters.get(PARAM_SRC_FOLDER),
+ GEN_SRC_DIRECTORY
+ };
+ addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor);
// Create the resource folders in the project if they don't already exist.
addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
// Setup class path
IJavaProject javaProject = JavaCore.create(project);
- setupSourceFolder(javaProject, sourceFolder[0], monitor);
+ for (String sourceFolder : sourceFolders) {
+ setupSourceFolder(javaProject, sourceFolder, monitor);
+ }
if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
// Create files in the project if they don't already exist
@@ -359,7 +366,7 @@
addIcon(project, monitor);
// Create the default package components
- addSampleCode(project, sourceFolder[0], parameters, stringDictionary, monitor);
+ addSampleCode(project, sourceFolders[0], parameters, stringDictionary, monitor);
// add the string definition file if needed
if (stringDictionary.size() > 0) {
@@ -371,7 +378,8 @@
monitor);
}
- Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET));
+ Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET),
+ null /* apkConfigMap*/);
// Fix the project to make sure all properties are as expected.
// Necessary for existing projects and good for new ones to.
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java
index b1c57a6..e201132 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java
@@ -148,8 +148,11 @@
/** The old common plug-in ID. Please do not use for new features. */
public static final String COMMON_PLUGIN_ID = "com.android.ide.eclipse.common"; //$NON-NLS-1$
- /** aapt marker error. */
- public final static String MARKER_AAPT = COMMON_PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$
+ /** aapt marker error when running the compile command */
+ public final static String MARKER_AAPT_COMPILE = COMMON_PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$
+
+ /** aapt marker error when running the package command */
+ public final static String MARKER_AAPT_PACKAGE = COMMON_PLUGIN_ID + ".aapt2Problem"; //$NON-NLS-1$
/** XML marker error. */
public final static String MARKER_XML = COMMON_PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java
index dca7db0..c7541e9 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java
@@ -19,6 +19,7 @@
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import com.android.sdklib.IAndroidTarget;
@@ -97,8 +98,9 @@
private StructuredTextEditor mTextEditor;
/** Listener for the XML model from the StructuredEditor */
private XmlModelStateListener mXmlModelStateListener;
- /** Listener to update the root node if the resource framework changes */
- private Runnable mResourceRefreshListener;
+ /** Listener to update the root node if the target of the file is changed because of a
+ * SDK location change or a project target change */
+ private ITargetChangeListener mTargetListener;
/**
* Creates a form editor.
@@ -107,15 +109,21 @@
super();
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
- mResourceRefreshListener = new Runnable() {
- public void run() {
- commitPages(false /* onSave */);
+ mTargetListener = new ITargetChangeListener() {
+ public void onProjectTargetChange(IProject changedProject) {
+ if (changedProject == getProject()) {
+ onTargetsLoaded();
+ }
+ }
+ public void onTargetsLoaded() {
+ commitPages(false /* onSave */);
+
// recreate the ui root node always
initUiRootNode(true /*force*/);
}
};
- AdtPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener);
+ AdtPlugin.getDefault().addTargetListener(mTargetListener);
}
// ---- Abstract Methods ----
@@ -340,9 +348,9 @@
}
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
- if (mResourceRefreshListener != null) {
- AdtPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener);
- mResourceRefreshListener = null;
+ if (mTargetListener != null) {
+ AdtPlugin.getDefault().removeTargetListener(mTargetListener);
+ mTargetListener = null;
}
super.dispose();
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java
index cc923bf..f1d62a1 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java
@@ -313,7 +313,7 @@
*
* @param attributes The list of {@link AttributeDescriptor} to compare to.
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
- * See {@link AndroidConstants#NS_RESOURCES} for a common value.
+ * See {@link SdkConstants#NS_RESOURCES} for a common value.
* @param info The {@link AttributeInfo} to know whether it is included in the above list.
* @return True if this {@link AttributeInfo} is already present in
* the {@link AttributeDescriptor} list.
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java
index ca7cac5..eb7dee6 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java
@@ -21,6 +21,7 @@
import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge;
+import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.common.resources.ResourceType;
import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.layout.LayoutEditor.UiEditorActions;
@@ -198,13 +199,21 @@
private ProjectCallback mProjectCallback;
private ILayoutLog mLogger;
+ private boolean mNeedsXmlReload = false;
private boolean mNeedsRecompute = false;
private int mPlatformThemeCount = 0;
private boolean mDisableUpdates = false;
- private boolean mActive = false;
- private Runnable mFrameworkResourceChangeListener = new Runnable() {
- public void run() {
+ /** Listener to update the root node if the target of the file is changed because of a
+ * SDK location change or a project target change */
+ private ITargetChangeListener mTargetListener = new ITargetChangeListener() {
+ public void onProjectTargetChange(IProject changedProject) {
+ if (changedProject == getLayoutEditor().getProject()) {
+ onTargetsLoaded();
+ }
+ }
+
+ public void onTargetsLoaded() {
// because the SDK changed we must reset the configured framework resource.
mConfiguredFrameworkRes = null;
@@ -228,7 +237,7 @@
private final Runnable mConditionalRecomputeRunnable = new Runnable() {
public void run() {
- if (mActive) {
+ if (mLayoutEditor.isGraphicalEditorActive()) {
recomputeLayout();
} else {
mNeedsRecompute = true;
@@ -253,7 +262,7 @@
mMatchImage = factory.getIcon("match"); //$NON-NLS-1$
mErrorImage = factory.getIcon("error"); //$NON-NLS-1$
- AdtPlugin.getDefault().addResourceChangedListener(mFrameworkResourceChangeListener);
+ AdtPlugin.getDefault().addTargetListener(mTargetListener);
}
// ------------------------------------
@@ -561,10 +570,9 @@
@Override
public void dispose() {
- if (mFrameworkResourceChangeListener != null) {
- AdtPlugin.getDefault().removeResourceChangedListener(
- mFrameworkResourceChangeListener);
- mFrameworkResourceChangeListener = null;
+ if (mTargetListener != null) {
+ AdtPlugin.getDefault().removeTargetListener(mTargetListener);
+ mTargetListener = null;
}
LayoutReloadMonitor.getMonitor().removeListener(mEditedFile.getProject(), this);
@@ -1026,25 +1034,36 @@
}
/**
- * Update the layout editor when the Xml model is changed.
+ * Callback for XML model changed. Only update/recompute the layout if the editor is visible
*/
void onXmlModelChanged() {
- GraphicalViewer viewer = getGraphicalViewer();
-
- // try to preserve the selection before changing the content
- SelectionManager selMan = viewer.getSelectionManager();
- ISelection selection = selMan.getSelection();
-
- try {
- viewer.setContents(getModel());
- } finally {
- selMan.setSelection(selection);
- }
-
if (mLayoutEditor.isGraphicalEditorActive()) {
+ doXmlReload(true /* force */);
recomputeLayout();
} else {
- mNeedsRecompute = true;
+ mNeedsXmlReload = true;
+ }
+ }
+
+ /**
+ * Actually performs the XML reload
+ * @see #onXmlModelChanged()
+ */
+ private void doXmlReload(boolean force) {
+ if (force || mNeedsXmlReload) {
+ GraphicalViewer viewer = getGraphicalViewer();
+
+ // try to preserve the selection before changing the content
+ SelectionManager selMan = viewer.getSelectionManager();
+ ISelection selection = selMan.getSelection();
+
+ try {
+ viewer.setContents(getModel());
+ } finally {
+ selMan.setSelection(selection);
+ }
+
+ mNeedsXmlReload = false;
}
}
@@ -1648,7 +1667,9 @@
/**
* Recomputes the layout with the help of layoutlib.
*/
+ @SuppressWarnings("deprecation")
void recomputeLayout() {
+ doXmlReload(false /* force */);
try {
// check that the resource exists. If the file is opened but the project is closed
// or deleted for some reason (changed from outside of eclipse), then this will
@@ -1763,20 +1784,47 @@
if (themeIndex != -1) {
String theme = mThemeCombo.getItem(themeIndex);
- // change the string if it's a custom theme to make sure we can
- // differentiate them
- if (themeIndex >= mPlatformThemeCount) {
- theme = "*" + theme; //$NON-NLS-1$
- }
-
// Compute the layout
UiElementPullParser parser = new UiElementPullParser(getModel());
Rectangle rect = getBounds();
- ILayoutResult result = bridge.bridge.computeLayout(parser,
- iProject /* projectKey */,
- rect.width, rect.height, theme,
- mConfiguredProjectRes, frameworkResources, mProjectCallback,
- mLogger);
+ ILayoutResult result = null;
+ if (bridge.apiLevel >= 3) {
+ // call the new api with proper theme differentiator and
+ // density/dpi support.
+ boolean isProjectTheme = themeIndex >= mPlatformThemeCount;
+
+ // FIXME pass the density/dpi from somewhere (resource config or skin).
+ result = bridge.bridge.computeLayout(parser,
+ iProject /* projectKey */,
+ rect.width, rect.height, 160, 160.f, 160.f,
+ theme, isProjectTheme,
+ mConfiguredProjectRes, frameworkResources, mProjectCallback,
+ mLogger);
+ } else if (bridge.apiLevel == 2) {
+ // api with boolean for separation of project/framework theme
+ boolean isProjectTheme = themeIndex >= mPlatformThemeCount;
+
+ result = bridge.bridge.computeLayout(parser,
+ iProject /* projectKey */,
+ rect.width, rect.height, theme, isProjectTheme,
+ mConfiguredProjectRes, frameworkResources, mProjectCallback,
+ mLogger);
+ } else {
+ // oldest api with no density/dpi, and project theme boolean mixed
+ // into the theme name.
+
+ // change the string if it's a custom theme to make sure we can
+ // differentiate them
+ if (themeIndex >= mPlatformThemeCount) {
+ theme = "*" + theme; //$NON-NLS-1$
+ }
+
+ result = bridge.bridge.computeLayout(parser,
+ iProject /* projectKey */,
+ rect.width, rect.height, theme,
+ mConfiguredProjectRes, frameworkResources, mProjectCallback,
+ mLogger);
+ }
// update the UiElementNode with the layout info.
if (result.getSuccess() == ILayoutResult.SUCCESS) {
@@ -1921,8 +1969,7 @@
* Responds to a page change that made the Graphical editor page the activated page.
*/
void activated() {
- mActive = true;
- if (mNeedsRecompute) {
+ if (mNeedsRecompute || mNeedsXmlReload) {
recomputeLayout();
}
}
@@ -1931,7 +1978,7 @@
* Responds to a page change that made the Graphical editor page the deactivated page
*/
void deactivated() {
- mActive = false;
+ // nothing to be done here for now.
}
/**
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java
index 880ee2b..dabe797 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java
@@ -269,8 +269,12 @@
protected void pageChange(int newPageIndex) {
super.pageChange(newPageIndex);
- if (mGraphicalEditor != null && newPageIndex == mGraphicalEditorIndex) {
- mGraphicalEditor.activated();
+ if (mGraphicalEditor != null) {
+ if (newPageIndex == mGraphicalEditorIndex) {
+ mGraphicalEditor.activated();
+ } else {
+ mGraphicalEditor.deactivated();
+ }
}
}
@@ -278,8 +282,12 @@
public void partActivated(IWorkbenchPart part) {
if (part == this) {
- if (mGraphicalEditor != null && getActivePage() == mGraphicalEditorIndex) {
- mGraphicalEditor.activated();
+ if (mGraphicalEditor != null) {
+ if (getActivePage() == mGraphicalEditorIndex) {
+ mGraphicalEditor.activated();
+ } else {
+ mGraphicalEditor.deactivated();
+ }
}
}
}
@@ -334,23 +342,23 @@
// ---- Local Methods ----
/**
- * Returns true if the Graphics editor page is visible.
- * This <b>must</b> be called from the UI thread.
+ * Returns true if the Graphics editor page is visible. This <b>must</b> be
+ * called from the UI thread.
*/
boolean isGraphicalEditorActive() {
IWorkbenchPartSite workbenchSite = getSite();
IWorkbenchPage workbenchPage = workbenchSite.getPage();
-
+
// check if the editor is visible in the workbench page
- if (workbenchPage.isPartVisible(this)) {
+ if (workbenchPage.isPartVisible(this) && workbenchPage.getActiveEditor() == this) {
// and then if the page of the editor is visible (not to be confused with
// the workbench page)
return mGraphicalEditorIndex == getActivePage();
}
-
- return false;
- }
+ return false;
+ }
+
@Override
protected void initUiRootNode(boolean force) {
// The root UI node is always created, even if there's no corresponding XML node.
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java
index 61b73a2..77c08b5 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java
@@ -195,6 +195,8 @@
overrides.put("*/permission", ListAttributeDescriptor.class); //$NON-NLS-1$
overrides.put("*/targetPackage", PackageAttributeDescriptor.class); //$NON-NLS-1$
+ overrides.put("uses-library/name", ListAttributeDescriptor.class); //$NON-NLS-1$
+
overrides.put("action,category,uses-permission/" + ANDROID_NAME_ATTR, //$NON-NLS-1$
ListAttributeDescriptor.class);
overrides.put("application/" + ANDROID_NAME_ATTR, ApplicationAttributeDescriptor.class); //$NON-NLS-1$
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java
index abaf438..629b37c 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java
@@ -16,12 +16,12 @@
package com.android.ide.eclipse.editors.manifest.descriptors;
-import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor;
import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode;
import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction;
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
+import com.android.sdklib.SdkConstants;
/**
* Describes an XML attribute representing a class name.
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java
index e3255d9..fc384e8 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.editors.ui.tree;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.editors.AndroidEditor;
import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.descriptors.ElementDescriptor;
@@ -26,6 +27,7 @@
import com.android.ide.eclipse.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
+import org.eclipse.core.resources.IProject;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
@@ -285,13 +287,21 @@
}
};
- final Runnable resourceRefreshListener = new Runnable() {
- public void run() {
+ /** Listener to update the root node if the target of the file is changed because of a
+ * SDK location change or a project target change */
+ final ITargetChangeListener targetListener = new ITargetChangeListener() {
+ public void onProjectTargetChange(IProject changedProject) {
+ if (changedProject == mEditor.getProject()) {
+ onTargetsLoaded();
+ }
+ }
+
+ public void onTargetsLoaded() {
// If a details part has been created, we need to "refresh" it too.
if (mDetailsPart != null) {
// The details part does not directly expose access to its internal
// page book. Instead it is possible to resize the page book to 0 and then
- // back to its original value, which as the side effect of removing all
+ // back to its original value, which has the side effect of removing all
// existing cached pages.
int limit = mDetailsPart.getPageLimit();
mDetailsPart.setPageLimit(0);
@@ -306,7 +316,7 @@
changeRootAndDescriptors(mUiRootNode, mDescriptorFilters, false /* refresh */);
// Listen on resource framework changes to refresh the tree
- AdtPlugin.getDefault().addResourceChangedListener(resourceRefreshListener);
+ AdtPlugin.getDefault().addTargetListener(targetListener);
// Remove listeners when the tree widget gets disposed.
tree.addDisposeListener(new DisposeListener() {
@@ -318,7 +328,7 @@
node.removeUpdateListener(mUiRefreshListener);
mUiRootNode.removeUpdateListener(mUiEnableListener);
- AdtPlugin.getDefault().removeResourceChangedListener(resourceRefreshListener);
+ AdtPlugin.getDefault().removeTargetListener(targetListener);
if (mClipboard != null) {
mClipboard.dispose();
mClipboard = null;
@@ -580,7 +590,11 @@
ui_node = ui_node.getUiParent()) {
segments.add(0, ui_node);
}
- mTreeViewer.setSelection(new TreeSelection(new TreePath(segments.toArray())));
+ if (segments.size() > 0) {
+ mTreeViewer.setSelection(new TreeSelection(new TreePath(segments.toArray())));
+ } else {
+ mTreeViewer.setSelection(null);
+ }
}
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java
index 4d17176..5781938 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java
@@ -23,6 +23,7 @@
import com.android.ide.eclipse.common.project.ProjectChooserHelper;
import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor;
import com.android.ide.eclipse.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider;
import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors;
import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration;
import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier;
@@ -81,6 +82,7 @@
private final String mXmlns;
private final String mDefaultAttrs;
private final String mDefaultRoot;
+ private final int mTargetApiLevel;
public TypeInfo(String uiName,
String tooltip,
@@ -88,7 +90,8 @@
Object rootSeed,
String defaultRoot,
String xmlns,
- String defaultAttrs) {
+ String defaultAttrs,
+ int targetApiLevel) {
mUiName = uiName;
mResFolderType = resFolderType;
mTooltip = tooltip;
@@ -96,6 +99,7 @@
mDefaultRoot = defaultRoot;
mXmlns = xmlns;
mDefaultAttrs = defaultAttrs;
+ mTargetApiLevel = targetApiLevel;
}
/** Returns the UI name for the resource type. Unique. Never null. */
@@ -176,6 +180,13 @@
String getDefaultAttrs() {
return mDefaultAttrs;
}
+
+ /**
+ * The minimum API level required by the current SDK target to support this feature.
+ */
+ public int getTargetApiLevel() {
+ return mTargetApiLevel;
+ }
}
/**
@@ -190,7 +201,8 @@
"LinearLayout", // default root
SdkConstants.NS_RESOURCES, // xmlns
"android:layout_width=\"wrap_content\"\n" + // default attributes
- "android:layout_height=\"wrap_content\""
+ "android:layout_height=\"wrap_content\"",
+ 1 // target API level
),
new TypeInfo("Values", // UI name
"An XML file with simple values: colors, strings, dimensions, etc.", // tooltip
@@ -198,7 +210,8 @@
ResourcesDescriptors.ROOT_ELEMENT, // root seed
null, // default root
null, // xmlns
- null // default attributes
+ null, // default attributes
+ 1 // target API level
),
new TypeInfo("Menu", // UI name
"An XML file that describes an menu.", // tooltip
@@ -206,7 +219,17 @@
MenuDescriptors.MENU_ROOT_ELEMENT, // root seed
null, // default root
SdkConstants.NS_RESOURCES, // xmlns
- null // default attributes
+ null, // default attributes
+ 1 // target API level
+ ),
+ new TypeInfo("Gadget Provider", // UI name
+ "An XML file that describes a gadget provider.", // tooltip
+ ResourceFolderType.XML, // folder type
+ AndroidTargetData.DESCRIPTOR_GADGET_PROVIDER, // root seed
+ null, // default root
+ SdkConstants.NS_RESOURCES, // xmlns
+ null, // default attributes
+ 3 // target API level
),
new TypeInfo("Preference", // UI name
"An XML file that describes preferences.", // tooltip
@@ -214,15 +237,17 @@
AndroidTargetData.DESCRIPTOR_PREFERENCES, // root seed
AndroidConstants.CLASS_PREFERENCE_SCREEN, // default root
SdkConstants.NS_RESOURCES, // xmlns
- null // default attributes
+ null, // default attributes
+ 1 // target API level
),
new TypeInfo("Searchable", // UI name
- "An XML file that describes a searchable [TODO].", // tooltip
+ "An XML file that describes a searchable.", // tooltip
ResourceFolderType.XML, // folder type
AndroidTargetData.DESCRIPTOR_SEARCHABLE, // root seed
null, // default root
SdkConstants.NS_RESOURCES, // xmlns
- null // default attributes
+ null, // default attributes
+ 1 // target API level
),
new TypeInfo("Animation", // UI name
"An XML file that describes an animation.", // tooltip
@@ -237,10 +262,14 @@
},
"set", //$NON-NLS-1$ // default root
null, // xmlns
- null // default attributes
+ null, // default attributes
+ 1 // target API level
),
};
+ /** Number of columns in the grid layout */
+ final static int NUM_COL = 4;
+
/** Absolute destination folder root, e.g. "/res/" */
private static String sResFolderAbs = AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP;
/** Relative destination folder root, e.g. "res/" */
@@ -290,7 +319,7 @@
initializeDialogUnits(parent);
- composite.setLayout(new GridLayout(3, false /*makeColumnsEqualWidth*/));
+ composite.setLayout(new GridLayout(NUM_COL, false /*makeColumnsEqualWidth*/));
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
createProjectGroup(composite);
@@ -303,8 +332,9 @@
setControl(composite);
// Update state the first time
- initializeRootValues();
initializeFromSelection(mInitialSelection);
+ initializeRootValues();
+ enableTypesBasedOnApi();
validatePage();
}
@@ -419,16 +449,34 @@
}
/**
+ * Pads the parent with empty cells to match the number of columns of the parent grid.
+ *
+ * @param parent A grid layout with NUM_COL columns
+ * @param col The current number of columns used.
+ * @return 0, the new number of columns used, for convenience.
+ */
+ private int padWithEmptyCells(Composite parent, int col) {
+ for (; col < NUM_COL; ++col) {
+ emptyCell(parent);
+ }
+ col = 0;
+ return col;
+ }
+
+ /**
* Creates the project & filename fields.
* <p/>
- * The parent must be a GridLayout with 3 colums.
+ * The parent must be a GridLayout with NUM_COL colums.
*/
private void createProjectGroup(Composite parent) {
+ int col = 0;
+
// project name
String tooltip = "The Android Project where the new resource file will be created.";
Label label = new Label(parent, SWT.NONE);
label.setText("Project");
label.setToolTipText(tooltip);
+ ++col;
mProjectTextField = new Text(parent, SWT.BORDER);
mProjectTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
@@ -438,6 +486,7 @@
onProjectFieldUpdated();
}
});
+ ++col;
mProjectBrowseButton = new Button(parent, SWT.NONE);
mProjectBrowseButton.setText("Browse...");
@@ -449,12 +498,16 @@
}
});
mProjectChooserHelper = new ProjectChooserHelper(parent.getShell());
+ ++col;
+ col = padWithEmptyCells(parent, col);
+
// file name
tooltip = "The name of the resource file to create.";
label = new Label(parent, SWT.NONE);
label.setText("File");
label.setToolTipText(tooltip);
+ ++col;
mFileNameTextField = new Text(parent, SWT.BORDER);
mFileNameTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
@@ -464,31 +517,32 @@
validatePage();
}
});
+ ++col;
- emptyCell(parent);
+ padWithEmptyCells(parent, col);
}
/**
* Creates the type field, {@link ConfigurationSelector} and the folder field.
* <p/>
- * The parent must be a GridLayout with 3 colums.
+ * The parent must be a GridLayout with NUM_COL colums.
*/
private void createTypeGroup(Composite parent) {
// separator
Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
- label.setLayoutData(newGridData(3, GridData.GRAB_HORIZONTAL));
+ label.setLayoutData(newGridData(NUM_COL, GridData.GRAB_HORIZONTAL));
// label before type radios
label = new Label(parent, SWT.NONE);
label.setText("What type of resource would you like to create?");
- label.setLayoutData(newGridData(3));
+ label.setLayoutData(newGridData(NUM_COL));
// display the types on three columns of radio buttons.
emptyCell(parent);
Composite grid = new Composite(parent, SWT.NONE);
- emptyCell(parent);
+ padWithEmptyCells(parent, 2);
- grid.setLayout(new GridLayout(3, true /*makeColumnsEqualWidth*/));
+ grid.setLayout(new GridLayout(NUM_COL, true /*makeColumnsEqualWidth*/));
SelectionListener radioListener = new SelectionAdapter() {
@Override
@@ -501,23 +555,27 @@
};
int n = sTypes.length;
- int num_lines = n/3;
- for (int line = 0; line < num_lines; line++) {
- for (int i = 0; i < 3; i++) {
- TypeInfo type = sTypes[line * 3 + i];
- Button radio = new Button(grid, SWT.RADIO);
- type.setWidget(radio);
- radio.setSelection(false);
- radio.setText(type.getUiName());
- radio.setToolTipText(type.getTooltip());
- radio.addSelectionListener(radioListener);
+ int num_lines = (n + NUM_COL/2) / NUM_COL;
+ for (int line = 0, k = 0; line < num_lines; line++) {
+ for (int i = 0; i < NUM_COL; i++, k++) {
+ if (k < n) {
+ TypeInfo type = sTypes[k];
+ Button radio = new Button(grid, SWT.RADIO);
+ type.setWidget(radio);
+ radio.setSelection(false);
+ radio.setText(type.getUiName());
+ radio.setToolTipText(type.getTooltip());
+ radio.addSelectionListener(radioListener);
+ } else {
+ emptyCell(grid);
+ }
}
}
// label before configuration selector
label = new Label(parent, SWT.NONE);
label.setText("What type of resource configuration would you like?");
- label.setLayoutData(newGridData(3));
+ label.setLayoutData(newGridData(NUM_COL));
// configuration selector
emptyCell(parent);
@@ -527,6 +585,7 @@
gd.heightHint = ConfigurationSelector.HEIGHT_HINT;
mConfigSelector.setLayoutData(gd);
mConfigSelector.setOnChangeListener(new onConfigSelectorUpdated());
+ emptyCell(parent);
// folder name
String tooltip = "The folder where the file will be generated, relative to the project.";
@@ -542,25 +601,23 @@
onWsFolderPathUpdated();
}
});
-
- emptyCell(parent);
}
/**
* Creates the root element combo.
* <p/>
- * The parent must be a GridLayout with 3 colums.
+ * The parent must be a GridLayout with NUM_COL colums.
*/
private void createRootGroup(Composite parent) {
// separator
Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
- label.setLayoutData(newGridData(3, GridData.GRAB_HORIZONTAL));
+ label.setLayoutData(newGridData(NUM_COL, GridData.GRAB_HORIZONTAL));
// label before the root combo
String tooltip = "The root element to create in the XML file.";
label = new Label(parent, SWT.NONE);
label.setText("Select the root element for the XML file:");
- label.setLayoutData(newGridData(3));
+ label.setLayoutData(newGridData(NUM_COL));
label.setToolTipText(tooltip);
// root combo
@@ -572,7 +629,7 @@
mRootElementCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mRootElementCombo.setToolTipText(tooltip);
- emptyCell(parent);
+ padWithEmptyCells(parent, 2);
}
/**
@@ -690,11 +747,13 @@
// get the AndroidTargetData from the project
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
- ElementDescriptor descriptor = data.getDescriptorProvider(
- (Integer)rootSeed).getDescriptor();
- HashSet<ElementDescriptor> visited = new HashSet<ElementDescriptor>();
- initRootElementDescriptor(roots, descriptor, visited);
+ IDescriptorProvider provider = data.getDescriptorProvider((Integer)rootSeed);
+ ElementDescriptor descriptor = provider.getDescriptor();
+ if (descriptor != null) {
+ HashSet<ElementDescriptor> visited = new HashSet<ElementDescriptor>();
+ initRootElementDescriptor(roots, descriptor, visited);
+ }
// Sort alphabetically.
Collections.sort(roots);
@@ -743,15 +802,7 @@
}
if (found != mProject) {
- mProject = found;
-
- // update the Type with the new descriptors.
- initializeRootValues();
-
- // update the combo
- updateRootCombo(getSelectedType());
-
- validatePage();
+ changeProject(found);
}
}
@@ -761,17 +812,27 @@
private void onProjectBrowse() {
IJavaProject p = mProjectChooserHelper.chooseJavaProject(mProjectTextField.getText());
if (p != null) {
- mProject = p.getProject();
+ changeProject(p.getProject());
mProjectTextField.setText(mProject.getName());
-
- // update the Type with the new descriptors.
- initializeRootValues();
-
- // update the combo
- updateRootCombo(getSelectedType());
-
- validatePage();
}
+ }
+
+ /**
+ * Changes mProject to the given new project and update the UI accordingly.
+ */
+ private void changeProject(IProject newProject) {
+ mProject = newProject;
+
+ // enable types based on new API level
+ enableTypesBasedOnApi();
+
+ // update the Type with the new descriptors.
+ initializeRootValues();
+
+ // update the combo
+ updateRootCombo(getSelectedType());
+
+ validatePage();
}
/**
@@ -986,6 +1047,26 @@
}
/**
+ * Helper method to enable the type radio buttons depending on the current API level.
+ * <p/>
+ * A type radio button is enabled either if:
+ * - if mProject is null, API level 1 is considered valid
+ * - if mProject is !null, the project->target->API must be >= to the type's API level.
+ */
+ private void enableTypesBasedOnApi() {
+
+ IAndroidTarget target = mProject != null ? Sdk.getCurrent().getTarget(mProject) : null;
+ int currentApiLevel = 1;
+ if (target != null) {
+ currentApiLevel = target.getApiVersionNumber();
+ }
+
+ for (TypeInfo type : sTypes) {
+ type.getWidget().setEnabled(type.getTargetApiLevel() <= currentApiLevel);
+ }
+ }
+
+ /**
* Validates the fields, displays errors and warnings.
* Enables the finish button if there are no errors.
*/
@@ -1017,6 +1098,22 @@
}
}
+ // -- validate type API level
+ if (error == null) {
+ IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
+ int currentApiLevel = 1;
+ if (target != null) {
+ currentApiLevel = target.getApiVersionNumber();
+ }
+
+ TypeInfo type = getSelectedType();
+
+ if (type.getTargetApiLevel() > currentApiLevel) {
+ error = "The API level of the selected type (e.g. gadget, etc.) is not " +
+ "compatible with the API level of the project.";
+ }
+ }
+
// -- validate folder configuration
if (error == null) {
ConfigurationState state = mConfigSelector.getState();
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java
index fa1370f..7929b5a 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java
@@ -53,6 +53,9 @@
/** The root document descriptor for preferences. */
private DocumentDescriptor mPrefDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$
+ /** The root document descriptor for gadget provider. */
+ private DocumentDescriptor mGadgetDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$
+
/** @return the root descriptor for both searchable and preferences. */
public DocumentDescriptor getDescriptor() {
return mDescriptor;
@@ -72,6 +75,11 @@
return mPrefDescriptor;
}
+ /** @return the root descriptor for gadget providers. */
+ public DocumentDescriptor getGadgetDescriptor() {
+ return mGadgetDescriptor;
+ }
+
public IDescriptorProvider getSearchableProvider() {
return new IDescriptorProvider() {
public ElementDescriptor getDescriptor() {
@@ -96,6 +104,18 @@
};
}
+ public IDescriptorProvider getGadgetProvider() {
+ return new IDescriptorProvider() {
+ public ElementDescriptor getDescriptor() {
+ return mGadgetDescriptor;
+ }
+
+ public ElementDescriptor[] getRootElementDescriptors() {
+ return mGadgetDescriptor.getChildren();
+ }
+ };
+ }
+
/**
* Updates the document descriptor.
* <p/>
@@ -103,11 +123,13 @@
* all at once.
*
* @param searchableStyleMap The map style=>attributes for <searchable> from the attrs.xml file
+ * @param gadgetStyleMap The map style=>attributes for <gadget-provider> from the attrs.xml file
* @param prefs The list of non-group preference descriptions
* @param prefGroups The list of preference group descriptions
*/
public synchronized void updateDescriptors(
Map<String, DeclareStyleableInfo> searchableStyleMap,
+ Map<String, DeclareStyleableInfo> gadgetStyleMap,
ViewClassInfo[] prefs, ViewClassInfo[] prefGroups) {
XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(
@@ -115,12 +137,17 @@
SdkConstants.NS_RESOURCES);
ElementDescriptor searchable = createSearchable(searchableStyleMap, xmlns);
+ ElementDescriptor gadget = createGadgetProviderInfo(gadgetStyleMap, xmlns);
ElementDescriptor preferences = createPreference(prefs, prefGroups, xmlns);
ArrayList<ElementDescriptor> list = new ArrayList<ElementDescriptor>();
if (searchable != null) {
list.add(searchable);
mSearchDescriptor.setChildren(new ElementDescriptor[]{ searchable });
}
+ if (gadget != null) {
+ list.add(gadget);
+ mGadgetDescriptor.setChildren(new ElementDescriptor[]{ gadget });
+ }
if (preferences != null) {
list.add(preferences);
mPrefDescriptor.setChildren(new ElementDescriptor[]{ preferences });
@@ -161,6 +188,28 @@
false /* mandatory */ );
return searchable;
}
+
+ /**
+ * Returns the new ElementDescriptor for <gadget-provider>
+ */
+ private ElementDescriptor createGadgetProviderInfo(
+ Map<String, DeclareStyleableInfo> gadgetStyleMap,
+ XmlnsAttributeDescriptor xmlns) {
+
+ if (gadgetStyleMap == null) {
+ return null;
+ }
+
+ ElementDescriptor gadget = createElement(gadgetStyleMap,
+ "GadgetProviderInfo", //$NON-NLS-1$ styleName
+ "gadget-provider", //$NON-NLS-1$ xmlName
+ "Gadget Provider", // uiName
+ null, // sdk url
+ xmlns, // extraAttribute
+ null, // childrenElements
+ false /* mandatory */ );
+ return gadget;
+ }
/**
* Returns a new ElementDescriptor constructed from the information given here
diff --git a/tools/eclipse/scripts/collect_sources_for_sdk.sh b/tools/eclipse/scripts/collect_sources_for_sdk.sh
index 4637595..4824da7 100644
--- a/tools/eclipse/scripts/collect_sources_for_sdk.sh
+++ b/tools/eclipse/scripts/collect_sources_for_sdk.sh
@@ -22,6 +22,13 @@
shift
fi
+DIR="frameworks"
+if [ "-s" == "$1" ]; then
+ shift
+ DIR="$1"
+ shift
+fi
+
SRC="$1"
DST="$2"
@@ -36,7 +43,7 @@
N=0
E=0
-for i in `find -L "${SRC}/frameworks" -name "*.java"`; do
+for i in `find -L "${SRC}/${DIR}" -name "*.java"`; do
if [ -f "$i" ]; then
# look for ^package (android.view.blah);$
PACKAGE=`sed -n '/^package [^ ;]\+; */{s/[^ ]* *\([^ ;]*\).*/\1/p;q}' "$i"`
diff --git a/tools/hierarchyviewer/etc/hierarchyviewer.bat b/tools/hierarchyviewer/etc/hierarchyviewer.bat
index 67e4f80..2024a79 100755
--- a/tools/hierarchyviewer/etc/hierarchyviewer.bat
+++ b/tools/hierarchyviewer/etc/hierarchyviewer.bat
@@ -20,9 +20,9 @@
rem and set up progdir to be the fully-qualified pathname of its directory.
set prog=%~f0
-rem Change current directory to where ddms is, to avoid issues with directories
-rem containing whitespaces.
-cd %~dp0
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
set jarfile=hierarchyviewer.jar
set frameworkdir=
diff --git a/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewHierarchyLoader.java b/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewHierarchyLoader.java
index 6efb52d..51e1396 100644
--- a/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewHierarchyLoader.java
+++ b/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewHierarchyLoader.java
@@ -32,6 +32,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.Stack;
+import java.util.regex.Pattern;
public class ViewHierarchyLoader {
@SuppressWarnings("empty-statement")
@@ -109,7 +110,9 @@
parent.children.add(lastNode);
}
}
-
+
+ updateIndices(scene.getRoot());
+
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
} finally {
@@ -127,10 +130,18 @@
}
System.out.println("==> DONE");
-
+
return scene;
}
-
+
+ private static void updateIndices(ViewNode root) {
+ root.computeIndex();
+
+ for (ViewNode node : root.children) {
+ updateIndices(node);
+ }
+ }
+
private static int countFrontWhitespace(String line) {
int count = 0;
while (line.charAt(count) == ' ') {
diff --git a/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewHierarchyScene.java b/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewHierarchyScene.java
index d99a80c..08dc395 100644
--- a/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewHierarchyScene.java
+++ b/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewHierarchyScene.java
@@ -60,22 +60,25 @@
@Override
protected Widget attachNodeWidget(ViewNode node) {
- Widget widget = createBox(node.name, node.id);
+ Widget widget = createBox(node, node.name, node.id);
widget.getActions().addAction(createSelectAction());
widget.getActions().addAction(moveAction);
widgetLayer.addChild(widget);
return widget;
}
- private Widget createBox(String node, String id) {
- Widget box = new GradientWidget(this);
+ private Widget createBox(ViewNode node, String nodeName, String id) {
+ final String shortName = getShortName(nodeName);
+ node.setShortName(shortName);
+
+ GradientWidget box = new GradientWidget(this, node);
box.setLayout(LayoutFactory.createVerticalFlowLayout());
box.setBorder(BorderFactory.createLineBorder(2, Color.BLACK));
box.setOpaque(true);
LabelWidget label = new LabelWidget(this);
label.setFont(getDefaultFont().deriveFont(Font.PLAIN, 12.0f));
- label.setLabel(getShortName(node));
+ label.setLabel(shortName);
label.setBorder(BorderFactory.createEmptyBorder(6, 6, 0, 6));
label.setAlignment(LabelWidget.Alignment.CENTER);
@@ -83,9 +86,11 @@
label = new LabelWidget(this);
label.setFont(getDefaultFont().deriveFont(Font.PLAIN, 10.0f));
- label.setLabel(getAddress(node));
+ label.setLabel(getAddress(nodeName));
label.setBorder(BorderFactory.createEmptyBorder(3, 6, 0, 6));
label.setAlignment(LabelWidget.Alignment.CENTER);
+
+ box.addressWidget = label;
box.addChild(label);
@@ -136,7 +141,7 @@
connection.setTargetAnchor(AnchorFactory.createRectangularAnchor(target));
}
- private static class GradientWidget extends Widget {
+ private static class GradientWidget extends Widget implements ViewNode.StateListener {
public static final GradientPaint BLUE_EXPERIENCE = new GradientPaint(
new Point2D.Double(0, 0),
new Color(168, 204, 241),
@@ -177,15 +182,28 @@
new Color(129, 138, 155),
new Point2D.Double(0, 1),
new Color(58, 66, 82));
+ public static final GradientPaint NIGHT_GRAY_VERY_LIGHT = new GradientPaint(
+ new Point2D.Double(0, 0),
+ new Color(129, 138, 155, 60),
+ new Point2D.Double(0, 1),
+ new Color(58, 66, 82, 60));
private static Color UNSELECTED = Color.BLACK;
private static Color SELECTED = Color.WHITE;
- private boolean isSelected = false;
- private GradientPaint gradient = MAC_OSX_SELECTED;
+ private final ViewNode node;
- public GradientWidget(ViewHierarchyScene scene) {
+ private LabelWidget addressWidget;
+
+ private boolean isSelected = false;
+ private final GradientPaint selectedGradient = MAC_OSX_SELECTED;
+ private final GradientPaint filteredGradient = RED_XP;
+ private final GradientPaint focusGradient = NIGHT_GRAY_VERY_LIGHT;
+
+ public GradientWidget(ViewHierarchyScene scene, ViewNode node) {
super(scene);
+ this.node = node;
+ node.setStateListener(this);
}
@Override
@@ -193,8 +211,12 @@
super.notifyStateChanged(previous, state);
isSelected = state.isSelected() || state.isFocused() || state.isWidgetFocused();
+ pickChildrenColor();
+ }
+
+ private void pickChildrenColor() {
for (Widget child : getChildren()) {
- child.setForeground(isSelected ? SELECTED : UNSELECTED);
+ child.setForeground(isSelected || node.filtered ? SELECTED : UNSELECTED);
}
repaint();
@@ -206,14 +228,35 @@
Graphics2D g2 = getGraphics();
Rectangle bounds = getBounds();
-
+
if (!isSelected) {
- g2.setColor(Color.WHITE);
+ if (!node.filtered) {
+ if (!node.hasFocus) {
+ g2.setColor(Color.WHITE);
+ } else {
+ g2.setPaint(new GradientPaint(bounds.x, bounds.y,
+ focusGradient.getColor1(), bounds.x, bounds.x + bounds.height,
+ focusGradient.getColor2()));
+ }
+ } else {
+ g2.setPaint(new GradientPaint(bounds.x, bounds.y, filteredGradient.getColor1(),
+ bounds.x, bounds.x + bounds.height, filteredGradient.getColor2()));
+ }
} else {
- g2.setPaint(new GradientPaint(bounds.x, bounds.y, gradient.getColor1(),
- bounds.x, bounds.x + bounds.height, gradient.getColor2()));
+ g2.setPaint(new GradientPaint(bounds.x, bounds.y, selectedGradient.getColor1(),
+ bounds.x, bounds.x + bounds.height, selectedGradient.getColor2()));
}
g2.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
+
+ public void nodeStateChanged(ViewNode node) {
+ pickChildrenColor();
+ }
+
+ public void nodeIndexChanged(ViewNode node) {
+ if (addressWidget != null) {
+ addressWidget.setLabel("#" + node.index + addressWidget.getLabel());
+ }
+ }
}
}
diff --git a/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewManager.java b/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewManager.java
index 6b212c0..2b7efd6 100644
--- a/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewManager.java
+++ b/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewManager.java
@@ -17,7 +17,6 @@
package com.android.hierarchyviewer.scene;
import com.android.ddmlib.Device;
-import com.android.hierarchyviewer.device.Configuration;
import com.android.hierarchyviewer.device.Window;
import com.android.hierarchyviewer.device.DeviceBridge;
diff --git a/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewNode.java b/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewNode.java
index 8284df1..64c0703 100644
--- a/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewNode.java
+++ b/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewNode.java
@@ -21,6 +21,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.regex.Pattern;
public class ViewNode {
public String id;
@@ -52,8 +53,15 @@
public boolean willNotDraw;
public boolean hasMargins;
+ boolean hasFocus;
+ int index;
+
public boolean decoded;
-
+ public boolean filtered;
+
+ private String shortName;
+ private StateListener listener;
+
void decode() {
id = namedProperties.get("mID").value;
@@ -73,6 +81,7 @@
marginBottom = getInt("layout_bottomMargin", Integer.MIN_VALUE);
baseline = getInt("getBaseline()", 0);
willNotDraw = getBoolean("willNotDraw()", false);
+ hasFocus = getBoolean("hasFocus()", false);
hasMargins = marginLeft != Integer.MIN_VALUE &&
marginRight != Integer.MIN_VALUE &&
@@ -101,11 +110,33 @@
return Integer.parseInt(p.value);
} catch (NumberFormatException e) {
return defaultValue;
- }
+ }
}
return defaultValue;
}
+ public void filter(Pattern pattern) {
+ if (pattern == null || pattern.pattern().length() == 0) {
+ filtered = false;
+ } else {
+ filtered = pattern.matcher(shortName).find() || pattern.matcher(id).find();
+ }
+ listener.nodeStateChanged(this);
+ }
+
+ void computeIndex() {
+ index = parent == null ? 0 : parent.children.indexOf(this);
+ listener.nodeIndexChanged(this);
+ }
+
+ void setShortName(String shortName) {
+ this.shortName = shortName;
+ }
+
+ void setStateListener(StateListener listener) {
+ this.listener = listener;
+ }
+
@SuppressWarnings({"StringEquality"})
@Override
public boolean equals(Object obj) {
@@ -164,4 +195,9 @@
return hash;
}
}
+
+ interface StateListener {
+ void nodeStateChanged(ViewNode node);
+ void nodeIndexChanged(ViewNode node);
+ }
}
diff --git a/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java b/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java
index 0add4e9..20093ae 100644
--- a/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java
+++ b/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java
@@ -76,6 +76,9 @@
import javax.swing.SwingUtilities;
import javax.swing.JTree;
import javax.swing.Box;
+import javax.swing.JTextField;
+import javax.swing.text.Document;
+import javax.swing.text.BadLocationException;
import javax.swing.tree.TreePath;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.event.ChangeEvent;
@@ -84,6 +87,8 @@
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.DocumentEvent;
import javax.swing.table.DefaultTableModel;
import java.awt.image.BufferedImage;
import java.awt.BorderLayout;
@@ -105,6 +110,8 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
import java.util.concurrent.ExecutionException;
public class Workspace extends JFrame {
@@ -156,6 +163,8 @@
private JTable windows;
private JLabel minZoomLabel;
private JLabel maxZoomLabel;
+ private JTextField filterText;
+ private JLabel filterLabel;
public Workspace() {
super("Hierarchy Viewer");
@@ -313,10 +322,33 @@
graphViewButton.setSelected(true);
+ filterText = new JTextField(20);
+ filterText.putClientProperty("JComponent.sizeVariant", "small");
+ filterText.getDocument().addDocumentListener(new DocumentListener() {
+ public void insertUpdate(DocumentEvent e) {
+ updateFilter(e);
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ updateFilter(e);
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ updateFilter(e);
+ }
+ });
+
+ filterLabel = new JLabel("Filter by class or id:");
+ filterLabel.putClientProperty("JComponent.sizeVariant", "small");
+ filterLabel.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 6));
+
+ leftSide.add(filterLabel);
+ leftSide.add(filterText);
+
minZoomLabel = new JLabel();
minZoomLabel.setText("20%");
minZoomLabel.putClientProperty("JComponent.sizeVariant", "small");
- minZoomLabel.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 0));
+ minZoomLabel.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 0));
leftSide.add(minZoomLabel);
zoomSlider = new JSlider();
@@ -357,12 +389,18 @@
statusPanel.add(rightSide, BorderLayout.LINE_END);
+ hideStatusBarComponents();
+
+ return statusPanel;
+ }
+
+ private void hideStatusBarComponents() {
viewCountLabel.setVisible(false);
zoomSlider.setVisible(false);
minZoomLabel.setVisible(false);
- maxZoomLabel.setVisible(false);
-
- return statusPanel;
+ maxZoomLabel.setVisible(false);
+ filterLabel.setVisible(false);
+ filterText.setVisible(false);
}
private JToolBar buildToolBar() {
@@ -513,10 +551,7 @@
}
private void toggleGraphView() {
- viewCountLabel.setVisible(true);
- zoomSlider.setVisible(true);
- minZoomLabel.setVisible(true);
- maxZoomLabel.setVisible(true);
+ showStatusBarComponents();
screenViewer.stop();
mainPanel.remove(pixelPerfectPanel);
@@ -526,6 +561,15 @@
repaint();
}
+ private void showStatusBarComponents() {
+ viewCountLabel.setVisible(true);
+ zoomSlider.setVisible(true);
+ minZoomLabel.setVisible(true);
+ maxZoomLabel.setVisible(true);
+ filterLabel.setVisible(true);
+ filterText.setVisible(true);
+ }
+
private void togglePixelPerfectView() {
if (pixelPerfectPanel == null) {
pixelPerfectPanel = buildPixelPerfectPanel();
@@ -534,10 +578,7 @@
screenViewer.start();
}
- viewCountLabel.setVisible(false);
- zoomSlider.setVisible(false);
- minZoomLabel.setVisible(false);
- maxZoomLabel.setVisible(false);
+ hideStatusBarComponents();
mainPanel.remove(mainSplitter);
mainPanel.add(pixelPerfectPanel, BorderLayout.CENTER);
@@ -602,10 +643,7 @@
graphViewButton.setEnabled(true);
pixelPerfectViewButton.setEnabled(true);
- viewCountLabel.setVisible(true);
- zoomSlider.setVisible(true);
- minZoomLabel.setVisible(true);
- maxZoomLabel.setVisible(true);
+ showStatusBarComponents();
}
sceneView = scene.createView();
@@ -776,10 +814,7 @@
pixelPerfectPanel = mainSplitter = null;
graphViewButton.setSelected(true);
- viewCountLabel.setVisible(false);
- zoomSlider.setVisible(false);
- minZoomLabel.setVisible(false);
- maxZoomLabel.setVisible(false);
+ hideStatusBarComponents();
saveMenuItem.setEnabled(false);
showDevicesMenuItem.setEnabled(false);
@@ -865,6 +900,34 @@
});
}
+ private void updateFilter(DocumentEvent e) {
+ final Document document = e.getDocument();
+ try {
+ updateFilteredNodes(document.getText(0, document.getLength()));
+ } catch (BadLocationException e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ private void updateFilteredNodes(String filterText) {
+ final ViewNode root = scene.getRoot();
+ try {
+ final Pattern pattern = Pattern.compile(filterText, Pattern.CASE_INSENSITIVE);
+ filterNodes(pattern, root);
+ } catch (PatternSyntaxException e) {
+ filterNodes(null, root);
+ }
+ repaint();
+ }
+
+ private void filterNodes(Pattern pattern, ViewNode root) {
+ root.filter(pattern);
+
+ for (ViewNode node : root.children) {
+ filterNodes(pattern, node);
+ }
+ }
+
public void beginTask() {
progress.setVisible(true);
}
diff --git a/tools/runtest b/tools/runtest
index 6280910..349b5a7 100755
--- a/tools/runtest
+++ b/tools/runtest
@@ -113,7 +113,7 @@
# system-wide tests
"framework frameworks/base/tests/FrameworkTest # com.android.frameworktest.AllTests com.android.frameworktest.tests #"
- "android frameworks/base/tests/AndroidTests com.android.unit_tests AndroidTests # #"
+ "android frameworks/base/tests/AndroidTests # AndroidTests com.android.unit_tests #"
"smoke frameworks/base/tests/SmokeTest com.android.smoketest # com.android.smoketest.tests #"
"core frameworks/base/tests/CoreTests # android.core.CoreTests android.core #"
"libcore frameworks/base/tests/CoreTests # android.core.JavaTests android.core #"
diff --git a/tools/sdkmanager/app/etc/android.bat b/tools/sdkmanager/app/etc/android.bat
index 1af1e47..de950ed 100755
--- a/tools/sdkmanager/app/etc/android.bat
+++ b/tools/sdkmanager/app/etc/android.bat
@@ -23,9 +23,9 @@
rem Grab current directory before we change it
set workdir=%cd%
-rem Change current directory to where ddms is, to avoid issues with directories
-rem containing whitespaces.
-cd %~dp0
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
set jarfile=sdkmanager.jar
set frameworkdir=
diff --git a/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java b/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java
index 1a15fce..154788e 100644
--- a/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java
+++ b/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java
@@ -383,24 +383,28 @@
*/
private void displayAvdList() {
try {
- AvdManager avdManager = new AvdManager(mSdkManager, null /* sdklog */);
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
mSdkLog.printf("Available Android Virtual Devices:\n");
- int index = 1;
- for (AvdInfo info : avdManager.getAvds()) {
- mSdkLog.printf("[%d] %s\n", index, info.getName());
- mSdkLog.printf(" Path: %s\n", info.getPath());
+ AvdInfo[] avds = avdManager.getAvds();
+ for (int index = 0 ; index < avds.length ; index++) {
+ AvdInfo info = avds[index];
+ if (index > 0) {
+ mSdkLog.printf("---------\n");
+ }
+ mSdkLog.printf(" Name: %s\n", info.getName());
+ mSdkLog.printf(" Path: %s\n", info.getPath());
// get the target of the AVD
IAndroidTarget target = info.getTarget();
if (target.isPlatform()) {
- mSdkLog.printf(" Target: %s (API level %d)\n", target.getName(),
+ mSdkLog.printf(" Target: %s (API level %d)\n", target.getName(),
target.getApiVersionNumber());
} else {
- mSdkLog.printf(" Target: %s (%s)\n", target.getName(), target
+ mSdkLog.printf(" Target: %s (%s)\n", target.getName(), target
.getVendor());
- mSdkLog.printf(" Based on Android %s (API level %d)\n", target
+ mSdkLog.printf(" Based on Android %s (API level %d)\n", target
.getApiVersionName(), target.getApiVersionNumber());
}
@@ -408,17 +412,15 @@
Map<String, String> properties = info.getProperties();
String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME);
if (skin != null) {
- mSdkLog.printf(" Skin: %s\n", skin);
+ mSdkLog.printf(" Skin: %s\n", skin);
}
String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE);
if (sdcard == null) {
sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH);
}
if (sdcard != null) {
- mSdkLog.printf(" Sdcard: %s\n", sdcard);
+ mSdkLog.printf(" Sdcard: %s\n", sdcard);
}
-
- index++;
}
} catch (AndroidLocationException e) {
errorAndExit(e.getMessage());
@@ -499,7 +501,7 @@
// Is it NNNxMMM?
if (!valid) {
- valid = skin.matches("[0-9]{2,}x[0-9]{2,}");
+ valid = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin).matches();
}
if (!valid) {
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
index 87f9b56..00594d1 100644
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
@@ -103,6 +103,8 @@
public final static String FD_ASSETS = "assets"; //$NON-NLS-1$
/** Default source folder name, i.e. "src" */
public final static String FD_SOURCES = "src"; //$NON-NLS-1$
+ /** Default generated source folder name, i.e. "gen" */
+ public final static String FD_GEN_SOURCES = "gen"; //$NON-NLS-1$
/** Default native library folder name inside the project, i.e. "libs"
* While the folder inside the .apk is "lib", we call that one libs because
* that's what we use in ant for both .jar and .so and we need to make the 2 development ways
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
index b44cf01..2c0f164 100644
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
@@ -55,6 +55,12 @@
public final static String AVD_INI_IMAGES_1 = "image.sysdir.1";
public final static String AVD_INI_IMAGES_2 = "image.sysdir.2";
+ /**
+ * Pattern to match pixel-sized skin "names", e.g. "320x480".
+ */
+ public final static Pattern NUMERIC_SKIN_SIZE = Pattern.compile("[0-9]{2,}x[0-9]{2,}");
+
+
private final static String USERDATA_IMG = "userdata.img";
private final static String CONFIG_INI = "config.ini";
private final static String SDCARD_IMG = "sdcard.img";
@@ -255,16 +261,21 @@
skinName = target.getDefaultSkin();
}
- // get the path of the skin (relative to the SDK)
- // assume skin name is valid
- String skinPath = getSkinRelativePath(skinName, target, log);
- if (skinPath == null) {
- needCleanup = true;
- return null;
- }
+ if (NUMERIC_SKIN_SIZE.matcher(skinName).matches()) {
+ // Skin name is an actual screen resolution, no skin.path
+ values.put(AVD_INI_SKIN_NAME, skinName);
+ } else {
+ // get the path of the skin (relative to the SDK)
+ // assume skin name is valid
+ String skinPath = getSkinRelativePath(skinName, target, log);
+ if (skinPath == null) {
+ needCleanup = true;
+ return null;
+ }
- values.put(AVD_INI_SKIN_PATH, skinPath);
- values.put(AVD_INI_SKIN_NAME, skinName);
+ values.put(AVD_INI_SKIN_PATH, skinPath);
+ values.put(AVD_INI_SKIN_NAME, skinName);
+ }
if (sdcard != null) {
File sdcardFile = new File(sdcard);
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ApkConfigurationHelper.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ApkConfigurationHelper.java
index ab43f46..b89d3bd 100644
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ApkConfigurationHelper.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ApkConfigurationHelper.java
@@ -25,24 +25,30 @@
* Helper class to read and write Apk Configuration into a {@link ProjectProperties} file.
*/
public class ApkConfigurationHelper {
+ /** Prefix for property names for config definition. This prevents having config named
+ * after other valid properties such as "target". */
+ final static String CONFIG_PREFIX = "apk-config-";
/**
* Reads the Apk Configurations from a {@link ProjectProperties} file and returns them as a map.
* <p/>If there are no defined configurations, the returned map will be empty.
+ * @return a map of apk configurations. The map contains (name, filter) where name is
+ * the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
+ * resource configuration to include in the apk (see aapt -c)
*/
public static Map<String, String> getConfigs(ProjectProperties properties) {
HashMap<String, String> configMap = new HashMap<String, String>();
// get the list of configs.
- String configList = properties.getProperty(ProjectProperties.PROPERTY_CONFIGS);
+ String configList = properties.getProperty(ProjectProperties.PROPERTY_APK_CONFIGS);
if (configList != null) {
// this is a comma separated list
String[] configs = configList.split(","); //$NON-NLS-1$
// read the value of each config and store it in a map
-
for (String config : configs) {
- String configValue = properties.getProperty(config);
+ config = config.trim();
+ String configValue = properties.getProperty(CONFIG_PREFIX + config);
if (configValue != null) {
configMap.put(config, configValue);
}
@@ -54,6 +60,10 @@
/**
* Writes the Apk Configurations from a given map into a {@link ProjectProperties}.
+ * @param properties the {@link ProjectProperties} in which to store the apk configurations.
+ * @param configMap a map of apk configurations. The map contains (name, filter) where name is
+ * the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
+ * resource configuration to include in the apk (see aapt -c)
* @return true if the {@link ProjectProperties} contained Apk Configuration that were not
* present in the map.
*/
@@ -62,17 +72,20 @@
// in case a config was removed.
// get the list of configs.
- String configList = properties.getProperty(ProjectProperties.PROPERTY_CONFIGS);
-
- // this is a comma separated list
- String[] configs = configList.split(","); //$NON-NLS-1$
-
+ String configList = properties.getProperty(ProjectProperties.PROPERTY_APK_CONFIGS);
+
boolean hasRemovedConfig = false;
-
- for (String config : configs) {
- if (configMap.containsKey(config) == false) {
- hasRemovedConfig = true;
- properties.removeProperty(config);
+
+ if (configList != null) {
+ // this is a comma separated list
+ String[] configs = configList.split(","); //$NON-NLS-1$
+
+ for (String config : configs) {
+ config = config.trim();
+ if (configMap.containsKey(config) == false) {
+ hasRemovedConfig = true;
+ properties.removeProperty(CONFIG_PREFIX + config);
+ }
}
}
@@ -84,9 +97,9 @@
sb.append(",");
}
sb.append(entry.getKey());
- properties.setProperty(entry.getKey(), entry.getValue());
+ properties.setProperty(CONFIG_PREFIX + entry.getKey(), entry.getValue());
}
- properties.setProperty(ProjectProperties.PROPERTY_CONFIGS, sb.toString());
+ properties.setProperty(ProjectProperties.PROPERTY_APK_CONFIGS, sb.toString());
return hasRemovedConfig;
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java
index 18e2ac9..7489b65 100644
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectCreator.java
@@ -209,7 +209,7 @@
}
// create the source folder and the java package folders.
- final String srcFolderPath = SdkConstants.FD_SOURCES + File.separator + packagePath;
+ String srcFolderPath = SdkConstants.FD_SOURCES + File.separator + packagePath;
File sourceFolder = createDirs(projectFolder, srcFolderPath);
String javaTemplate = "java_file.template";
String activityFileName = activityName + ".java";
@@ -220,6 +220,10 @@
installTemplate(javaTemplate, new File(sourceFolder, activityFileName),
keywords, target);
+ // create the generate source folder
+ srcFolderPath = SdkConstants.FD_GEN_SOURCES + File.separator + packagePath;
+ sourceFolder = createDirs(projectFolder, srcFolderPath);
+
// create other useful folders
File resourceFodler = createDirs(projectFolder, SdkConstants.FD_RESOURCES);
createDirs(projectFolder, SdkConstants.FD_OUTPUT);
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java
index 1f6a047..69a16be 100644
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/project/ProjectProperties.java
@@ -33,7 +33,7 @@
public final class ProjectProperties {
/** The property name for the project target */
public final static String PROPERTY_TARGET = "target";
- public final static String PROPERTY_CONFIGS = "configs";
+ public final static String PROPERTY_APK_CONFIGS = "apk-configurations";
public final static String PROPERTY_SDK = "sdk-location";
public static enum PropertyType {
@@ -98,7 +98,19 @@
// 1-------10--------20--------30--------40--------50--------60--------70--------80
COMMENT_MAP.put(PROPERTY_TARGET,
"# Project target.\n");
- COMMENT_MAP.put(PROPERTY_SDK, "# location of the SDK. This is only used by Ant\n" +
+ COMMENT_MAP.put(PROPERTY_APK_CONFIGS,
+ "# apk configurations. This property allows creation of APK files with limited\n" +
+ "# resources. For example, if your application contains many locales and\n" +
+ "# you wish to release multiple smaller apks instead of a large one, you can\n" +
+ "# define configuration to create apks with limited language sets.\n" +
+ "# Format is a comma separated list of configuration names. For each\n" +
+ "# configuration, a property will declare the resource configurations to\n" +
+ "# include. Example:\n" +
+ "# " + PROPERTY_APK_CONFIGS +"=european,northamerica\n" +
+ "# " + ApkConfigurationHelper.CONFIG_PREFIX + "european=en,fr,it,de,es\n" +
+ "# " + ApkConfigurationHelper.CONFIG_PREFIX + "northamerica=en,es\n");
+ COMMENT_MAP.put(PROPERTY_SDK,
+ "# location of the SDK. This is only used by Ant\n" +
"# For customization when using a Version Control System, please read the\n" +
"# header note.\n");
}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigEditDialog.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigEditDialog.java
new file mode 100644
index 0000000..1460fd7
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigEditDialog.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2009 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.sdkuilib;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.VerifyEvent;
+import org.eclipse.swt.events.VerifyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Edit dialog to create/edit APK configuration. The dialog displays 2 text fields for the config
+ * name and its filter.
+ */
+class ApkConfigEditDialog extends Dialog implements ModifyListener, VerifyListener {
+
+ private String mName;
+ private String mFilter;
+ private Text mNameField;
+ private Text mFilterField;
+ private Button mOkButton;
+
+ /**
+ * Creates an edit dialog with optional initial values for the name and filter.
+ * @param name optional value for the name. Can be null.
+ * @param filter optional value for the filter. Can be null.
+ * @param parentShell the parent shell.
+ */
+ protected ApkConfigEditDialog(String name, String filter, Shell parentShell) {
+ super(parentShell);
+ mName = name;
+ mFilter = filter;
+ }
+
+ /**
+ * Returns the name of the config. This is only valid if the user clicked OK and {@link #open()}
+ * returned {@link Window#OK}
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the filter for the config. This is only valid if the user clicked OK and
+ * {@link #open()} returned {@link Window#OK}
+ */
+ public String getFilter() {
+ return mFilter;
+ }
+
+ @Override
+ protected Control createContents(Composite parent) {
+ Control control = super.createContents(parent);
+
+ mOkButton = getButton(IDialogConstants.OK_ID);
+ validateButtons();
+
+ return control;
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite composite = new Composite(parent, SWT.NONE);
+ GridLayout layout;
+ composite.setLayout(layout = new GridLayout(2, false));
+ layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
+ layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
+ layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
+ layout.horizontalSpacing = convertHorizontalDLUsToPixels(
+ IDialogConstants.HORIZONTAL_SPACING);
+
+ composite.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ Label l = new Label(composite, SWT.NONE);
+ l.setText("Name");
+
+ mNameField = new Text(composite, SWT.BORDER);
+ mNameField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mNameField.addVerifyListener(this);
+ if (mName != null) {
+ mNameField.setText(mName);
+ }
+ mNameField.addModifyListener(this);
+
+ l = new Label(composite, SWT.NONE);
+ l.setText("Filter");
+
+ mFilterField = new Text(composite, SWT.BORDER);
+ mFilterField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ if (mFilter != null) {
+ mFilterField.setText(mFilter);
+ }
+ mFilterField.addVerifyListener(this);
+ mFilterField.addModifyListener(this);
+
+ applyDialogFont(composite);
+ return composite;
+ }
+
+ /**
+ * Validates the OK button based on the content of the 2 text fields.
+ */
+ private void validateButtons() {
+ mOkButton.setEnabled(mNameField.getText().trim().length() > 0 &&
+ mFilterField.getText().trim().length() > 0);
+ }
+
+ @Override
+ protected void okPressed() {
+ mName = mNameField.getText();
+ mFilter = mFilterField.getText().trim();
+ super.okPressed();
+ }
+
+ /**
+ * Callback for text modification in the 2 text fields.
+ */
+ public void modifyText(ModifyEvent e) {
+ validateButtons();
+ }
+
+ /**
+ * Callback to ensure the content of the text field are proper.
+ */
+ public void verifyText(VerifyEvent e) {
+ Text source = ((Text)e.getSource());
+ if (source == mNameField) {
+ // check for a-zA-Z0-9.
+ final String text = e.text;
+ final int len = text.length();
+ for (int i = 0 ; i < len; i++) {
+ char letter = text.charAt(i);
+ if (letter > 255 || Character.isLetterOrDigit(letter) == false) {
+ e.doit = false;
+ return;
+ }
+ }
+ } else if (source == mFilterField) {
+ // we can't validate the content as its typed, but we can at least ensure the characters
+ // are valid. Same as mNameFiled + the comma.
+ final String text = e.text;
+ final int len = text.length();
+ for (int i = 0 ; i < len; i++) {
+ char letter = text.charAt(i);
+ if (letter > 255 || (Character.isLetterOrDigit(letter) == false && letter != ',')) {
+ e.doit = false;
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java
new file mode 100644
index 0000000..6bf1df3
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2009 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.sdkuilib;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The APK Configuration widget is a table that is added to the given parent composite.
+ * <p/>
+ * To use, create it using {@link #ApkConfigWidget(Composite)} then
+ * call {@link #fillTable(Map) to set the initial list of configurations.
+ */
+public class ApkConfigWidget {
+ private final static int INDEX_NAME = 0;
+ private final static int INDEX_FILTER = 1;
+
+ private Table mApkConfigTable;
+ private Button mEditButton;
+ private Button mDelButton;
+
+ public ApkConfigWidget(final Composite parent) {
+ final Composite apkConfigComp = new Composite(parent, SWT.NONE);
+ apkConfigComp.setLayoutData(new GridData(GridData.FILL_BOTH));
+ apkConfigComp.setLayout(new GridLayout(2, false));
+
+ mApkConfigTable = new Table(apkConfigComp, SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER);
+ mApkConfigTable.setHeaderVisible(true);
+ mApkConfigTable.setLinesVisible(true);
+
+ GridData data = new GridData();
+ data.grabExcessVerticalSpace = true;
+ data.grabExcessHorizontalSpace = true;
+ data.horizontalAlignment = GridData.FILL;
+ data.verticalAlignment = GridData.FILL;
+ mApkConfigTable.setLayoutData(data);
+
+ // create the table columns
+ final TableColumn column0 = new TableColumn(mApkConfigTable, SWT.NONE);
+ column0.setText("Name");
+ column0.setWidth(100);
+ final TableColumn column1 = new TableColumn(mApkConfigTable, SWT.NONE);
+ column1.setText("Configuration");
+ column1.setWidth(100);
+
+ Composite buttonComp = new Composite(apkConfigComp, SWT.NONE);
+ buttonComp.setLayoutData(new GridData(GridData.FILL_VERTICAL));
+ GridLayout gl;
+ buttonComp.setLayout(gl = new GridLayout(1, false));
+ gl.marginHeight = gl.marginWidth = 0;
+
+ Button newButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
+ newButton.setText("New...");
+ newButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ mEditButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
+ mEditButton.setText("Edit...");
+ mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ mDelButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
+ mDelButton.setText("Delete");
+ mDelButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ newButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ ApkConfigEditDialog dlg = new ApkConfigEditDialog(null /*name*/, null /*filter*/,
+ apkConfigComp.getShell());
+ if (dlg.open() == Dialog.OK) {
+ TableItem item = new TableItem(mApkConfigTable, SWT.NONE);
+ item.setText(INDEX_NAME, dlg.getName());
+ item.setText(INDEX_FILTER, dlg.getFilter());
+
+ onSelectionChanged();
+ }
+ }
+ });
+
+ mEditButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ // get the current selection (single mode so we don't care about any item beyond
+ // index 0).
+ TableItem[] items = mApkConfigTable.getSelection();
+ if (items.length != 0) {
+ ApkConfigEditDialog dlg = new ApkConfigEditDialog(
+ items[0].getText(INDEX_NAME), items[0].getText(INDEX_FILTER),
+ apkConfigComp.getShell());
+ if (dlg.open() == Dialog.OK) {
+ items[0].setText(INDEX_NAME, dlg.getName());
+ items[0].setText(INDEX_FILTER, dlg.getFilter());
+ }
+ }
+ }
+ });
+
+ mDelButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ // get the current selection (single mode so we don't care about any item beyond
+ // index 0).
+ int[] indices = mApkConfigTable.getSelectionIndices();
+ if (indices.length != 0) {
+ TableItem item = mApkConfigTable.getItem(indices[0]);
+ if (MessageDialog.openQuestion(parent.getShell(),
+ "Apk Configuration deletion",
+ String.format(
+ "Are you sure you want to delete configuration '%1$s'?",
+ item.getText(INDEX_NAME)))) {
+ // delete the item.
+ mApkConfigTable.remove(indices[0]);
+
+ onSelectionChanged();
+ }
+ }
+ }
+ });
+
+ // Add a listener to resize the column to the full width of the table
+ mApkConfigTable.addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ Rectangle r = mApkConfigTable.getClientArea();
+ column0.setWidth(r.width * 30 / 100); // 30%
+ column1.setWidth(r.width * 70 / 100); // 70%
+ }
+ });
+
+ // add a selection listener on the table, to enable/disable buttons.
+ mApkConfigTable.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onSelectionChanged();
+ }
+ });
+ }
+
+ public void fillTable(Map<String, String> apkConfigMap) {
+ // get the names in a list so that we can sort them.
+ if (apkConfigMap != null) {
+ Set<String> keys = apkConfigMap.keySet();
+ String[] keyArray = keys.toArray(new String[keys.size()]);
+ Arrays.sort(keyArray);
+
+ for (String key : keyArray) {
+ TableItem item = new TableItem(mApkConfigTable, SWT.NONE);
+ item.setText(INDEX_NAME, key);
+ item.setText(INDEX_FILTER, apkConfigMap.get(key));
+ }
+ }
+
+ onSelectionChanged();
+ }
+
+ public Map<String, String> getApkConfigs() {
+ // go through all the items from the table and fill a new map
+ HashMap<String, String> map = new HashMap<String, String>();
+
+ TableItem[] items = mApkConfigTable.getItems();
+ for (TableItem item : items) {
+ map.put(item.getText(INDEX_NAME), item.getText(INDEX_FILTER));
+ }
+
+ return map;
+ }
+
+ /**
+ * Handles table selection changes.
+ */
+ private void onSelectionChanged() {
+ if (mApkConfigTable.getSelectionCount() > 0) {
+ mEditButton.setEnabled(true);
+ mDelButton.setEnabled(true);
+ } else {
+ mEditButton.setEnabled(false);
+ mDelButton.setEnabled(false);
+ }
+ }
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
index fc951f2..5f9e9c2 100644
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/SdkTargetSelector.java
@@ -48,32 +48,57 @@
*/
public class SdkTargetSelector {
- private final IAndroidTarget[] mTargets;
+ private IAndroidTarget[] mTargets;
+ private final boolean mAllowSelection;
private final boolean mAllowMultipleSelection;
private SelectionListener mSelectionListener;
private Table mTable;
private Label mDescription;
+ private Composite mInnerGroup;
+
+ /**
+ * Creates a new SDK Target Selector.
+ *
+ * @param parent The parent composite where the selector will be added.
+ * @param targets The list of targets. This is <em>not</em> copied, the caller must not modify.
+ * Targets can be null or an empty array, in which case the table is disabled.
+ * @param allowMultipleSelection True if more than one SDK target can be selected at the same
+ * time.
+ */
+ public SdkTargetSelector(Composite parent, IAndroidTarget[] targets,
+ boolean allowMultipleSelection) {
+ this(parent, targets, true /*allowSelection*/, allowMultipleSelection);
+ }
/**
* Creates a new SDK Target Selector.
*
* @param parent The parent composite where the selector will be added.
* @param targets The list of targets. This is <em>not</em> copied, the caller must not modify.
+ * Targets can be null or an empty array, in which case the table is disabled.
+ * @param allowSelection True if selection is enabled.
* @param allowMultipleSelection True if more than one SDK target can be selected at the same
- * time.
+ * time. Used only if allowSelection is true.
*/
public SdkTargetSelector(Composite parent, IAndroidTarget[] targets,
+ boolean allowSelection,
boolean allowMultipleSelection) {
- mTargets = targets;
-
// Layout has 1 column
- Composite group = new Composite(parent, SWT.NONE);
- group.setLayout(new GridLayout());
- group.setLayoutData(new GridData(GridData.FILL_BOTH));
- group.setFont(parent.getFont());
+ mInnerGroup = new Composite(parent, SWT.NONE);
+ mInnerGroup.setLayout(new GridLayout());
+ mInnerGroup.setLayoutData(new GridData(GridData.FILL_BOTH));
+ mInnerGroup.setFont(parent.getFont());
+ mAllowSelection = allowSelection;
mAllowMultipleSelection = allowMultipleSelection;
- mTable = new Table(group, SWT.CHECK | SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER);
+ int style = SWT.BORDER;
+ if (allowSelection) {
+ style |= SWT.CHECK | SWT.FULL_SELECTION;
+ }
+ if (!mAllowMultipleSelection) {
+ style |= SWT.SINGLE;
+ }
+ mTable = new Table(mInnerGroup, style);
mTable.setHeaderVisible(true);
mTable.setLinesVisible(false);
@@ -84,7 +109,7 @@
data.verticalAlignment = GridData.FILL;
mTable.setLayoutData(data);
- mDescription = new Label(group, SWT.WRAP);
+ mDescription = new Label(mInnerGroup, SWT.WRAP);
mDescription.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// create the table columns
@@ -93,15 +118,26 @@
final TableColumn column1 = new TableColumn(mTable, SWT.NONE);
column1.setText("Vendor");
final TableColumn column2 = new TableColumn(mTable, SWT.NONE);
- column2.setText("API Level");
+ column2.setText("Version");
final TableColumn column3 = new TableColumn(mTable, SWT.NONE);
- column3.setText("SDK");
+ column3.setText("API Level");
adjustColumnsWidth(mTable, column0, column1, column2, column3);
setupSelectionListener(mTable);
- fillTable(mTable);
+ setTargets(targets);
setupTooltip(mTable);
}
+
+ /**
+ * Returns the layout data of the inner composite widget that contains the target selector.
+ * By default the layout data is set to a {@link GridData} with a {@link GridData#FILL_BOTH}
+ * mode.
+ * <p/>
+ * This can be useful if you want to change the {@link GridData#horizontalSpan} for example.
+ */
+ public Object getLayoutData() {
+ return mInnerGroup.getLayoutData();
+ }
/**
* Returns the list of known targets.
@@ -113,6 +149,16 @@
}
/**
+ * Changes the targets of the SDK Target Selector.
+ *
+ * @param targets The list of targets. This is <em>not</em> copied, the caller must not modify.
+ */
+ public void setTargets(IAndroidTarget[] targets) {
+ mTargets = targets;
+ fillTable(mTable);
+ }
+
+ /**
* Sets a selection listener. Set it to null to remove it.
* The listener will be called <em>after</em> this table processed its selection
* events so that the caller can see the updated state.
@@ -139,6 +185,10 @@
* @return true if the target could be selected, false otherwise.
*/
public boolean setSelection(IAndroidTarget target) {
+ if (!mAllowSelection) {
+ return false;
+ }
+
boolean found = false;
boolean modified = false;
for (TableItem i : mTable.getItems()) {
@@ -224,6 +274,10 @@
* double-clicked (aka "the default selection").
*/
private void setupSelectionListener(final Table table) {
+ if (!mAllowSelection) {
+ return;
+ }
+
// Add a selection listener that will check/uncheck items when they are double-clicked
table.addSelectionListener(new SelectionListener() {
/** Default selection means double-click on "most" platforms */
@@ -281,6 +335,9 @@
* </ul>
*/
private void fillTable(final Table table) {
+
+ table.removeAll();
+
if (mTargets != null && mTargets.length > 0) {
table.setEnabled(true);
for (IAndroidTarget target : mTargets) {
diff --git a/tools/traceview/etc/traceview.bat b/tools/traceview/etc/traceview.bat
index d074f42..a9b573d 100755
--- a/tools/traceview/etc/traceview.bat
+++ b/tools/traceview/etc/traceview.bat
@@ -20,9 +20,9 @@
rem and set up progdir to be the fully-qualified pathname of its directory.
set prog=%~f0
-rem Change current directory to where traceview is, to avoid issues with directories
-rem containing whitespaces.
-cd %~dp0
+rem Change current directory and drive to where traceview.bat is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
set jarfile=traceview.jar
set frameworkdir=