auto import from //depot/cupcake/@137055

This commit is contained in:
The Android Open Source Project
2009-03-02 22:54:20 -08:00
parent 74a996a2c7
commit b8747bc7b1
91 changed files with 3663 additions and 838 deletions

View File

@@ -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)

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -1,7 +1,7 @@
LOCAL_PATH:= $(call my-dir) LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := foo LOCAL_MODULE_TAGS := user
LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_SRC_FILES := $(call all-subdir-java-files)

View File

@@ -20,6 +20,17 @@
<resources> <resources>
<string name="app_label">Spare Parts</string> <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="general_title">General</string>
<string name="title_window_animations">Window animations</string> <string name="title_window_animations">Window animations</string>
@@ -48,7 +59,7 @@
<string name="title_accelerometer">Display rotation</string> <string name="title_accelerometer">Display rotation</string>
<string name="summary_on_accelerometer">Display rotates from orientation</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> <string name="applications_title">Applications</string>

View File

@@ -18,6 +18,35 @@
--> -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <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 <PreferenceCategory
android:title="@string/general_title"> android:title="@string/general_title">

View File

@@ -19,7 +19,11 @@ package com.android.spare_parts;
import android.app.ActivityManagerNative; import android.app.ActivityManagerNative;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences; 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.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.RemoteException; import android.os.RemoteException;
@@ -28,6 +32,7 @@ import android.preference.CheckBoxPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.provider.Settings; import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException; import android.provider.Settings.SettingNotFoundException;
@@ -35,11 +40,17 @@ import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.IWindowManager; import android.view.IWindowManager;
import java.util.List;
public class SpareParts extends PreferenceActivity public class SpareParts extends PreferenceActivity
implements Preference.OnPreferenceChangeListener, implements Preference.OnPreferenceChangeListener,
SharedPreferences.OnSharedPreferenceChangeListener { SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "SpareParts"; 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 WINDOW_ANIMATIONS_PREF = "window_animations";
private static final String TRANSITION_ANIMATIONS_PREF = "transition_animations"; private static final String TRANSITION_ANIMATIONS_PREF = "transition_animations";
private static final String FANCY_IME_ANIMATIONS_PREF = "fancy_ime_animations"; private static final String FANCY_IME_ANIMATIONS_PREF = "fancy_ime_animations";
@@ -62,6 +73,41 @@ public class SpareParts extends PreferenceActivity
private IWindowManager mWindowManager; 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 @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
@@ -84,7 +130,15 @@ public class SpareParts extends PreferenceActivity
mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); 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() { private void updateToggles() {

View File

@@ -66,6 +66,7 @@ development/samples/NotePad platforms/${PLATFORM_NAME}/samples/NotePad
development/samples/ApiDemos platforms/${PLATFORM_NAME}/samples/ApiDemos development/samples/ApiDemos platforms/${PLATFORM_NAME}/samples/ApiDemos
development/samples/SkeletonApp platforms/${PLATFORM_NAME}/samples/SkeletonApp development/samples/SkeletonApp platforms/${PLATFORM_NAME}/samples/SkeletonApp
development/samples/Snake platforms/${PLATFORM_NAME}/samples/Snake development/samples/Snake platforms/${PLATFORM_NAME}/samples/Snake
development/samples/SoftKeyboard platforms/${PLATFORM_NAME}/samples/SoftKeyboard
# dx # dx
bin/dx platforms/${PLATFORM_NAME}/tools/dx bin/dx platforms/${PLATFORM_NAME}/tools/dx

View File

@@ -146,6 +146,11 @@ function package() {
echo "Done" echo "Done"
echo echo
echo "Resulting SDK is in $DIST_DIR/$DEST_NAME_ZIP" 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 check

19
pdk/docs/templates/footer.cs vendored Executable file
View File

@@ -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 -->

37
pdk/docs/templates/head_tag.cs vendored Executable file
View File

@@ -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>

3
pdk/docs/templates/header.cs vendored Executable file
View File

@@ -0,0 +1,3 @@
<?cs call:custom_masthead() ?>
<?cs call:custom_left_nav() ?>

8
pdk/docs/templates/index.cs vendored Executable file
View File

@@ -0,0 +1,8 @@
<html>
<head>
<meta http-equiv="refresh" content="0;url=packages.html">
</head>
<body>
<?cs include:"analytics.cs" ?>
</body>
</html>

11
pdk/docs/templates/trailer.cs vendored Executable file
View File

@@ -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>

View File

@@ -105,7 +105,7 @@ public class ApplicationsStackLayout extends ViewGroup implements View.OnClickLi
a.recycle(); a.recycle();
mIconSize = (int) getResources().getDimension(android.R.dimen.app_icon_size); mIconSize = 42; //(int) getResources().getDimension(android.R.dimen.app_icon_size);
initLayout(); initLayout();
} }

View File

@@ -21,28 +21,26 @@ import android.app.ActivityManager;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; 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.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter; import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.ColorFilter;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable; import android.graphics.drawable.PaintDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Environment;
import android.util.Log; import android.util.Log;
import android.util.Xml;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@@ -57,14 +55,19 @@ import android.widget.ArrayAdapter;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.GridView; import android.widget.GridView;
import android.widget.TextView; import android.widget.TextView;
import android.net.Uri;
import java.io.IOException; import java.io.IOException;
import java.io.FileReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
public class Home extends Activity { public class Home extends Activity {
/** /**
* Tag used for logging errors. * Tag used for logging errors.
@@ -76,6 +79,13 @@ public class Home extends Activity {
*/ */
private static final String KEY_SAVE_GRID_OPENED = "grid.opened"; 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 // Identifiers for option menu items
private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1; private static final int MENU_WALLPAPER_SETTINGS = Menu.FIRST + 1;
private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1; private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
@@ -90,11 +100,8 @@ public class Home extends Activity {
private static ArrayList<ApplicationInfo> mApplications; private static ArrayList<ApplicationInfo> mApplications;
private static LinkedList<ApplicationInfo> mFavorites; private static LinkedList<ApplicationInfo> mFavorites;
private Handler mHandler = new Handler();
private final BroadcastReceiver mWallpaperReceiver = new WallpaperIntentReceiver(); private final BroadcastReceiver mWallpaperReceiver = new WallpaperIntentReceiver();
private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver(); private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver();
private final ContentObserver mObserver = new FavoritesChangeObserver();
private GridView mGrid; private GridView mGrid;
@@ -120,7 +127,6 @@ public class Home extends Activity {
setContentView(R.layout.home); setContentView(R.layout.home);
registerIntentReceivers(); registerIntentReceivers();
registerContentObservers();
setDefaultWallpaper(); setDefaultWallpaper();
@@ -156,7 +162,6 @@ public class Home extends Activity {
mApplications.get(i).icon.setCallback(null); mApplications.get(i).icon.setCallback(null);
} }
getContentResolver().unregisterContentObserver(mObserver);
unregisterReceiver(mWallpaperReceiver); unregisterReceiver(mWallpaperReceiver);
unregisterReceiver(mApplicationsReceiver); unregisterReceiver(mApplicationsReceiver);
} }
@@ -198,16 +203,6 @@ public class Home extends Activity {
registerReceiver(mApplicationsReceiver, filter); registerReceiver(mApplicationsReceiver, filter);
} }
/**
* 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. * Creates a new appplications adapter for the grid view and registers it.
*/ */
@@ -247,7 +242,7 @@ public class Home extends Activity {
Log.e(LOG_TAG, "Failed to clear wallpaper " + e); Log.e(LOG_TAG, "Failed to clear wallpaper " + e);
} }
} else { } else {
getWindow().setBackgroundDrawable(wallpaper); getWindow().setBackgroundDrawable(new ClippedDrawable(wallpaper));
} }
mWallpaperChecked = true; mWallpaperChecked = true;
} }
@@ -259,18 +254,17 @@ public class Home extends Activity {
*/ */
private void bindFavorites(boolean isLaunching) { private void bindFavorites(boolean isLaunching) {
if (!isLaunching || mFavorites == null) { 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"); FileReader favReader;
final int titleIndex = c.getColumnIndexOrThrow("title");
final int typeIndex = c.getColumnIndexOrThrow("itemType");
final PackageManager manager = getPackageManager(); // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
final File favFile = new File(Environment.getRootDirectory(), DEFAULT_FAVORITES_PATH);
ApplicationInfo info; try {
String intentDescription; favReader = new FileReader(favFile);
} catch (FileNotFoundException e) {
Log.e(LOG_TAG, "Couldn't find or open favorites file " + favFile);
return;
}
if (mFavorites == null) { if (mFavorites == null) {
mFavorites = new LinkedList<ApplicationInfo>(); mFavorites = new LinkedList<ApplicationInfo>();
@@ -278,38 +272,77 @@ public class Home extends Activity {
mFavorites.clear(); mFavorites.clear();
} }
while (c.moveToNext()) { final Intent intent = new Intent(Intent.ACTION_MAIN, null);
final int itemType = c.getInt(typeIndex); intent.addCategory(Intent.CATEGORY_LAUNCHER);
if (itemType == 0 || // 0 == application final PackageManager packageManager = getPackageManager();
itemType == 1) { // 1 == shortcut
intentDescription = c.getString(intentIndex); try {
if (intentDescription == null) { final XmlPullParser parser = Xml.newPullParser();
continue; 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; final String favoritePackage = parser.getAttributeValue(null, TAG_PACKAGE);
try { final String favoriteClass = parser.getAttributeValue(null, TAG_CLASS);
intent = Intent.getIntent(intentDescription);
} catch (java.net.URISyntaxException e) { final ComponentName cn = new ComponentName(favoritePackage, favoriteClass);
continue; intent.setComponent(cn);
} intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
info = getApplicationInfo(manager, intent);
info = getApplicationInfo(packageManager, intent);
if (info != null) { if (info != null) {
info.title = c.getString(titleIndex);
info.intent = intent; info.intent = intent;
mFavorites.addFirst(info); 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); 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 * Refreshes the recently launched applications stacked over the favorites. The number
* of recents depends on how many favorites are present. * of recents depends on how many favorites are present.
@@ -361,14 +394,6 @@ public class Home extends Activity {
return info; return info;
} }
/**
* When the notification that favorites have changed is received, requests
* a favorites list refresh.
*/
private void onFavoritesChanged() {
bindFavorites(false);
}
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event) { public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) { if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -530,7 +555,7 @@ public class Home extends Activity {
private class WallpaperIntentReceiver extends BroadcastReceiver { private class WallpaperIntentReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
getWindow().setBackgroundDrawable(getWallpaper()); getWindow().setBackgroundDrawable(new ClippedDrawable(getWallpaper()));
} }
} }
@@ -547,20 +572,6 @@ public class Home extends Activity {
} }
} }
/**
* 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. * GridView adapter to show the list of all installed applications.
*/ */
@@ -580,13 +591,12 @@ public class Home extends Activity {
convertView = inflater.inflate(R.layout.application, parent, false); convertView = inflater.inflate(R.layout.application, parent, false);
} }
//final ImageView imageView = (ImageView) convertView.findViewById(R.id.icon);
Drawable icon = info.icon; Drawable icon = info.icon;
if (!info.filtered) { if (!info.filtered) {
final Resources resources = getContext().getResources(); //final Resources resources = getContext().getResources();
int width = (int) resources.getDimension(android.R.dimen.app_icon_size); int width = 42;//(int) resources.getDimension(android.R.dimen.app_icon_size);
int height = (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 iconWidth = icon.getIntrinsicWidth();
final int iconHeight = icon.getIntrinsicHeight(); final int iconHeight = icon.getIntrinsicHeight();
@@ -687,4 +697,47 @@ public class Home extends Activity {
startActivity(app.intent); 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();
}
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -25,9 +25,7 @@
<string name="word_separators">\u0020.,;:!?\n()[]*&amp;@{}/&lt;&gt;_+=|&quot;</string> <string name="word_separators">\u0020.,;:!?\n()[]*&amp;@{}/&lt;&gt;_+=|&quot;</string>
<!-- Labels on soft keys --> <!-- Labels on soft keys -->
<string name="label_done">Done</string> <string name="label_go_key">Go</string>
<string name="label_search">Search</string> <string name="label_next_key">Next</string>
<string name="label_enter">Enter</string> <string name="label_send_key">Send</string>
<string name="label_next">Next</string>
<string name="label_previous">Previous</string>
</resources> </resources>

View File

@@ -20,9 +20,14 @@ import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.XmlResourceParser; import android.content.res.XmlResourceParser;
import android.inputmethodservice.Keyboard; import android.inputmethodservice.Keyboard;
import android.inputmethodservice.Keyboard.Key;
import android.inputmethodservice.Keyboard.Row;
import android.view.inputmethod.EditorInfo;
public class LatinKeyboard extends Keyboard { public class LatinKeyboard extends Keyboard {
private Key mEnterKey;
public LatinKeyboard(Context context, int xmlLayoutResId) { public LatinKeyboard(Context context, int xmlLayoutResId) {
super(context, xmlLayoutResId); super(context, xmlLayoutResId);
} }
@@ -35,7 +40,49 @@ public class LatinKeyboard extends Keyboard {
@Override @Override
protected Key createKeyFromXml(Resources res, Row parent, int x, int y, protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
XmlResourceParser parser) { 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 { static class LatinKey extends Keyboard.Key {

View File

@@ -65,11 +65,11 @@ public class SoftKeyboard extends InputMethodService
private long mLastShiftTime; private long mLastShiftTime;
private long mMetaState; private long mMetaState;
private Keyboard mSymbolsKeyboard; private LatinKeyboard mSymbolsKeyboard;
private Keyboard mSymbolsShiftedKeyboard; private LatinKeyboard mSymbolsShiftedKeyboard;
private Keyboard mQwertyKeyboard; private LatinKeyboard mQwertyKeyboard;
private Keyboard mCurKeyboard; private LatinKeyboard mCurKeyboard;
private String mWordSeparators; private String mWordSeparators;
@@ -208,6 +208,10 @@ public class SoftKeyboard extends InputMethodService
// keyboard with no special features. // keyboard with no special features.
mCurKeyboard = mQwertyKeyboard; 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 class SoftKeyboard extends InputMethodService
} }
} }
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 * Update the list of available candidates from the current composing
* text. This will need to be filled in by however you are determining * text. This will need to be filled in by however you are determining

208
testrunner/tests.xml Normal file
View File

@@ -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>

View File

@@ -20,9 +20,9 @@ rem Set up prog to be the path of this script, including following symlinks,
rem and set up progdir to be the fully-qualified pathname of its directory. rem and set up progdir to be the fully-qualified pathname of its directory.
set prog=%~f0 set prog=%~f0
rem Change current directory to where ddms is, to avoid issues with directories rem Change current directory and drive to where the script is, to avoid
rem containing whitespaces. rem issues with directories containing whitespaces.
cd %~dp0 cd /d %~dp0
set jarfile=apkbuilder.jar set jarfile=apkbuilder.jar
set frameworkdir= set frameworkdir=

View File

@@ -20,9 +20,9 @@ rem Set up prog to be the path of this script, including following symlinks,
rem and set up progdir to be the fully-qualified pathname of its directory. rem and set up progdir to be the fully-qualified pathname of its directory.
set prog=%~f0 set prog=%~f0
rem Change current directory to where ddms is, to avoid issues with directories rem Change current directory and drive to where the script is, to avoid
rem containing whitespaces. rem issues with directories containing whitespaces.
cd %~dp0 cd /d %~dp0
set jarfile=ddms.jar set jarfile=ddms.jar
set frameworkdir= set frameworkdir=

View File

@@ -94,7 +94,7 @@ public class Client {
* is only used for data generated within Client. * is only used for data generated within Client.
*/ */
private static final int INITIAL_BUF_SIZE = 2*1024; 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 ByteBuffer mReadBuffer;
private static final int WRITE_BUF_SIZE = 256; private static final int WRITE_BUF_SIZE = 256;

View File

@@ -30,7 +30,7 @@ import java.util.Map;
/** /**
* A Device. It can be a physical device or an emulator. * A Device. It can be a physical device or an emulator.
* *
* TODO: make this class package-protected, and shift all callers to use IDevice * TODO: make this class package-protected, and shift all callers to use IDevice
*/ */
public final class Device implements IDevice { public final class Device implements IDevice {
@@ -62,10 +62,10 @@ public final class Device implements IDevice {
return null; return null;
} }
} }
/** Emulator Serial Number regexp. */ /** Emulator Serial Number regexp. */
final static String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$ final static String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$
/** Serial number of the device */ /** Serial number of the device */
String serialNumber = null; String serialNumber = null;
@@ -74,7 +74,7 @@ public final class Device implements IDevice {
/** State of the device. */ /** State of the device. */
DeviceState state = null; DeviceState state = null;
/** Device properties. */ /** Device properties. */
private final Map<String, String> mProperties = new HashMap<String, String>(); private final Map<String, String> mProperties = new HashMap<String, String>();
@@ -85,29 +85,29 @@ public final class Device implements IDevice {
* Socket for the connection monitoring client connection/disconnection. * Socket for the connection monitoring client connection/disconnection.
*/ */
private SocketChannel mSocketChannel; private SocketChannel mSocketChannel;
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getSerialNumber() * @see com.android.ddmlib.IDevice#getSerialNumber()
*/ */
public String getSerialNumber() { public String getSerialNumber() {
return serialNumber; return serialNumber;
} }
public String getAvdName() { public String getAvdName() {
return mAvdName; return mAvdName;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getState() * @see com.android.ddmlib.IDevice#getState()
*/ */
public DeviceState getState() { public DeviceState getState() {
return state; return state;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getProperties() * @see com.android.ddmlib.IDevice#getProperties()
*/ */
@@ -115,7 +115,7 @@ public final class Device implements IDevice {
return Collections.unmodifiableMap(mProperties); return Collections.unmodifiableMap(mProperties);
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getPropertyCount() * @see com.android.ddmlib.IDevice#getPropertyCount()
*/ */
@@ -123,21 +123,21 @@ public final class Device implements IDevice {
return mProperties.size(); return mProperties.size();
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getProperty(java.lang.String) * @see com.android.ddmlib.IDevice#getProperty(java.lang.String)
*/ */
public String getProperty(String name) { public String getProperty(String name) {
return mProperties.get(name); return mProperties.get(name);
} }
@Override @Override
public String toString() { public String toString() {
return serialNumber; return serialNumber;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#isOnline() * @see com.android.ddmlib.IDevice#isOnline()
*/ */
@@ -145,7 +145,7 @@ public final class Device implements IDevice {
return state == DeviceState.ONLINE; return state == DeviceState.ONLINE;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#isEmulator() * @see com.android.ddmlib.IDevice#isEmulator()
*/ */
@@ -153,7 +153,7 @@ public final class Device implements IDevice {
return serialNumber.matches(RE_EMULATOR_SN); return serialNumber.matches(RE_EMULATOR_SN);
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#isOffline() * @see com.android.ddmlib.IDevice#isOffline()
*/ */
@@ -161,7 +161,7 @@ public final class Device implements IDevice {
return state == DeviceState.OFFLINE; return state == DeviceState.OFFLINE;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#isBootLoader() * @see com.android.ddmlib.IDevice#isBootLoader()
*/ */
@@ -169,7 +169,7 @@ public final class Device implements IDevice {
return state == DeviceState.BOOTLOADER; return state == DeviceState.BOOTLOADER;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#hasClients() * @see com.android.ddmlib.IDevice#hasClients()
*/ */
@@ -177,7 +177,7 @@ public final class Device implements IDevice {
return mClients.size() > 0; return mClients.size() > 0;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getClients() * @see com.android.ddmlib.IDevice#getClients()
*/ */
@@ -186,8 +186,8 @@ public final class Device implements IDevice {
return mClients.toArray(new Client[mClients.size()]); return mClients.toArray(new Client[mClients.size()]);
} }
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getClient(java.lang.String) * @see com.android.ddmlib.IDevice#getClient(java.lang.String)
*/ */
@@ -204,7 +204,7 @@ public final class Device implements IDevice {
return null; return null;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getSyncService() * @see com.android.ddmlib.IDevice#getSyncService()
*/ */
@@ -217,7 +217,7 @@ public final class Device implements IDevice {
return null; return null;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getFileListingService() * @see com.android.ddmlib.IDevice#getFileListingService()
*/ */
@@ -225,7 +225,7 @@ public final class Device implements IDevice {
return new FileListingService(this); return new FileListingService(this);
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getScreenshot() * @see com.android.ddmlib.IDevice#getScreenshot()
*/ */
@@ -233,7 +233,7 @@ public final class Device implements IDevice {
return AdbHelper.getFrameBuffer(AndroidDebugBridge.sSocketAddr, this); return AdbHelper.getFrameBuffer(AndroidDebugBridge.sSocketAddr, this);
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#executeShellCommand(java.lang.String, com.android.ddmlib.IShellOutputReceiver) * @see com.android.ddmlib.IDevice#executeShellCommand(java.lang.String, com.android.ddmlib.IShellOutputReceiver)
*/ */
@@ -242,16 +242,25 @@ public final class Device implements IDevice {
AdbHelper.executeRemoteCommand(AndroidDebugBridge.sSocketAddr, command, this, AdbHelper.executeRemoteCommand(AndroidDebugBridge.sSocketAddr, command, this,
receiver); receiver);
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#runEventLogService(com.android.ddmlib.log.LogReceiver) * @see com.android.ddmlib.IDevice#runEventLogService(com.android.ddmlib.log.LogReceiver)
*/ */
public void runEventLogService(LogReceiver receiver) throws IOException { public void runEventLogService(LogReceiver receiver) throws IOException {
AdbHelper.runEventLogService(AndroidDebugBridge.sSocketAddr, this, receiver); 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) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#createForward(int, int) * @see com.android.ddmlib.IDevice#createForward(int, int)
*/ */
@@ -265,7 +274,7 @@ public final class Device implements IDevice {
} }
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#removeForward(int, int) * @see com.android.ddmlib.IDevice#removeForward(int, int)
*/ */
@@ -279,7 +288,7 @@ public final class Device implements IDevice {
} }
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.android.ddmlib.IDevice#getClientName(int) * @see com.android.ddmlib.IDevice#getClientName(int)
*/ */
@@ -325,7 +334,7 @@ public final class Device implements IDevice {
return false; return false;
} }
void clearClientList() { void clearClientList() {
synchronized (mClients) { synchronized (mClients) {
mClients.clear(); mClients.clear();

View File

@@ -44,7 +44,7 @@ public interface IDevice {
* Returns the serial number of the device. * Returns the serial number of the device.
*/ */
public String getSerialNumber(); public String getSerialNumber();
/** /**
* Returns the name of the AVD the emulator is running. * Returns the name of the AVD the emulator is running.
* <p/>This is only valid if {@link #isEmulator()} returns true. * <p/>This is only valid if {@link #isEmulator()} returns true.
@@ -151,6 +151,14 @@ public interface IDevice {
*/ */
public void runEventLogService(LogReceiver receiver) throws IOException; 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. * Creates a port forwarding between a local and a remote port.
* @param localPort the local port to forward * @param localPort the local port to forward

View File

@@ -17,56 +17,69 @@
package com.android.ddmlib.testrunner; package com.android.ddmlib.testrunner;
/** /**
* Listener for instrumentation test runs * Receives event notifications during instrumentation test runs.
* * Patterned after {@link junit.runner.TestRunListener}.
* Modeled after junit.runner.TestRunListener
*/ */
public interface ITestRunListener { 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 * Reports the start of a test run.
* @param testCount - total number of tests in test run *
* */ * @param testCount total number of tests in test run
*/
public void testRunStarted(int testCount); public void testRunStarted(int testCount);
/** /**
* Reports end of test run * Reports end of test run.
* @param elapsedTime - device reported elapsed time, in milliseconds *
* @param elapsedTime device reported elapsed time, in milliseconds
*/ */
public void testRunEnded(long elapsedTime); public void testRunEnded(long elapsedTime);
/** /**
* Reports test run stopped before completion * Reports test run stopped before completion.
* @param elapsedTime - device reported elapsed time, in milliseconds *
* @param elapsedTime device reported elapsed time, in milliseconds
*/ */
public void testRunStopped(long elapsedTime); public void testRunStopped(long elapsedTime);
/** /**
* Reports the start of an individual test case * 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
* *
* @param status - one of STATUS_ERROR, STATUS_FAILURE * @param test identifies the test
* @param className - name of test class
* @param testName - name of test method
* @param trace - stack trace of failure
*/ */
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); public void testRunFailed(String errorMessage);
} }

View File

@@ -20,24 +20,21 @@ import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.Log; import com.android.ddmlib.Log;
import com.android.ddmlib.MultiLineReceiver; 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 * Parses the 'raw output mode' results of an instrumentation test run from shell and informs a
* ITestRunListener of the results * ITestRunListener of the results.
* *
* Expects the following output: * <p>Expects the following output:
* *
* If fatal error occurred when attempted to run the tests: * <p>If fatal error occurred when attempted to run the tests:
* <i> INSTRUMENTATION_FAILED: </i> * <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 * 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 * run, expects that the elapsed test time in seconds will be displayed
* *
* i.e. * <p>For example:
* <i> * <pre>
* INSTRUMENTATION_STATUS_CODE: 1 * INSTRUMENTATION_STATUS_CODE: 1
* INSTRUMENTATION_STATUS: class=com.foo.FooTest * INSTRUMENTATION_STATUS: class=com.foo.FooTest
* INSTRUMENTATION_STATUS: test=testFoo * INSTRUMENTATION_STATUS: test=testFoo
@@ -48,64 +45,85 @@ import java.util.Map;
* ... * ...
* *
* Time: X * Time: X
* </i> * </pre>
* * <p>Note that the "value" portion of the key-value pair may wrap over several text lines
* Note that the "value" portion of the key-value pair may wrap over several text lines
*/ */
public class InstrumentationResultParser extends MultiLineReceiver { public class InstrumentationResultParser extends MultiLineReceiver {
// relevant test status keys /** Relevant test status keys. */
private static final String CODE_KEY = "code"; private static class StatusKeys {
private static final String TEST_KEY = "test"; private static final String TEST = "test";
private static final String CLASS_KEY = "class"; private static final String CLASS = "class";
private static final String STACK_KEY = "stack"; private static final String STACK = "stack";
private static final String NUMTESTS_KEY = "numtests"; private static final String NUMTESTS = "numtests";
}
// test result status codes /** Test result status codes. */
private static final int FAILURE_STATUS_CODE = -2; private static class StatusCodes {
private static final int START_STATUS_CODE = 1; private static final int FAILURE = -2;
private static final int ERROR_STATUS_CODE = -1; private static final int START = 1;
private static final int OK_STATUS_CODE = 0; private static final int ERROR = -1;
private static final int OK = 0;
}
// recognized output patterns /** Prefixes used to identify output. */
private static final String STATUS_PREFIX = "INSTRUMENTATION_STATUS: "; private static class Prefixes {
private static final String STATUS_PREFIX_CODE = "INSTRUMENTATION_STATUS_CODE: "; private static final String STATUS = "INSTRUMENTATION_STATUS: ";
private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: "; private static final String STATUS_CODE = "INSTRUMENTATION_STATUS_CODE: ";
private static final String TIME_REPORT = "Time: "; private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: ";
private static final String TIME_REPORT = "Time: ";
}
private final ITestRunListener mTestListener; 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 */ * Test result data
private String mCurrentKey; */
/** stores the current "value" portion of the status key-value being parsed */ private static class TestResult {
private StringBuilder mCurrentValue; private Integer mCode = null;
/** true if start of test has already been reported to listener */ private String mTestName = null;
private boolean mTestStartReported; private String mTestClass = null;
/** the elapsed time of the test run, in ms */ private String mStackTrace = null;
private long mTestTime; private Integer mNumTests = null;
/** true if current test run has been canceled by user */
private boolean mIsCancelled; /** 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"; private static final String LOG_TAG = "InstrumentationResultParser";
/** /**
* Creates the InstrumentationResultParser * Creates the InstrumentationResultParser.
* @param listener - listener to report results to. will be informed of test results as the *
* tests are executing * @param listener informed of test results as the tests are executing
*/ */
public InstrumentationResultParser(ITestRunListener listener) { public InstrumentationResultParser(ITestRunListener listener) {
mStatusValues = new Hashtable<String, String>();
mCurrentKey = null;
setTrimLine(false);
mTestListener = listener; 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 * @see MultiLineReceiver#processNewLines
*/ */
@Override @Override
@@ -116,31 +134,37 @@ public class InstrumentationResultParser extends MultiLineReceiver {
} }
/** /**
* Parse an individual output line. Expects a line that either is: * Parse an individual output line. Expects a line that is one of:
* a) the start of a new status line (ie. starts with STATUS_PREFIX or STATUS_PREFIX_CODE), * <ul>
* and thus there is a new key=value pair to parse, and the previous key-value pair is * <li>
* finished * The start of a new status line (starts with Prefixes.STATUS or Prefixes.STATUS_CODE),
* b) a continuation of the previous status (ie the "value" portion of the key has wrapped * and thus there is a new key=value pair to parse, and the previous key-value pair is
* to the next line. * finished.
* c) a line reporting a fatal error in the test run (STATUS_FAILED) * </li>
* d) a line reporting the total elapsed time of the test run. * <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) { 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. // Previous status key-value has been collected. Store it.
submitCurrentKeyValue(); submitCurrentKeyValue();
parseStatusCode(line); parseStatusCode(line);
} else if (line.startsWith(STATUS_PREFIX)) { } else if (line.startsWith(Prefixes.STATUS)) {
// Previous status key-value has been collected. Store it. // Previous status key-value has been collected. Store it.
submitCurrentKeyValue(); submitCurrentKeyValue();
parseKey(line, STATUS_PREFIX.length()); parseKey(line, Prefixes.STATUS.length());
} else if (line.startsWith(STATUS_FAILED)) { } else if (line.startsWith(Prefixes.STATUS_FAILED)) {
Log.e(LOG_TAG, "test run failed " + line); Log.e(LOG_TAG, "test run failed " + line);
mTestListener.testRunFailed(line); mTestListener.testRunFailed(line);
} else if (line.startsWith(TIME_REPORT)) { } else if (line.startsWith(Prefixes.TIME_REPORT)) {
parseTime(line, TIME_REPORT.length()); parseTime(line, Prefixes.TIME_REPORT.length());
} else { } else {
if (mCurrentValue != null) { if (mCurrentValue != null) {
// this is a value that has wrapped to next line. // this is a value that has wrapped to next line.
@@ -153,21 +177,53 @@ public class InstrumentationResultParser extends MultiLineReceiver {
} }
/** /**
* Stores the currently parsed key-value pair in the status map * Stores the currently parsed key-value pair into mCurrentTestInfo.
*/ */
private void submitCurrentKeyValue() { private void submitCurrentKeyValue() {
if (mCurrentKey != null && mCurrentValue != null) { 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; mCurrentKey = null;
mCurrentValue = 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 * Parses the key from the current line.
* Expects format of "key=value", * 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 * @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) { private void parseKey(String line, int keyStartPos) {
int endKeyPos = line.indexOf('=', keyStartPos); int endKeyPos = line.indexOf('=', keyStartPos);
@@ -178,7 +234,8 @@ public class InstrumentationResultParser extends MultiLineReceiver {
} }
/** /**
* 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 line - full line of text to parse
* @param valueStartPos - the starting position of the value in the given line * @param valueStartPos - the starting position of the value in the given line
*/ */
@@ -188,20 +245,25 @@ public class InstrumentationResultParser extends MultiLineReceiver {
} }
/** /**
* Parses out a status code result. For consistency, stores the result as a CODE entry in * Parses out a status code result.
* key-value status map
*/ */
private void parseStatusCode(String line) { private void parseStatusCode(String line) {
String value = line.substring(STATUS_PREFIX_CODE.length()).trim(); String value = line.substring(Prefixes.STATUS_CODE.length()).trim();
mStatusValues.put(CODE_KEY, value); 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 // this means we're done with current test result bundle
reportResult(mStatusValues); reportResult(testInfo);
mStatusValues.clear(); clearCurrentTestInfo();
} }
/** /**
* Returns true if test run canceled * Returns true if test run canceled.
* *
* @see IShellOutputReceiver#isCancelled() * @see IShellOutputReceiver#isCancelled()
*/ */
@@ -210,7 +272,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
} }
/** /**
* Requests cancellation of test result parsing * Requests cancellation of test run.
*/ */
public void cancel() { public void cancel() {
mIsCancelled = true; mIsCancelled = true;
@@ -219,82 +281,62 @@ public class InstrumentationResultParser extends MultiLineReceiver {
/** /**
* Reports a test result to the test run listener. Must be called when a individual test * Reports a test result to the test run listener. Must be called when a individual test
* result has been fully parsed. * 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) { private void reportResult(TestResult testInfo) {
String className = statusMap.get(CLASS_KEY); if (!testInfo.isComplete()) {
String testName = statusMap.get(TEST_KEY); Log.e(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString());
String statusCodeString = statusMap.get(CODE_KEY);
if (className == null || testName == null || statusCodeString == null) {
Log.e(LOG_TAG, "invalid instrumentation status bundle " + statusMap.toString());
return; return;
} }
className = className.trim(); reportTestRunStarted(testInfo);
testName = testName.trim(); TestIdentifier testId = new TestIdentifier(testInfo.mTestClass, testInfo.mTestName);
reportTestStarted(statusMap); switch (testInfo.mCode) {
case StatusCodes.START:
try { mTestListener.testStarted(testId);
int statusCode = Integer.parseInt(statusCodeString); break;
case StatusCodes.FAILURE:
switch (statusCode) { mTestListener.testFailed(ITestRunListener.TestFailure.FAILURE, testId,
case START_STATUS_CODE: getTrace(testInfo));
mTestListener.testStarted(className, testName); mTestListener.testEnded(testId);
break; break;
case FAILURE_STATUS_CODE: case StatusCodes.ERROR:
mTestListener.testFailed(ITestRunListener.STATUS_FAILURE, className, testName, mTestListener.testFailed(ITestRunListener.TestFailure.ERROR, testId,
getTrace(statusMap)); getTrace(testInfo));
mTestListener.testEnded(className, testName); mTestListener.testEnded(testId);
break; break;
case ERROR_STATUS_CODE: case StatusCodes.OK:
mTestListener.testFailed(ITestRunListener.STATUS_ERROR, className, testName, mTestListener.testEnded(testId);
getTrace(statusMap)); break;
mTestListener.testEnded(className, testName); default:
break; Log.e(LOG_TAG, "Unknown status code received: " + testInfo.mCode);
case OK_STATUS_CODE: mTestListener.testEnded(testId);
mTestListener.testEnded(className, testName); break;
break;
default:
Log.e(LOG_TAG, "Expected status code, received: " + statusCodeString);
mTestListener.testEnded(className, testName);
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 * Reports the start of a test run, and the total test count, if it has not been previously
* reported * reported.
* @param statusMap - key-value status pairs *
* @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 start test run not reported yet
if (!mTestStartReported) { if (!mTestStartReported && testInfo.mNumTests != null) {
String numTestsString = statusMap.get(NUMTESTS_KEY); mTestListener.testRunStarted(testInfo.mNumTests);
if (numTestsString != null) { mTestStartReported = true;
try {
int numTests = Integer.parseInt(numTestsString);
mTestListener.testRunStarted(numTests);
mTestStartReported = true;
}
catch (NumberFormatException e) {
Log.e(LOG_TAG, "Unexpected numTests format " + numTestsString);
}
}
} }
} }
/** /**
* 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) { private String getTrace(TestResult testInfo) {
String stackTrace = statusMap.get(STACK_KEY); if (testInfo.mStackTrace != null) {
if (stackTrace != null) { return testInfo.mStackTrace;
return stackTrace;
} }
else { else {
Log.e(LOG_TAG, "Could not find stack trace for failed test "); Log.e(LOG_TAG, "Could not find stack trace for failed test ");
@@ -303,7 +345,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
} }
/** /**
* Parses out and store the elapsed time * Parses out and store the elapsed time.
*/ */
private void parseTime(String line, int startPos) { private void parseTime(String line, int startPos) {
String timeString = line.substring(startPos); String timeString = line.substring(startPos);

View File

@@ -23,7 +23,7 @@ import com.android.ddmlib.Log;
import java.io.IOException; 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 { public class RemoteAndroidTestRunner {
@@ -43,11 +43,12 @@ public class RemoteAndroidTestRunner {
"android.test.InstrumentationTestRunner"; "android.test.InstrumentationTestRunner";
/** /**
* Creates a remote android test runner. * 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 * @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 * runner
* @param remoteDevice - the Android device to execute tests on * @param remoteDevice the Android device to execute tests on
*/ */
public RemoteAndroidTestRunner(String packageName, public RemoteAndroidTestRunner(String packageName,
String runnerName, String runnerName,
@@ -62,9 +63,10 @@ public class RemoteAndroidTestRunner {
} }
/** /**
* Alternate constructor. Uses default instrumentation runner * 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 * @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, public RemoteAndroidTestRunner(String packageName,
IDevice remoteDevice) { IDevice remoteDevice) {
@@ -72,14 +74,14 @@ public class RemoteAndroidTestRunner {
} }
/** /**
* Returns the application package name * Returns the application package name.
*/ */
public String getPackageName() { public String getPackageName() {
return mPackageName; return mPackageName;
} }
/** /**
* Returns the runnerName * Returns the runnerName.
*/ */
public String getRunnerName() { public String getRunnerName() {
if (mRunnerName == null) { if (mRunnerName == null) {
@@ -89,7 +91,7 @@ public class RemoteAndroidTestRunner {
} }
/** /**
* Returns the complete instrumentation component path * Returns the complete instrumentation component path.
*/ */
private String getRunnerPath() { private String getRunnerPath() {
return getPackageName() + RUNNER_SEPARATOR + getRunnerName(); return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
@@ -97,8 +99,9 @@ public class RemoteAndroidTestRunner {
/** /**
* Sets to run only tests in this class * Sets to run only tests in this class
* Must be called before 'run' * Must be called before 'run'.
* @param className - fully qualified class name (eg x.y.z) *
* @param className fully qualified class name (eg x.y.z)
*/ */
public void setClassName(String className) { public void setClassName(String className) {
mClassArg = className; mClassArg = className;
@@ -106,10 +109,12 @@ public class RemoteAndroidTestRunner {
/** /**
* Sets to run only tests in the provided classes * 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 * If providing more than one class, requires a InstrumentationTestRunner that supports
* the multiple class argument syntax * the multiple class argument syntax.
* @param classNames - array of fully qualified class name (eg x.y.z) *
* @param classNames array of fully qualified class names (eg x.y.z)
*/ */
public void setClassNames(String[] classNames) { public void setClassNames(String[] classNames) {
StringBuilder classArgBuilder = new StringBuilder(); StringBuilder classArgBuilder = new StringBuilder();
@@ -125,9 +130,10 @@ public class RemoteAndroidTestRunner {
/** /**
* Sets to run only specified test method * Sets to run only specified test method
* Must be called before 'run' * Must be called before 'run'.
* @param className - fully qualified class name (eg x.y.z) *
* @param testName - method name * @param className fully qualified class name (eg x.y.z)
* @param testName method name
*/ */
public void setMethodName(String className, String testName) { public void setMethodName(String className, String testName) {
mClassArg = className + METHOD_SEPARATOR + testName; mClassArg = className + METHOD_SEPARATOR + testName;
@@ -135,8 +141,9 @@ public class RemoteAndroidTestRunner {
/** /**
* Sets extra arguments to include in instrumentation command. * Sets extra arguments to include in instrumentation command.
* Must be called before 'run' * Must be called before 'run'.
* @param instrumentationArgs - must not be null *
* @param instrumentationArgs must not be null
*/ */
public void setExtraArgs(String instrumentationArgs) { public void setExtraArgs(String instrumentationArgs) {
if (instrumentationArgs == null) { if (instrumentationArgs == null) {
@@ -146,23 +153,23 @@ public class RemoteAndroidTestRunner {
} }
/** /**
* Returns the extra instrumentation arguments * Returns the extra instrumentation arguments.
*/ */
public String getExtraArgs() { public String getExtraArgs() {
return mExtraArgs; 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) { public void setLogOnly(boolean logOnly) {
mLogOnlyMode = 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) { public void run(ITestRunListener listener) {
final String runCaseCommandStr = "am instrument -w -r " final String runCaseCommandStr = "am instrument -w -r "
@@ -179,7 +186,7 @@ public class RemoteAndroidTestRunner {
} }
/** /**
* Requests cancellation of this test run * Requests cancellation of this test run.
*/ */
public void cancel() { public void cancel() {
if (mParser != null) { if (mParser != null) {
@@ -188,7 +195,7 @@ public class RemoteAndroidTestRunner {
} }
/** /**
* Returns the test class argument * Returns the test class argument.
*/ */
private String getClassArg() { private String getClassArg() {
return mClassArg; return mClassArg;
@@ -196,7 +203,7 @@ public class RemoteAndroidTestRunner {
/** /**
* Returns the full instrumentation command which specifies the test classes to execute. * 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() { private String getClassCmd() {
String classArg = getClassArg(); String classArg = getClassArg();
@@ -208,7 +215,7 @@ public class RemoteAndroidTestRunner {
/** /**
* Returns the full command to enable log only mode - if specified. Otherwise returns an * Returns the full command to enable log only mode - if specified. Otherwise returns an
* empty string * empty string.
*/ */
private String getLogCmd() { private String getLogCmd() {
if (mLogOnlyMode) { if (mLogOnlyMode) {

View File

@@ -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();
}
}

View File

@@ -20,7 +20,7 @@ import junit.framework.TestCase;
/** /**
* Tests InstrumentationResultParser * Tests InstrumentationResultParser.
*/ */
public class InstrumentationResultParserTest extends TestCase { public class InstrumentationResultParserTest extends TestCase {
@@ -51,7 +51,7 @@ public class InstrumentationResultParserTest extends TestCase {
/** /**
* Tests that the test run started and test start events is sent on first * Tests that the test run started and test start events is sent on first
* bundle received * bundle received.
*/ */
public void testTestStarted() { public void testTestStarted() {
StringBuilder output = buildCommonResult(); StringBuilder output = buildCommonResult();
@@ -63,7 +63,7 @@ public class InstrumentationResultParserTest extends TestCase {
} }
/** /**
* Tests that a single successful test execution * Tests that a single successful test execution.
*/ */
public void testTestSuccess() { public void testTestSuccess() {
StringBuilder output = buildCommonResult(); StringBuilder output = buildCommonResult();
@@ -74,11 +74,11 @@ public class InstrumentationResultParserTest extends TestCase {
injectTestString(output.toString()); injectTestString(output.toString());
assertCommonAttributes(); assertCommonAttributes();
assertEquals(1, mTestResult.mNumTestsRun); 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() { public void testTestFailed() {
StringBuilder output = buildCommonResult(); StringBuilder output = buildCommonResult();
@@ -91,12 +91,12 @@ public class InstrumentationResultParserTest extends TestCase {
assertCommonAttributes(); assertCommonAttributes();
assertEquals(1, mTestResult.mNumTestsRun); assertEquals(1, mTestResult.mNumTestsRun);
assertEquals(ITestRunListener.STATUS_FAILURE, mTestResult.mTestStatus); assertEquals(ITestRunListener.TestFailure.FAILURE, mTestResult.mTestStatus);
assertEquals(STACK_TRACE, mTestResult.mTrace); 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() { public void testTimeParsing() {
final String timeString = "Time: 4.9"; final String timeString = "Time: 4.9";
@@ -105,7 +105,7 @@ public class InstrumentationResultParserTest extends TestCase {
} }
/** /**
* 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() { private StringBuilder buildCommonResult() {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
@@ -118,7 +118,7 @@ public class InstrumentationResultParserTest extends TestCase {
} }
/** /**
* Adds common status results to the provided output * Adds common status results to the provided output.
*/ */
private void addCommonStatus(StringBuilder output) { private void addCommonStatus(StringBuilder output) {
addStatusKey(output, "stream", "\r\n" + CLASS_NAME); addStatusKey(output, "stream", "\r\n" + CLASS_NAME);
@@ -130,7 +130,7 @@ public class InstrumentationResultParserTest extends TestCase {
} }
/** /**
* Adds a stack trace status bundle to output * Adds a stack trace status bundle to output.
*/ */
private void addStackTrace(StringBuilder output) { private void addStackTrace(StringBuilder output) {
addStatusKey(output, "stack", STACK_TRACE); addStatusKey(output, "stack", STACK_TRACE);
@@ -138,7 +138,7 @@ public class InstrumentationResultParserTest extends TestCase {
} }
/** /**
* 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, private void addStatusKey(StringBuilder outputBuilder, String key,
String value) { String value) {
@@ -168,7 +168,7 @@ public class InstrumentationResultParserTest extends TestCase {
} }
/** /**
* inject a test string into the result parser * inject a test string into the result parser.
* *
* @param result * @param result
*/ */
@@ -185,7 +185,7 @@ public class InstrumentationResultParserTest extends TestCase {
} }
/** /**
* 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 { private class VerifyingTestResult implements ITestRunListener {
@@ -194,29 +194,28 @@ public class InstrumentationResultParserTest extends TestCase {
int mNumTestsRun; int mNumTestsRun;
String mTestName; String mTestName;
long mTestTime; long mTestTime;
int mTestStatus; TestFailure mTestStatus;
String mTrace; String mTrace;
boolean mStopped; boolean mStopped;
VerifyingTestResult() { VerifyingTestResult() {
mNumTestsRun = 0; mNumTestsRun = 0;
mTestStatus = 0; mTestStatus = null;
mStopped = false; mStopped = false;
} }
public void testEnded(String className, String testName) { public void testEnded(TestIdentifier test) {
mNumTestsRun++; mNumTestsRun++;
assertEquals("Unexpected class name", mSuiteName, className); assertEquals("Unexpected class name", mSuiteName, test.getClassName());
assertEquals("Unexpected test ended", mTestName, testName); assertEquals("Unexpected test ended", mTestName, test.getTestName());
} }
public void testFailed(int status, String className, String testName, public void testFailed(TestFailure status, TestIdentifier test, String trace) {
String trace) {
mTestStatus = status; mTestStatus = status;
mTrace = trace; mTrace = trace;
assertEquals("Unexpected class name", mSuiteName, className); assertEquals("Unexpected class name", mSuiteName, test.getClassName());
assertEquals("Unexpected test ended", mTestName, testName); assertEquals("Unexpected test ended", mTestName, test.getTestName());
} }
public void testRunEnded(long elapsedTime) { public void testRunEnded(long elapsedTime) {
@@ -233,9 +232,9 @@ public class InstrumentationResultParserTest extends TestCase {
mStopped = true; mStopped = true;
} }
public void testStarted(String className, String testName) { public void testStarted(TestIdentifier test) {
mSuiteName = className; mSuiteName = test.getClassName();
mTestName = testName; mTestName = test.getTestName();
} }
public void testRunFailed(String errorMessage) { public void testRunFailed(String errorMessage) {

View File

@@ -31,16 +31,16 @@ import java.io.IOException;
import java.util.Map; import java.util.Map;
/** /**
* Test RemoteAndroidTestRunner. * Tests RemoteAndroidTestRunner.
*/ */
public class RemoteAndroidTestRunnerTest extends TestCase { public class RemoteAndroidTestRunnerTest extends TestCase {
private RemoteAndroidTestRunner mRunner; private RemoteAndroidTestRunner mRunner;
private MockDevice mMockDevice; private MockDevice mMockDevice;
private static final String TEST_PACKAGE = "com.test"; private static final String TEST_PACKAGE = "com.test";
private static final String TEST_RUNNER = "com.test.InstrumentationTestRunner"; private static final String TEST_RUNNER = "com.test.InstrumentationTestRunner";
/** /**
* @see junit.framework.TestCase#setUp() * @see junit.framework.TestCase#setUp()
*/ */
@@ -49,40 +49,40 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
mMockDevice = new MockDevice(); mMockDevice = new MockDevice();
mRunner = new RemoteAndroidTestRunner(TEST_PACKAGE, TEST_RUNNER, mMockDevice); 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() { public void testRun() {
mRunner.run(new EmptyListener()); 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()); 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() { public void testRunWithLog() {
mRunner.setLogOnly(true); mRunner.setLogOnly(true);
mRunner.run(new EmptyListener()); 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_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() { public void testRunWithMethod() {
final String className = "FooTest"; final String className = "FooTest";
final String testName = "fooTest"; final String testName = "fooTest";
mRunner.setMethodName(className, testName); mRunner.setMethodName(className, testName);
mRunner.run(new EmptyListener()); 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()); 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() { public void testRunWithExtraArgs() {
final String extraArgs = "blah"; final String extraArgs = "blah";
@@ -94,37 +94,37 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
/** /**
* Assert two strings are equal ignoring whitespace * Assert two strings are equal ignoring whitespace.
*/ */
private void assertStringsEquals(String str1, String str2) { private void assertStringsEquals(String str1, String str2) {
String strippedStr1 = str1.replaceAll(" ", ""); String strippedStr1 = str1.replaceAll(" ", "");
String strippedStr2 = str2.replaceAll(" ", ""); String strippedStr2 = str2.replaceAll(" ", "");
assertEquals(strippedStr1, strippedStr2); assertEquals(strippedStr1, strippedStr2);
} }
/** /**
* A dummy device that does nothing except store the provided executed shell command for * A dummy device that does nothing except store the provided executed shell command for
* later retrieval * later retrieval.
*/ */
private static class MockDevice implements IDevice { private static class MockDevice implements IDevice {
private String mLastShellCommand; 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, public void executeShellCommand(String command,
IShellOutputReceiver receiver) throws IOException { IShellOutputReceiver receiver) throws IOException {
mLastShellCommand = command; mLastShellCommand = command;
} }
/** /**
* Get the last command provided to executeShellCommand * Get the last command provided to executeShellCommand.
*/ */
public String getLastShellCommand() { public String getLastShellCommand() {
return mLastShellCommand; return mLastShellCommand;
} }
public boolean createForward(int localPort, int remotePort) { public boolean createForward(int localPort, int remotePort) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@@ -201,22 +201,26 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public void runLogService(String logname, LogReceiver receiver) throws IOException {
throw new UnsupportedOperationException();
}
public String getAvdName() { public String getAvdName() {
return ""; return "";
} }
} }
/** An empty implementation of TestRunListener /**
* An empty implementation of ITestRunListener.
*/ */
private static class EmptyListener implements ITestRunListener { private static class EmptyListener implements ITestRunListener {
public void testEnded(String className, String testName) { public void testEnded(TestIdentifier test) {
// ignore // ignore
} }
public void testFailed(int status, String className, String testName, public void testFailed(TestFailure status, TestIdentifier test, String trace) {
String trace) {
// ignore // ignore
} }
@@ -236,9 +240,9 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
// ignore // ignore
} }
public void testStarted(String className, String testName) { public void testStarted(TestIdentifier test) {
// ignore // ignore
} }
} }
} }

View File

@@ -237,7 +237,7 @@ public final class NativeHeapPanel extends BaseHeapPanel {
*/ */
private HashMap<Long, NativeStackCallInfo> mSourceCache = private HashMap<Long, NativeStackCallInfo> mSourceCache =
new HashMap<Long,NativeStackCallInfo>(); new HashMap<Long,NativeStackCallInfo>();
private int mTotalSize; private long mTotalSize;
private Button mSaveButton; private Button mSaveButton;
private Button mSymbolsButton; private Button mSymbolsButton;

View File

@@ -20,9 +20,9 @@ rem Set up prog to be the path of this script, including following symlinks,
rem and set up progdir to be the fully-qualified pathname of its directory. rem and set up progdir to be the fully-qualified pathname of its directory.
set prog=%~f0 set prog=%~f0
rem Change current directory to where ddms is, to avoid issues with directories rem Change current directory and drive to where the script is, to avoid
rem containing whitespaces. rem issues with directories containing whitespaces.
cd %~dp0 cd /d %~dp0
set jarfile=draw9patch.jar set jarfile=draw9patch.jar
set frameworkdir= set frameworkdir=

View File

@@ -1,14 +1,18 @@
0.9.0 (work in progress) 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: 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. - 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 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. - 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. - Undo/redo support in all XML form editors and Graphical layout editor.
0.8.0: 0.8.0:

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -17,6 +17,14 @@
<super type="org.eclipse.core.resources.textmarker"/> <super type="org.eclipse.core.resources.textmarker"/>
<persistent value="true"/> <persistent value="true"/>
</extension> </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 <extension
id="com.android.ide.eclipse.common.aidlProblem" id="com.android.ide.eclipse.common.aidlProblem"
name="Android AIDL Problem" name="Android AIDL Problem"
@@ -464,4 +472,31 @@
</enabledWhen> </enabledWhen>
</page> </page>
</extension> </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> </plugin>

View File

@@ -28,6 +28,7 @@ import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerIni
import com.android.ide.eclipse.adt.sdk.AndroidTargetParser; import com.android.ide.eclipse.adt.sdk.AndroidTargetParser;
import com.android.ide.eclipse.adt.sdk.LoadStatus; import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk; 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.AndroidConstants;
import com.android.ide.eclipse.common.EclipseUiHelper; import com.android.ide.eclipse.common.EclipseUiHelper;
import com.android.ide.eclipse.common.SdkStatsHelper; import com.android.ide.eclipse.common.SdkStatsHelper;
@@ -173,7 +174,8 @@ public class AdtPlugin extends AbstractUIPlugin {
private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>(); private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>();
private ResourceMonitor mResourceMonitor; 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 * Custom PrintStream for Dx output. This class overrides the method
@@ -861,7 +863,6 @@ public class AdtPlugin extends AbstractUIPlugin {
/** /**
* Returns the lock object for SDK loading. If you wish to do things while the SDK is loading, * Returns the lock object for SDK loading. If you wish to do things while the SDK is loading,
* you must synchronize on this object. * you must synchronize on this object.
* @return
*/ */
public final Object getSdkLockObject() { public final Object getSdkLockObject() {
return mPostLoadProjectsToResolve; return mPostLoadProjectsToResolve;
@@ -986,7 +987,7 @@ public class AdtPlugin extends AbstractUIPlugin {
Constants.BUNDLE_VERSION); Constants.BUNDLE_VERSION);
Version version = new Version(versionString); Version version = new Version(versionString);
SdkStatsHelper.pingUsageServer("editors", version); //$NON-NLS-1$ SdkStatsHelper.pingUsageServer("adt", version); //$NON-NLS-1$
return Status.OK_STATUS; return Status.OK_STATUS;
} catch (Throwable t) { } catch (Throwable t) {
@@ -1019,24 +1020,27 @@ public class AdtPlugin extends AbstractUIPlugin {
progress.setTaskName(Messages.AdtPlugin_Parsing_Resources); progress.setTaskName(Messages.AdtPlugin_Parsing_Resources);
for (IAndroidTarget target : sdk.getTargets()) { int n = sdk.getTargets().length;
IStatus status = new AndroidTargetParser(target).run(progress); if (n > 0) {
if (status.getCode() != IStatus.OK) { int w = 60 / n;
synchronized (getSdkLockObject()) { for (IAndroidTarget target : sdk.getTargets()) {
mSdkIsLoaded = LoadStatus.FAILED; SubMonitor p2 = progress.newChild(w);
mPostLoadProjectsToResolve.clear(); 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()) { synchronized (getSdkLockObject()) {
mSdkIsLoaded = LoadStatus.LOADED; mSdkIsLoaded = LoadStatus.LOADED;
progress.setTaskName("Check Projects");
// check the projects that need checking. // check the projects that need checking.
// The method modifies the list (it removes the project that // The method modifies the list (it removes the project that
// do not need to be resolved again). // do not need to be resolved again).
@@ -1052,25 +1056,33 @@ public class AdtPlugin extends AbstractUIPlugin {
AndroidClasspathContainerInitializer.updateProjects(array); AndroidClasspathContainerInitializer.updateProjects(array);
mPostLoadProjectsToResolve.clear(); mPostLoadProjectsToResolve.clear();
} }
progress.worked(10);
} }
} }
// Notify resource changed listeners // Notify resource changed listeners
progress.subTask("Refresh UI"); progress.setTaskName("Refresh UI");
progress.setWorkRemaining(mResourceRefreshListener.size()); progress.setWorkRemaining(mTargetChangeListeners.size());
// Clone the list before iterating, to avoid Concurrent Modification // Clone the list before iterating, to avoid Concurrent Modification
// exceptions // exceptions
List<Runnable> listeners = (List<Runnable>)mResourceRefreshListener.clone(); final List<ITargetChangeListener> listeners =
for (Runnable listener : listeners) { (List<ITargetChangeListener>)mTargetChangeListeners.clone();
try { final SubMonitor progress2 = progress;
AdtPlugin.getDisplay().syncExec(listener); AdtPlugin.getDisplay().syncExec(new Runnable() {
} catch (Exception e) { public void run() {
AdtPlugin.log(e, "ResourceRefreshListener Failed"); //$NON-NLS-1$ for (ITargetChangeListener listener : listeners) {
} finally { try {
progress.worked(1); listener.onTargetsLoaded();
} catch (Exception e) {
AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$
} finally {
progress2.worked(1);
}
}
} }
} });
} finally { } finally {
if (monitor != null) { if (monitor != null) {
monitor.done(); monitor.done();
@@ -1316,12 +1328,42 @@ public class AdtPlugin extends AbstractUIPlugin {
}, IResourceDelta.ADDED | IResourceDelta.CHANGED); }, 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() { public static synchronized OutputStream getErrorStream() {

View File

@@ -203,6 +203,9 @@ public class ApkBuilder extends BaseBuilder {
// get a project object // get a project object
IProject project = getProject(); IProject project = getProject();
// Top level check to make sure the build can move forward.
abortOnBadSetup(project);
// get the list of referenced projects. // get the list of referenced projects.
IProject[] referencedProjects = ProjectHelper.getReferencedProjects(project); IProject[] referencedProjects = ProjectHelper.getReferencedProjects(project);
IJavaProject[] referencedJavaProjects = getJavaProjects(referencedProjects); IJavaProject[] referencedJavaProjects = getJavaProjects(referencedProjects);
@@ -262,69 +265,12 @@ public class ApkBuilder extends BaseBuilder {
} }
} }
} }
// 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 // store the build status in the persistent storage
saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex); saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex);
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources); saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage); 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) { if (dv != null && dv.mXmlError) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.Xml_Error); Messages.Xml_Error);
@@ -351,6 +297,82 @@ public class ApkBuilder extends BaseBuilder {
return referencedProjects; 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 // 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 // or the dex file, but we don't know if we simply need to recreate them
// because they are missing // because they are missing
@@ -396,6 +418,9 @@ public class ApkBuilder extends BaseBuilder {
// first we check if we need to package the resources. // first we check if we need to package the resources.
if (mPackageResources) { 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; // need to figure out some path before we can execute aapt;
// resource to the AndroidManifest.xml file // resource to the AndroidManifest.xml file
@@ -552,7 +577,8 @@ public class ApkBuilder extends BaseBuilder {
* @param osAssetsPath The path to the assets folder. This can be null. * @param osAssetsPath The path to the assets folder. This can be null.
* @param osOutFilePath The path to the temporary resource file to create. * @param osOutFilePath The path to the temporary resource file to create.
* @param configFilter The configuration filter for the resources to include * @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. * @return true if success, false otherwise.
*/ */
private boolean executeAapt(IProject project, String osManifestPath, private boolean executeAapt(IProject project, String osManifestPath,

View File

@@ -149,6 +149,15 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
private final static Pattern sPattern8Line1 = Pattern.compile( private final static Pattern sPattern8Line1 = Pattern.compile(
"^(invalid resource directory name): (.*)$"); //$NON-NLS-1$ "^(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. */ /** SAX Parser factory. */
private SAXParserFactory mParserFactory; private SAXParserFactory mParserFactory;
@@ -440,8 +449,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
String location = m.group(1); String location = m.group(1);
// check the values and attempt to mark the file. // check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, if (checkAndMark(location, lineStr, msg, osRoot, project,
project, IMarker.SEVERITY_ERROR) == false) { AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true; return true;
} }
continue; continue;
@@ -465,7 +474,7 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
// display the error // display the error
if (checkAndMark(location, null, msg, osRoot, project, if (checkAndMark(location, null, msg, osRoot, project,
IMarker.SEVERITY_ERROR) == false) { AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true; return true;
} }
@@ -488,8 +497,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
String lineStr = m.group(2); String lineStr = m.group(2);
// check the values and attempt to mark the file. // check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, if (checkAndMark(location, lineStr, msg, osRoot, project,
project, IMarker.SEVERITY_ERROR) == false) { AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true; return true;
} }
continue; continue;
@@ -502,8 +511,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
String msg = m.group(3); String msg = m.group(3);
// check the values and attempt to mark the file. // check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, if (checkAndMark(location, lineStr, msg, osRoot, project,
project, IMarker.SEVERITY_ERROR) == false) { AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true; return true;
} }
@@ -526,8 +535,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
String lineStr = m.group(2); String lineStr = m.group(2);
// check the values and attempt to mark the file. // check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, if (checkAndMark(location, lineStr, msg, osRoot, project,
project, IMarker.SEVERITY_ERROR) == false) { AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true; return true;
} }
@@ -542,8 +551,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
String msg = m.group(3); String msg = m.group(3);
// check the values and attempt to mark the file. // check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, if (checkAndMark(location, lineStr, msg, osRoot, project,
project,IMarker.SEVERITY_WARNING) == false) { AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) {
return true; return true;
} }
@@ -558,8 +567,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
String msg = m.group(3); String msg = m.group(3);
// check the values and attempt to mark the file. // check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, if (checkAndMark(location, lineStr, msg, osRoot, project,
project, IMarker.SEVERITY_ERROR) == false) { AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true; return true;
} }
@@ -574,7 +583,25 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
// check the values and attempt to mark the file. // check the values and attempt to mark the file.
if (checkAndMark(location, null, msg, osRoot, project, 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; return true;
} }
@@ -659,23 +686,25 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
/** /**
* Check if the parameters gotten from the error output are valid, and mark * Check if the parameters gotten from the error output are valid, and mark
* the file with an AAPT marker. * 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 lineStr
* @param message * @param message
* @param root The root directory of the project, in OS specific format. * @param root The root directory of the project, in OS specific format.
* @param project * @param project
* @param markerId The marker id to put.
* @param severity The severity of the marker to put (IMarker.SEVERITY_*) * @param severity The severity of the marker to put (IMarker.SEVERITY_*)
* @return true if the parameters were valid and the file was marked * @return true if the parameters were valid and the file was marked successfully.
* sucessfully.
* *
* @see IMarker * @see IMarker
*/ */
private final boolean checkAndMark(String location, String lineStr, 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 // check this is in fact a file
File f = new File(location); if (location != null) {
if (f.exists() == false) { File f = new File(location);
return false; if (f.exists() == false) {
return false;
}
} }
// get the line number // get the line number
@@ -692,16 +721,18 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
} }
// add the marker // add the marker
IResource f2 = getResourceFromFullPath(location, root, project); IResource f2 = project;
if (f2 == null) { if (location != null) {
return false; f2 = getResourceFromFullPath(location, root, project);
if (f2 == null) {
return false;
}
} }
// check if there's a similar marker already, since aapt is launched twice // check if there's a similar marker already, since aapt is launched twice
boolean markerAlreadyExists = false; boolean markerAlreadyExists = false;
try { try {
IMarker[] markers = f2.findMarkers(AndroidConstants.MARKER_AAPT, true, IMarker[] markers = f2.findMarkers(markerId, true, IResource.DEPTH_ZERO);
IResource.DEPTH_ZERO);
for (IMarker marker : markers) { for (IMarker marker : markers) {
int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1); int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1);
@@ -732,10 +763,10 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
if (markerAlreadyExists == false) { if (markerAlreadyExists == false) {
if (line != -1) { if (line != -1) {
BaseProjectHelper.addMarker(f2, AndroidConstants.MARKER_AAPT, message, line, BaseProjectHelper.addMarker(f2, markerId, message, line,
severity); severity);
} else { } else {
BaseProjectHelper.addMarker(f2, AndroidConstants.MARKER_AAPT, message, severity); BaseProjectHelper.addMarker(f2, markerId, message, severity);
} }
} }
@@ -851,7 +882,7 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
* Aborts the build if the SDK/project setups are broken. This does not * Aborts the build if the SDK/project setups are broken. This does not
* display any errors. * display any errors.
* *
* @param javaProject The {@link IJavaProject} being compiled. * @param project The {@link IJavaProject} being compiled.
* @throws CoreException * @throws CoreException
*/ */
protected final void abortOnBadSetup(IProject project) throws CoreException { protected final void abortOnBadSetup(IProject project) throws CoreException {

View File

@@ -57,8 +57,11 @@ public final class DexWrapper {
private Field mConsoleErr; private Field mConsoleErr;
/** /**
* Loads the dex library from a file path. The loaded library can be used with the * Loads the dex library from a file path.
* {@link DexWrapper} object returned by {@link #getWrapper()} *
* The loaded library can be used via
* {@link DexWrapper#run(String, String[], boolean, PrintStream, PrintStream)}.
*
* @param osFilepath the location of the dex.jar file. * @param osFilepath the location of the dex.jar file.
* @return an IStatus indicating the result of the load. * @return an IStatus indicating the result of the load.
*/ */

View File

@@ -234,6 +234,10 @@ public class PreCompilerBuilder extends BaseBuilder {
// get the project objects // get the project objects
IProject project = getProject(); IProject project = getProject();
// Top level check to make sure the build can move forward.
abortOnBadSetup(project);
IJavaProject javaProject = JavaCore.create(project); IJavaProject javaProject = JavaCore.create(project);
IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
@@ -284,10 +288,6 @@ public class PreCompilerBuilder extends BaseBuilder {
saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mCompileResources); saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mCompileResources);
// TODO also needs to store the list of aidl to compile/remove // 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 // if there was some XML errors, we just return w/o doing
// anything since we've put some markers in the files anyway. // anything since we've put some markers in the files anyway.
if (dv != null && dv.mXmlError) { if (dv != null && dv.mXmlError) {
@@ -381,7 +381,7 @@ public class PreCompilerBuilder extends BaseBuilder {
// mark the manifest file // mark the manifest file
String message = String.format(Messages.Package_s_Doesnt_Exist_Error, String message = String.format(Messages.Package_s_Doesnt_Exist_Error,
mManifestPackage); mManifestPackage);
BaseProjectHelper.addMarker(manifest, AndroidConstants.MARKER_AAPT, message, BaseProjectHelper.addMarker(manifest, AndroidConstants.MARKER_AAPT_COMPILE, message,
IMarker.SEVERITY_ERROR); IMarker.SEVERITY_ERROR);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, message); AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, message);
@@ -409,8 +409,8 @@ public class PreCompilerBuilder extends BaseBuilder {
String osManifestPath = manifestLocation.toOSString(); String osManifestPath = manifestLocation.toOSString();
// remove the aapt markers // remove the aapt markers
removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT); removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT_COMPILE);
removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT); removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT_COMPILE);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.Preparing_Generated_Files); Messages.Preparing_Generated_Files);

View File

@@ -17,11 +17,19 @@
package com.android.ide.eclipse.adt.preferences; package com.android.ide.eclipse.adt.preferences;
import com.android.ide.eclipse.adt.AdtPlugin; 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.DirectoryFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage; import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.resource.JFaceResources; 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.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage; import org.eclipse.ui.IWorkbenchPreferencePage;
@@ -80,6 +88,9 @@ public class AndroidPreferencePage extends FieldEditorPreferencePage implements
*/ */
private static class SdkDirectoryFieldEditor extends DirectoryFieldEditor { private static class SdkDirectoryFieldEditor extends DirectoryFieldEditor {
private SdkTargetSelector mTargetSelector;
private TargetChangedListener mTargetChangeListener;
public SdkDirectoryFieldEditor(String name, String labelText, Composite parent) { public SdkDirectoryFieldEditor(String name, String labelText, Composite parent) {
super(name, labelText, parent); super(name, labelText, parent);
setEmptyStringAllowed(false); setEmptyStringAllowed(false);
@@ -131,5 +142,68 @@ public class AndroidPreferencePage extends FieldEditorPreferencePage implements
setValidateStrategy(VALIDATE_ON_KEY_STROKE); setValidateStrategy(VALIDATE_ON_KEY_STROKE);
return super.getTextControl(parent); 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);
}
}
}
} }
} }

View File

@@ -266,7 +266,6 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
// We schedule a new job to put the marker after. // We schedule a new job to put the marker after.
final String fmessage = markerMessage; final String fmessage = markerMessage;
Job markerJob = new Job("Android SDK: Resolving error markers") { Job markerJob = new Job("Android SDK: Resolving error markers") {
@SuppressWarnings("unchecked")
@Override @Override
protected IStatus run(IProgressMonitor monitor) { protected IStatus run(IProgressMonitor monitor) {
try { try {
@@ -296,7 +295,6 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
// In some cases, the workspace may be locked for modification when we pass // 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. // here, so we schedule a new job to put the marker after.
Job markerJob = new Job("Android SDK: Resolving error markers") { Job markerJob = new Job("Android SDK: Resolving error markers") {
@SuppressWarnings("unchecked")
@Override @Override
protected IStatus run(IProgressMonitor monitor) { protected IStatus run(IProgressMonitor monitor) {
try { try {

View File

@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.project.properties;
import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
import com.android.sdkuilib.ApkConfigWidget;
import com.android.sdkuilib.SdkTargetSelector; import com.android.sdkuilib.SdkTargetSelector;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
@@ -32,6 +33,8 @@ import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IWorkbenchPropertyPage; import org.eclipse.ui.IWorkbenchPropertyPage;
import org.eclipse.ui.dialogs.PropertyPage; import org.eclipse.ui.dialogs.PropertyPage;
import java.util.Map;
/** /**
* Property page for "Android" project. * Property page for "Android" project.
* This is accessible from the Package Explorer when right clicking a project and choosing * This is accessible from the Package Explorer when right clicking a project and choosing
@@ -42,6 +45,7 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
private IProject mProject; private IProject mProject;
private SdkTargetSelector mSelector; private SdkTargetSelector mSelector;
private ApkConfigWidget mApkConfigWidget;
public AndroidPropertyPage() { public AndroidPropertyPage() {
// pass // pass
@@ -51,7 +55,14 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
protected Control createContents(Composite parent) { protected Control createContents(Composite parent) {
// get the element (this is not yet valid in the constructor). // get the element (this is not yet valid in the constructor).
mProject = (IProject)getElement(); 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); Composite top = new Composite(parent, SWT.NONE);
top.setLayoutData(new GridData(GridData.FILL_BOTH)); top.setLayoutData(new GridData(GridData.FILL_BOTH));
top.setLayout(new GridLayout(1, false)); top.setLayout(new GridLayout(1, false));
@@ -59,20 +70,28 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
Label l = new Label(top, SWT.NONE); Label l = new Label(top, SWT.NONE);
l.setText("Project Target"); 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*/); mSelector = new SdkTargetSelector(top, targets, false /*allowMultipleSelection*/);
if (Sdk.getCurrent() != null) { l = new Label(top, SWT.SEPARATOR | SWT.HORIZONTAL);
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); 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) { if (target != null) {
mSelector.setSelection(target); mSelector.setSelection(target);
} }
// get the apk configurations
Map<String, String> configs = currentSdk.getProjectApkConfigs(mProject);
mApkConfigWidget.fillTable(configs);
} }
mSelector.setSelectionListener(new SelectionAdapter() { mSelector.setSelectionListener(new SelectionAdapter() {
@@ -83,14 +102,20 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
setValid(target != null); setValid(target != null);
} }
}); });
if (mProject.isOpen() == false) {
// disable the ui.
}
return top; return top;
} }
@Override @Override
public boolean performOk() { public boolean performOk() {
if (Sdk.getCurrent() != null) { Sdk currentSdk = Sdk.getCurrent();
Sdk.getCurrent().setProject(mProject, mSelector.getFirstSelected()); if (currentSdk != null) {
currentSdk.setProject(mProject, mSelector.getFirstSelected(),
mApkConfigWidget.getApkConfigs());
} }
return true; return true;

View File

@@ -27,6 +27,7 @@ import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors; import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors;
import com.android.layoutlib.api.ILayoutBridge; import com.android.layoutlib.api.ILayoutBridge;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Map; import java.util.Map;
@@ -43,6 +44,7 @@ public class AndroidTargetData {
public final static int DESCRIPTOR_RESOURCES = 5; public final static int DESCRIPTOR_RESOURCES = 5;
public final static int DESCRIPTOR_SEARCHABLE = 6; public final static int DESCRIPTOR_SEARCHABLE = 6;
public final static int DESCRIPTOR_PREFERENCES = 7; public final static int DESCRIPTOR_PREFERENCES = 7;
public final static int DESCRIPTOR_GADGET_PROVIDER = 8;
public final static class LayoutBridge { public final static class LayoutBridge {
/** Link to the layout bridge */ /** Link to the layout bridge */
@@ -51,6 +53,8 @@ public class AndroidTargetData {
public LoadStatus status = LoadStatus.LOADING; public LoadStatus status = LoadStatus.LOADING;
public ClassLoader classLoader; public ClassLoader classLoader;
public int apiLevel;
} }
private final IAndroidTarget mTarget; private final IAndroidTarget mTarget;
@@ -93,6 +97,7 @@ public class AndroidTargetData {
/** /**
* Creates an AndroidTargetData object. * Creates an AndroidTargetData object.
* @param optionalLibraries
*/ */
void setExtraData(IResourceRepository systemResourceRepository, void setExtraData(IResourceRepository systemResourceRepository,
AndroidManifestDescriptors manifestDescriptors, AndroidManifestDescriptors manifestDescriptors,
@@ -105,6 +110,7 @@ public class AndroidTargetData {
String[] broadcastIntentActionValues, String[] broadcastIntentActionValues,
String[] serviceIntentActionValues, String[] serviceIntentActionValues,
String[] intentCategoryValues, String[] intentCategoryValues,
IOptionalLibrary[] optionalLibraries,
ProjectResources resources, ProjectResources resources,
LayoutBridge layoutBridge) { LayoutBridge layoutBridge) {
@@ -120,8 +126,9 @@ public class AndroidTargetData {
setPermissions(permissionValues); setPermissions(permissionValues);
setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues, setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues,
serviceIntentActionValues, intentCategoryValues); serviceIntentActionValues, intentCategoryValues);
setOptionalLibraries(optionalLibraries);
} }
public DexWrapper getDexWrapper() { public DexWrapper getDexWrapper() {
return mDexWrapper; return mDexWrapper;
} }
@@ -151,6 +158,8 @@ public class AndroidTargetData {
return ResourcesDescriptors.getInstance(); return ResourcesDescriptors.getInstance();
case DESCRIPTOR_PREFERENCES: case DESCRIPTOR_PREFERENCES:
return mXmlDescriptors.getPreferencesProvider(); return mXmlDescriptors.getPreferencesProvider();
case DESCRIPTOR_GADGET_PROVIDER:
return mXmlDescriptors.getGadgetProvider();
case DESCRIPTOR_SEARCHABLE: case DESCRIPTOR_SEARCHABLE:
return mXmlDescriptors.getSearchableProvider(); return mXmlDescriptors.getSearchableProvider();
default : default :
@@ -283,6 +292,20 @@ public class AndroidTargetData {
setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$ setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$
setValues("(category,android:name)", intentCategoryValues); //$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. * Sets a (name, values) pair in the hash map.

View File

@@ -92,7 +92,7 @@ public final class AndroidTargetParser {
try { try {
SubMonitor progress = SubMonitor.convert(monitor, SubMonitor progress = SubMonitor.convert(monitor,
String.format("Parsing SDK %1$s", mAndroidTarget.getName()), String.format("Parsing SDK %1$s", mAndroidTarget.getName()),
200); 14);
AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget); AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget);
@@ -107,15 +107,14 @@ public final class AndroidTargetParser {
// we have loaded dx. // we have loaded dx.
targetData.setDexWrapper(dexWrapper); targetData.setDexWrapper(dexWrapper);
progress.worked(1);
// parse the rest of the data. // parse the rest of the data.
progress.setWorkRemaining(120);
AndroidJarLoader classLoader = AndroidJarLoader classLoader =
new AndroidJarLoader(mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR)); new AndroidJarLoader(mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
preload(classLoader, progress.newChild(40, SubMonitor.SUPPRESS_NONE)); preload(classLoader, progress.newChild(40, SubMonitor.SUPPRESS_NONE));
progress.setWorkRemaining(80);
if (progress.isCanceled()) { if (progress.isCanceled()) {
return Status.CANCEL_STATUS; return Status.CANCEL_STATUS;
@@ -124,7 +123,7 @@ public final class AndroidTargetParser {
// get the resource Ids. // get the resource Ids.
progress.subTask("Resource IDs"); progress.subTask("Resource IDs");
IResourceRepository frameworkRepository = collectResourceIds(classLoader); IResourceRepository frameworkRepository = collectResourceIds(classLoader);
progress.worked(5); progress.worked(1);
if (progress.isCanceled()) { if (progress.isCanceled()) {
return Status.CANCEL_STATUS; return Status.CANCEL_STATUS;
@@ -133,7 +132,7 @@ public final class AndroidTargetParser {
// get the permissions // get the permissions
progress.subTask("Permissions"); progress.subTask("Permissions");
String[] permissionValues = collectPermissions(classLoader); String[] permissionValues = collectPermissions(classLoader);
progress.worked(5); progress.worked(1);
if (progress.isCanceled()) { if (progress.isCanceled()) {
return Status.CANCEL_STATUS; return Status.CANCEL_STATUS;
@@ -147,7 +146,7 @@ public final class AndroidTargetParser {
ArrayList<String> categories = new ArrayList<String>(); ArrayList<String> categories = new ArrayList<String>();
collectIntentFilterActionsAndCategories(activity_actions, broadcast_actions, collectIntentFilterActionsAndCategories(activity_actions, broadcast_actions,
service_actions, categories); service_actions, categories);
progress.worked(5); progress.worked(1);
if (progress.isCanceled()) { if (progress.isCanceled()) {
return Status.CANCEL_STATUS; return Status.CANCEL_STATUS;
@@ -158,12 +157,14 @@ public final class AndroidTargetParser {
AttrsXmlParser attrsXmlParser = new AttrsXmlParser( AttrsXmlParser attrsXmlParser = new AttrsXmlParser(
mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES)); mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES));
attrsXmlParser.preload(); attrsXmlParser.preload();
progress.worked(1);
progress.subTask("Manifest definitions"); progress.subTask("Manifest definitions");
AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser( AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser(
mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES), mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES),
attrsXmlParser); attrsXmlParser);
attrsManifestXmlParser.preload(); attrsManifestXmlParser.preload();
progress.worked(1);
Collection<ViewClassInfo> mainList = new ArrayList<ViewClassInfo>(); Collection<ViewClassInfo> mainList = new ArrayList<ViewClassInfo>();
Collection<ViewClassInfo> groupList = new ArrayList<ViewClassInfo>(); Collection<ViewClassInfo> groupList = new ArrayList<ViewClassInfo>();
@@ -171,7 +172,7 @@ public final class AndroidTargetParser {
// collect the layout/widgets classes // collect the layout/widgets classes
progress.subTask("Widgets and layouts"); progress.subTask("Widgets and layouts");
collectLayoutClasses(classLoader, attrsXmlParser, mainList, groupList, collectLayoutClasses(classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(40)); progress.newChild(1));
if (progress.isCanceled()) { if (progress.isCanceled()) {
return Status.CANCEL_STATUS; return Status.CANCEL_STATUS;
@@ -185,7 +186,7 @@ public final class AndroidTargetParser {
mainList.clear(); mainList.clear();
groupList.clear(); groupList.clear();
collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList, collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(5)); progress.newChild(1));
if (progress.isCanceled()) { if (progress.isCanceled()) {
return Status.CANCEL_STATUS; return Status.CANCEL_STATUS;
@@ -202,6 +203,11 @@ public final class AndroidTargetParser {
attrsManifestXmlParser); attrsManifestXmlParser);
Map<String, Map<String, Integer>> enumValueMap = attrsXmlParser.getEnumFlagValues(); Map<String, Map<String, Integer>> enumValueMap = attrsXmlParser.getEnumFlagValues();
Map<String, DeclareStyleableInfo> xmlGadgetMap = null;
if (mAndroidTarget.getApiVersionNumber() >= 3) {
xmlGadgetMap = collectGadgetDefinitions(attrsXmlParser);
}
if (progress.isCanceled()) { if (progress.isCanceled()) {
return Status.CANCEL_STATUS; return Status.CANCEL_STATUS;
} }
@@ -210,7 +216,7 @@ public final class AndroidTargetParser {
// the PlatformData object. // the PlatformData object.
AndroidManifestDescriptors manifestDescriptors = new AndroidManifestDescriptors(); AndroidManifestDescriptors manifestDescriptors = new AndroidManifestDescriptors();
manifestDescriptors.updateDescriptors(manifestMap); manifestDescriptors.updateDescriptors(manifestMap);
progress.worked(10); progress.worked(1);
if (progress.isCanceled()) { if (progress.isCanceled()) {
return Status.CANCEL_STATUS; return Status.CANCEL_STATUS;
@@ -218,7 +224,7 @@ public final class AndroidTargetParser {
LayoutDescriptors layoutDescriptors = new LayoutDescriptors(); LayoutDescriptors layoutDescriptors = new LayoutDescriptors();
layoutDescriptors.updateDescriptors(layoutViewsInfo, layoutGroupsInfo); layoutDescriptors.updateDescriptors(layoutViewsInfo, layoutGroupsInfo);
progress.worked(10); progress.worked(1);
if (progress.isCanceled()) { if (progress.isCanceled()) {
return Status.CANCEL_STATUS; return Status.CANCEL_STATUS;
@@ -226,25 +232,28 @@ public final class AndroidTargetParser {
MenuDescriptors menuDescriptors = new MenuDescriptors(); MenuDescriptors menuDescriptors = new MenuDescriptors();
menuDescriptors.updateDescriptors(xmlMenuMap); menuDescriptors.updateDescriptors(xmlMenuMap);
progress.worked(10); progress.worked(1);
if (progress.isCanceled()) { if (progress.isCanceled()) {
return Status.CANCEL_STATUS; return Status.CANCEL_STATUS;
} }
XmlDescriptors xmlDescriptors = new XmlDescriptors(); XmlDescriptors xmlDescriptors = new XmlDescriptors();
xmlDescriptors.updateDescriptors(xmlSearchableMap, preferencesInfo, xmlDescriptors.updateDescriptors(
xmlSearchableMap,
xmlGadgetMap,
preferencesInfo,
preferenceGroupsInfo); preferenceGroupsInfo);
progress.worked(10); progress.worked(1);
// load the framework resources. // load the framework resources.
ProjectResources resources = ResourceManager.getInstance().loadFrameworkResources( ProjectResources resources = ResourceManager.getInstance().loadFrameworkResources(
mAndroidTarget); mAndroidTarget);
progress.worked(10); progress.worked(1);
// now load the layout lib bridge // now load the layout lib bridge
LayoutBridge layoutBridge = loadLayoutBridge(); LayoutBridge layoutBridge = loadLayoutBridge();
progress.worked(10); progress.worked(1);
// and finally create the PlatformData with all that we loaded. // and finally create the PlatformData with all that we loaded.
targetData.setExtraData(frameworkRepository, targetData.setExtraData(frameworkRepository,
@@ -258,6 +267,7 @@ public final class AndroidTargetParser {
broadcast_actions.toArray(new String[broadcast_actions.size()]), broadcast_actions.toArray(new String[broadcast_actions.size()]),
service_actions.toArray(new String[service_actions.size()]), service_actions.toArray(new String[service_actions.size()]),
categories.toArray(new String[categories.size()]), categories.toArray(new String[categories.size()]),
mAndroidTarget.getOptionalLibraries(),
resources, resources,
layoutBridge); layoutBridge);
@@ -268,10 +278,6 @@ public final class AndroidTargetParser {
AdtPlugin.logAndPrintError(e, TAG, "SDK parser failed"); //$NON-NLS-1$ AdtPlugin.logAndPrintError(e, TAG, "SDK parser failed"); //$NON-NLS-1$
AdtPlugin.printToConsole("SDK parser failed", e.getMessage()); AdtPlugin.printToConsole("SDK parser failed", e.getMessage());
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "SDK parser failed", e); return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "SDK parser failed", e);
} finally {
if (monitor != null) {
monitor.done();
}
} }
} }
@@ -604,6 +610,31 @@ public final class AndroidTargetParser {
return Collections.unmodifiableMap(map2); return Collections.unmodifiableMap(map2);
} }
/**
* 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. * Collects all manifest definition information from the attrs_manifest.xml and returns it.
*/ */
@@ -650,6 +681,15 @@ public final class AndroidTargetParser {
layoutBridge.status = LoadStatus.FAILED; layoutBridge.status = LoadStatus.FAILED;
AdtPlugin.log(IStatus.ERROR, "Failed to load " + AndroidConstants.CLASS_BRIDGE); //$NON-NLS-1$ AdtPlugin.log(IStatus.ERROR, "Failed to load " + AndroidConstants.CLASS_BRIDGE); //$NON-NLS-1$
} else { } 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; layoutBridge.status = LoadStatus.LOADED;
} }
} }

View File

@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer; 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;
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener; import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener;
import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.prefs.AndroidLocation.AndroidLocationException;
@@ -33,6 +34,7 @@ import com.android.sdklib.project.ProjectProperties.PropertyType;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaCore;
@@ -68,6 +70,22 @@ public class Sdk implements IProjectListener {
new HashMap<IProject, Map<String, String>>(); new HashMap<IProject, Map<String, String>>();
private final String mDocBaseUrl; 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. * Loads an SDK and returns an {@link Sdk} object if success.
* @param sdkLocation the OS path to the SDK. * @param sdkLocation the OS path to the SDK.
@@ -163,24 +181,87 @@ public class Sdk implements IProjectListener {
} }
/** /**
* 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) { synchronized (mProjectTargetMap) {
// look for the current target of the project boolean resolveProject = false;
IAndroidTarget previousTarget = mProjectTargetMap.get(project); boolean compileProject = false;
boolean cleanProject = false;
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);
// 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); IJavaProject javaProject = JavaCore.create(project);
AndroidClasspathContainerInitializer.updateProjects( AndroidClasspathContainerInitializer.updateProjects(
new IJavaProject[] { javaProject }); 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 @@ public class Sdk implements IProjectListener {
*/ */
private static String loadProjectProperties(IProject project, Sdk sdkStorage) { private static String loadProjectProperties(IProject project, Sdk sdkStorage) {
// load the default.properties from the project folder. // 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); PropertyType.DEFAULT);
if (properties == null) { if (properties == null) {
AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'", AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'",
@@ -229,7 +315,7 @@ public class Sdk implements IProjectListener {
if (sdkStorage != null) { if (sdkStorage != null) {
Map<String, String> configMap = ApkConfigurationHelper.getConfigs(properties); Map<String, String> configMap = ApkConfigurationHelper.getConfigs(properties);
if (configMap.size() > 0) { if (configMap != null) {
sdkStorage.mProjectApkConfigMap.put(project, configMap); sdkStorage.mProjectApkConfigMap.put(project, configMap);
} }
} }
@@ -296,40 +382,6 @@ public class Sdk implements IProjectListener {
return mProjectApkConfigMap.get(project); 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 * Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could
* be <code>null</code>. * be <code>null</code>.
@@ -402,8 +454,24 @@ public class Sdk implements IProjectListener {
} }
public void projectClosed(IProject project) { public void projectClosed(IProject project) {
mProjectTargetMap.remove(project); // get the target project
mProjectApkConfigMap.remove(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) { public void projectDeleted(IProject project) {

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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
}
}

View File

@@ -105,6 +105,8 @@ public class NewProjectWizard extends Wizard implements INewWizard {
SdkConstants.FD_LAYOUT + AndroidConstants.WS_SEP; SdkConstants.FD_LAYOUT + AndroidConstants.WS_SEP;
private static final String VALUES_DIRECTORY = private static final String VALUES_DIRECTORY =
SdkConstants.FD_VALUES + AndroidConstants.WS_SEP; 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 TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$
private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY
@@ -114,7 +116,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY
+ "uses-sdk.template"; //$NON-NLS-1$ + "uses-sdk.template"; //$NON-NLS-1$
private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY 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 private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY
+ "strings.template"; //$NON-NLS-1$ + "strings.template"; //$NON-NLS-1$
@@ -341,15 +343,20 @@ public class NewProjectWizard extends Wizard implements INewWizard {
// Create folders in the project if they don't already exist // Create folders in the project if they don't already exist
addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor); addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
String[] sourceFolder = new String[] { (String) parameters.get(PARAM_SRC_FOLDER) }; String[] sourceFolders = new String[] {
addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolder, monitor); (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. // Create the resource folders in the project if they don't already exist.
addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor); addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
// Setup class path // Setup class path
IJavaProject javaProject = JavaCore.create(project); 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()) { if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
// Create files in the project if they don't already exist // Create files in the project if they don't already exist
@@ -359,7 +366,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
addIcon(project, monitor); addIcon(project, monitor);
// Create the default package components // 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 // add the string definition file if needed
if (stringDictionary.size() > 0) { if (stringDictionary.size() > 0) {
@@ -371,7 +378,8 @@ public class NewProjectWizard extends Wizard implements INewWizard {
monitor); 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. // Fix the project to make sure all properties are as expected.
// Necessary for existing projects and good for new ones to. // Necessary for existing projects and good for new ones to.

View File

@@ -148,8 +148,11 @@ public class AndroidConstants {
/** The old common plug-in ID. Please do not use for new features. */ /** 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$ public static final String COMMON_PLUGIN_ID = "com.android.ide.eclipse.common"; //$NON-NLS-1$
/** aapt marker error. */ /** aapt marker error when running the compile command */
public final static String MARKER_AAPT = COMMON_PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$ 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. */ /** XML marker error. */
public final static String MARKER_XML = COMMON_PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$ public final static String MARKER_XML = COMMON_PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$

View File

@@ -19,6 +19,7 @@ package com.android.ide.eclipse.editors;
import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.sdk.Sdk; 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.ide.eclipse.editors.uimodel.UiElementNode;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
@@ -97,8 +98,9 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
private StructuredTextEditor mTextEditor; private StructuredTextEditor mTextEditor;
/** Listener for the XML model from the StructuredEditor */ /** Listener for the XML model from the StructuredEditor */
private XmlModelStateListener mXmlModelStateListener; private XmlModelStateListener mXmlModelStateListener;
/** Listener to update the root node if the resource framework changes */ /** Listener to update the root node if the target of the file is changed because of a
private Runnable mResourceRefreshListener; * SDK location change or a project target change */
private ITargetChangeListener mTargetListener;
/** /**
* Creates a form editor. * Creates a form editor.
@@ -107,15 +109,21 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
super(); super();
ResourcesPlugin.getWorkspace().addResourceChangeListener(this); ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
mResourceRefreshListener = new Runnable() { mTargetListener = new ITargetChangeListener() {
public void run() { public void onProjectTargetChange(IProject changedProject) {
commitPages(false /* onSave */); if (changedProject == getProject()) {
onTargetsLoaded();
}
}
public void onTargetsLoaded() {
commitPages(false /* onSave */);
// recreate the ui root node always // recreate the ui root node always
initUiRootNode(true /*force*/); initUiRootNode(true /*force*/);
} }
}; };
AdtPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener); AdtPlugin.getDefault().addTargetListener(mTargetListener);
} }
// ---- Abstract Methods ---- // ---- Abstract Methods ----
@@ -340,9 +348,9 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
} }
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
if (mResourceRefreshListener != null) { if (mTargetListener != null) {
AdtPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener); AdtPlugin.getDefault().removeTargetListener(mTargetListener);
mResourceRefreshListener = null; mTargetListener = null;
} }
super.dispose(); super.dispose();

View File

@@ -313,7 +313,7 @@ public final class DescriptorsUtils {
* *
* @param attributes The list of {@link AttributeDescriptor} to compare to. * @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. * @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. * @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 * @return True if this {@link AttributeInfo} is already present in
* the {@link AttributeDescriptor} list. * the {@link AttributeDescriptor} list.

View File

@@ -21,6 +21,7 @@ import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.sdk.LoadStatus; import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge; 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.common.resources.ResourceType;
import com.android.ide.eclipse.editors.IconFactory; import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.layout.LayoutEditor.UiEditorActions; import com.android.ide.eclipse.editors.layout.LayoutEditor.UiEditorActions;
@@ -198,13 +199,21 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
private ProjectCallback mProjectCallback; private ProjectCallback mProjectCallback;
private ILayoutLog mLogger; private ILayoutLog mLogger;
private boolean mNeedsXmlReload = false;
private boolean mNeedsRecompute = false; private boolean mNeedsRecompute = false;
private int mPlatformThemeCount = 0; private int mPlatformThemeCount = 0;
private boolean mDisableUpdates = false; private boolean mDisableUpdates = false;
private boolean mActive = false;
private Runnable mFrameworkResourceChangeListener = new Runnable() { /** Listener to update the root node if the target of the file is changed because of a
public void run() { * 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. // because the SDK changed we must reset the configured framework resource.
mConfiguredFrameworkRes = null; mConfiguredFrameworkRes = null;
@@ -228,7 +237,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
private final Runnable mConditionalRecomputeRunnable = new Runnable() { private final Runnable mConditionalRecomputeRunnable = new Runnable() {
public void run() { public void run() {
if (mActive) { if (mLayoutEditor.isGraphicalEditorActive()) {
recomputeLayout(); recomputeLayout();
} else { } else {
mNeedsRecompute = true; mNeedsRecompute = true;
@@ -253,7 +262,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
mMatchImage = factory.getIcon("match"); //$NON-NLS-1$ mMatchImage = factory.getIcon("match"); //$NON-NLS-1$
mErrorImage = factory.getIcon("error"); //$NON-NLS-1$ mErrorImage = factory.getIcon("error"); //$NON-NLS-1$
AdtPlugin.getDefault().addResourceChangedListener(mFrameworkResourceChangeListener); AdtPlugin.getDefault().addTargetListener(mTargetListener);
} }
// ------------------------------------ // ------------------------------------
@@ -561,10 +570,9 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
@Override @Override
public void dispose() { public void dispose() {
if (mFrameworkResourceChangeListener != null) { if (mTargetListener != null) {
AdtPlugin.getDefault().removeResourceChangedListener( AdtPlugin.getDefault().removeTargetListener(mTargetListener);
mFrameworkResourceChangeListener); mTargetListener = null;
mFrameworkResourceChangeListener = null;
} }
LayoutReloadMonitor.getMonitor().removeListener(mEditedFile.getProject(), this); LayoutReloadMonitor.getMonitor().removeListener(mEditedFile.getProject(), this);
@@ -1026,25 +1034,36 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
} }
/** /**
* 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() { 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()) { if (mLayoutEditor.isGraphicalEditorActive()) {
doXmlReload(true /* force */);
recomputeLayout(); recomputeLayout();
} else { } 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 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
/** /**
* Recomputes the layout with the help of layoutlib. * Recomputes the layout with the help of layoutlib.
*/ */
@SuppressWarnings("deprecation")
void recomputeLayout() { void recomputeLayout() {
doXmlReload(false /* force */);
try { try {
// check that the resource exists. If the file is opened but the project is closed // 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 // or deleted for some reason (changed from outside of eclipse), then this will
@@ -1763,20 +1784,47 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
if (themeIndex != -1) { if (themeIndex != -1) {
String theme = mThemeCombo.getItem(themeIndex); 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 // Compute the layout
UiElementPullParser parser = new UiElementPullParser(getModel()); UiElementPullParser parser = new UiElementPullParser(getModel());
Rectangle rect = getBounds(); Rectangle rect = getBounds();
ILayoutResult result = bridge.bridge.computeLayout(parser, ILayoutResult result = null;
iProject /* projectKey */, if (bridge.apiLevel >= 3) {
rect.width, rect.height, theme, // call the new api with proper theme differentiator and
mConfiguredProjectRes, frameworkResources, mProjectCallback, // density/dpi support.
mLogger); 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. // update the UiElementNode with the layout info.
if (result.getSuccess() == ILayoutResult.SUCCESS) { if (result.getSuccess() == ILayoutResult.SUCCESS) {
@@ -1921,8 +1969,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
* Responds to a page change that made the Graphical editor page the activated page. * Responds to a page change that made the Graphical editor page the activated page.
*/ */
void activated() { void activated() {
mActive = true; if (mNeedsRecompute || mNeedsXmlReload) {
if (mNeedsRecompute) {
recomputeLayout(); recomputeLayout();
} }
} }
@@ -1931,7 +1978,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
* Responds to a page change that made the Graphical editor page the deactivated page * Responds to a page change that made the Graphical editor page the deactivated page
*/ */
void deactivated() { void deactivated() {
mActive = false; // nothing to be done here for now.
} }
/** /**

View File

@@ -269,8 +269,12 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
protected void pageChange(int newPageIndex) { protected void pageChange(int newPageIndex) {
super.pageChange(newPageIndex); super.pageChange(newPageIndex);
if (mGraphicalEditor != null && newPageIndex == mGraphicalEditorIndex) { if (mGraphicalEditor != null) {
mGraphicalEditor.activated(); if (newPageIndex == mGraphicalEditorIndex) {
mGraphicalEditor.activated();
} else {
mGraphicalEditor.deactivated();
}
} }
} }
@@ -278,8 +282,12 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
public void partActivated(IWorkbenchPart part) { public void partActivated(IWorkbenchPart part) {
if (part == this) { if (part == this) {
if (mGraphicalEditor != null && getActivePage() == mGraphicalEditorIndex) { if (mGraphicalEditor != null) {
mGraphicalEditor.activated(); if (getActivePage() == mGraphicalEditorIndex) {
mGraphicalEditor.activated();
} else {
mGraphicalEditor.deactivated();
}
} }
} }
} }
@@ -334,23 +342,23 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
// ---- Local Methods ---- // ---- Local Methods ----
/** /**
* Returns true if the Graphics editor page is visible. * Returns true if the Graphics editor page is visible. This <b>must</b> be
* This <b>must</b> be called from the UI thread. * called from the UI thread.
*/ */
boolean isGraphicalEditorActive() { boolean isGraphicalEditorActive() {
IWorkbenchPartSite workbenchSite = getSite(); IWorkbenchPartSite workbenchSite = getSite();
IWorkbenchPage workbenchPage = workbenchSite.getPage(); IWorkbenchPage workbenchPage = workbenchSite.getPage();
// check if the editor is visible in the workbench page // 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 // and then if the page of the editor is visible (not to be confused with
// the workbench page) // the workbench page)
return mGraphicalEditorIndex == getActivePage(); return mGraphicalEditorIndex == getActivePage();
} }
return false;
}
return false;
}
@Override @Override
protected void initUiRootNode(boolean force) { protected void initUiRootNode(boolean force) {
// The root UI node is always created, even if there's no corresponding XML node. // The root UI node is always created, even if there's no corresponding XML node.

View File

@@ -195,6 +195,8 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider {
overrides.put("*/permission", ListAttributeDescriptor.class); //$NON-NLS-1$ overrides.put("*/permission", ListAttributeDescriptor.class); //$NON-NLS-1$
overrides.put("*/targetPackage", PackageAttributeDescriptor.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$ overrides.put("action,category,uses-permission/" + ANDROID_NAME_ATTR, //$NON-NLS-1$
ListAttributeDescriptor.class); ListAttributeDescriptor.class);
overrides.put("application/" + ANDROID_NAME_ATTR, ApplicationAttributeDescriptor.class); //$NON-NLS-1$ overrides.put("application/" + ANDROID_NAME_ATTR, ApplicationAttributeDescriptor.class); //$NON-NLS-1$

View File

@@ -16,12 +16,12 @@
package com.android.ide.eclipse.editors.manifest.descriptors; 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.descriptors.TextAttributeDescriptor;
import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode; 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.manifest.model.UiClassAttributeNode.IPostTypeCreationAction;
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode; import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import com.android.sdklib.SdkConstants;
/** /**
* Describes an XML attribute representing a class name. * Describes an XML attribute representing a class name.

View File

@@ -17,6 +17,7 @@
package com.android.ide.eclipse.editors.ui.tree; package com.android.ide.eclipse.editors.ui.tree;
import com.android.ide.eclipse.adt.AdtPlugin; 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.AndroidEditor;
import com.android.ide.eclipse.editors.IconFactory; import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.editors.descriptors.ElementDescriptor;
@@ -26,6 +27,7 @@ import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener;
import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; import com.android.ide.eclipse.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.editors.uimodel.UiElementNode; 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.Action;
import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IMenuManager;
@@ -285,13 +287,21 @@ public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml
} }
}; };
final Runnable resourceRefreshListener = new Runnable() { /** Listener to update the root node if the target of the file is changed because of a
public void run() { * 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 a details part has been created, we need to "refresh" it too.
if (mDetailsPart != null) { if (mDetailsPart != null) {
// The details part does not directly expose access to its internal // 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 // 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. // existing cached pages.
int limit = mDetailsPart.getPageLimit(); int limit = mDetailsPart.getPageLimit();
mDetailsPart.setPageLimit(0); mDetailsPart.setPageLimit(0);
@@ -306,7 +316,7 @@ public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml
changeRootAndDescriptors(mUiRootNode, mDescriptorFilters, false /* refresh */); changeRootAndDescriptors(mUiRootNode, mDescriptorFilters, false /* refresh */);
// Listen on resource framework changes to refresh the tree // 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. // Remove listeners when the tree widget gets disposed.
tree.addDisposeListener(new DisposeListener() { tree.addDisposeListener(new DisposeListener() {
@@ -318,7 +328,7 @@ public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml
node.removeUpdateListener(mUiRefreshListener); node.removeUpdateListener(mUiRefreshListener);
mUiRootNode.removeUpdateListener(mUiEnableListener); mUiRootNode.removeUpdateListener(mUiEnableListener);
AdtPlugin.getDefault().removeResourceChangedListener(resourceRefreshListener); AdtPlugin.getDefault().removeTargetListener(targetListener);
if (mClipboard != null) { if (mClipboard != null) {
mClipboard.dispose(); mClipboard.dispose();
mClipboard = null; mClipboard = null;
@@ -580,7 +590,11 @@ public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml
ui_node = ui_node.getUiParent()) { ui_node = ui_node.getUiParent()) {
segments.add(0, ui_node); 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);
}
} }
} }

View File

@@ -23,6 +23,7 @@ import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.ProjectChooserHelper; import com.android.ide.eclipse.common.project.ProjectChooserHelper;
import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor;
import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; 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.menu.descriptors.MenuDescriptors;
import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration;
import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier;
@@ -81,6 +82,7 @@ class NewXmlFileCreationPage extends WizardPage {
private final String mXmlns; private final String mXmlns;
private final String mDefaultAttrs; private final String mDefaultAttrs;
private final String mDefaultRoot; private final String mDefaultRoot;
private final int mTargetApiLevel;
public TypeInfo(String uiName, public TypeInfo(String uiName,
String tooltip, String tooltip,
@@ -88,7 +90,8 @@ class NewXmlFileCreationPage extends WizardPage {
Object rootSeed, Object rootSeed,
String defaultRoot, String defaultRoot,
String xmlns, String xmlns,
String defaultAttrs) { String defaultAttrs,
int targetApiLevel) {
mUiName = uiName; mUiName = uiName;
mResFolderType = resFolderType; mResFolderType = resFolderType;
mTooltip = tooltip; mTooltip = tooltip;
@@ -96,6 +99,7 @@ class NewXmlFileCreationPage extends WizardPage {
mDefaultRoot = defaultRoot; mDefaultRoot = defaultRoot;
mXmlns = xmlns; mXmlns = xmlns;
mDefaultAttrs = defaultAttrs; mDefaultAttrs = defaultAttrs;
mTargetApiLevel = targetApiLevel;
} }
/** Returns the UI name for the resource type. Unique. Never null. */ /** Returns the UI name for the resource type. Unique. Never null. */
@@ -176,6 +180,13 @@ class NewXmlFileCreationPage extends WizardPage {
String getDefaultAttrs() { String getDefaultAttrs() {
return mDefaultAttrs; 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 @@ class NewXmlFileCreationPage extends WizardPage {
"LinearLayout", // default root "LinearLayout", // default root
SdkConstants.NS_RESOURCES, // xmlns SdkConstants.NS_RESOURCES, // xmlns
"android:layout_width=\"wrap_content\"\n" + // default attributes "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 new TypeInfo("Values", // UI name
"An XML file with simple values: colors, strings, dimensions, etc.", // tooltip "An XML file with simple values: colors, strings, dimensions, etc.", // tooltip
@@ -198,7 +210,8 @@ class NewXmlFileCreationPage extends WizardPage {
ResourcesDescriptors.ROOT_ELEMENT, // root seed ResourcesDescriptors.ROOT_ELEMENT, // root seed
null, // default root null, // default root
null, // xmlns null, // xmlns
null // default attributes null, // default attributes
1 // target API level
), ),
new TypeInfo("Menu", // UI name new TypeInfo("Menu", // UI name
"An XML file that describes an menu.", // tooltip "An XML file that describes an menu.", // tooltip
@@ -206,7 +219,17 @@ class NewXmlFileCreationPage extends WizardPage {
MenuDescriptors.MENU_ROOT_ELEMENT, // root seed MenuDescriptors.MENU_ROOT_ELEMENT, // root seed
null, // default root null, // default root
SdkConstants.NS_RESOURCES, // xmlns 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 new TypeInfo("Preference", // UI name
"An XML file that describes preferences.", // tooltip "An XML file that describes preferences.", // tooltip
@@ -214,15 +237,17 @@ class NewXmlFileCreationPage extends WizardPage {
AndroidTargetData.DESCRIPTOR_PREFERENCES, // root seed AndroidTargetData.DESCRIPTOR_PREFERENCES, // root seed
AndroidConstants.CLASS_PREFERENCE_SCREEN, // default root AndroidConstants.CLASS_PREFERENCE_SCREEN, // default root
SdkConstants.NS_RESOURCES, // xmlns SdkConstants.NS_RESOURCES, // xmlns
null // default attributes null, // default attributes
1 // target API level
), ),
new TypeInfo("Searchable", // UI name 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 ResourceFolderType.XML, // folder type
AndroidTargetData.DESCRIPTOR_SEARCHABLE, // root seed AndroidTargetData.DESCRIPTOR_SEARCHABLE, // root seed
null, // default root null, // default root
SdkConstants.NS_RESOURCES, // xmlns SdkConstants.NS_RESOURCES, // xmlns
null // default attributes null, // default attributes
1 // target API level
), ),
new TypeInfo("Animation", // UI name new TypeInfo("Animation", // UI name
"An XML file that describes an animation.", // tooltip "An XML file that describes an animation.", // tooltip
@@ -237,10 +262,14 @@ class NewXmlFileCreationPage extends WizardPage {
}, },
"set", //$NON-NLS-1$ // default root "set", //$NON-NLS-1$ // default root
null, // xmlns 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/" */ /** Absolute destination folder root, e.g. "/res/" */
private static String sResFolderAbs = AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP; private static String sResFolderAbs = AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP;
/** Relative destination folder root, e.g. "res/" */ /** Relative destination folder root, e.g. "res/" */
@@ -290,7 +319,7 @@ class NewXmlFileCreationPage extends WizardPage {
initializeDialogUnits(parent); initializeDialogUnits(parent);
composite.setLayout(new GridLayout(3, false /*makeColumnsEqualWidth*/)); composite.setLayout(new GridLayout(NUM_COL, false /*makeColumnsEqualWidth*/));
composite.setLayoutData(new GridData(GridData.FILL_BOTH)); composite.setLayoutData(new GridData(GridData.FILL_BOTH));
createProjectGroup(composite); createProjectGroup(composite);
@@ -303,8 +332,9 @@ class NewXmlFileCreationPage extends WizardPage {
setControl(composite); setControl(composite);
// Update state the first time // Update state the first time
initializeRootValues();
initializeFromSelection(mInitialSelection); initializeFromSelection(mInitialSelection);
initializeRootValues();
enableTypesBasedOnApi();
validatePage(); validatePage();
} }
@@ -418,17 +448,35 @@ class NewXmlFileCreationPage extends WizardPage {
new Label(parent, SWT.NONE); new Label(parent, SWT.NONE);
} }
/**
* 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. * Creates the project & filename fields.
* <p/> * <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) { private void createProjectGroup(Composite parent) {
int col = 0;
// project name // project name
String tooltip = "The Android Project where the new resource file will be created."; String tooltip = "The Android Project where the new resource file will be created.";
Label label = new Label(parent, SWT.NONE); Label label = new Label(parent, SWT.NONE);
label.setText("Project"); label.setText("Project");
label.setToolTipText(tooltip); label.setToolTipText(tooltip);
++col;
mProjectTextField = new Text(parent, SWT.BORDER); mProjectTextField = new Text(parent, SWT.BORDER);
mProjectTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mProjectTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
@@ -438,6 +486,7 @@ class NewXmlFileCreationPage extends WizardPage {
onProjectFieldUpdated(); onProjectFieldUpdated();
} }
}); });
++col;
mProjectBrowseButton = new Button(parent, SWT.NONE); mProjectBrowseButton = new Button(parent, SWT.NONE);
mProjectBrowseButton.setText("Browse..."); mProjectBrowseButton.setText("Browse...");
@@ -449,12 +498,16 @@ class NewXmlFileCreationPage extends WizardPage {
} }
}); });
mProjectChooserHelper = new ProjectChooserHelper(parent.getShell()); mProjectChooserHelper = new ProjectChooserHelper(parent.getShell());
++col;
col = padWithEmptyCells(parent, col);
// file name // file name
tooltip = "The name of the resource file to create."; tooltip = "The name of the resource file to create.";
label = new Label(parent, SWT.NONE); label = new Label(parent, SWT.NONE);
label.setText("File"); label.setText("File");
label.setToolTipText(tooltip); label.setToolTipText(tooltip);
++col;
mFileNameTextField = new Text(parent, SWT.BORDER); mFileNameTextField = new Text(parent, SWT.BORDER);
mFileNameTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mFileNameTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
@@ -464,31 +517,32 @@ class NewXmlFileCreationPage extends WizardPage {
validatePage(); validatePage();
} }
}); });
++col;
emptyCell(parent); padWithEmptyCells(parent, col);
} }
/** /**
* Creates the type field, {@link ConfigurationSelector} and the folder field. * Creates the type field, {@link ConfigurationSelector} and the folder field.
* <p/> * <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) { private void createTypeGroup(Composite parent) {
// separator // separator
Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); 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 before type radios
label = new Label(parent, SWT.NONE); label = new Label(parent, SWT.NONE);
label.setText("What type of resource would you like to create?"); 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. // display the types on three columns of radio buttons.
emptyCell(parent); emptyCell(parent);
Composite grid = new Composite(parent, SWT.NONE); 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() { SelectionListener radioListener = new SelectionAdapter() {
@Override @Override
@@ -501,23 +555,27 @@ class NewXmlFileCreationPage extends WizardPage {
}; };
int n = sTypes.length; int n = sTypes.length;
int num_lines = n/3; int num_lines = (n + NUM_COL/2) / NUM_COL;
for (int line = 0; line < num_lines; line++) { for (int line = 0, k = 0; line < num_lines; line++) {
for (int i = 0; i < 3; i++) { for (int i = 0; i < NUM_COL; i++, k++) {
TypeInfo type = sTypes[line * 3 + i]; if (k < n) {
Button radio = new Button(grid, SWT.RADIO); TypeInfo type = sTypes[k];
type.setWidget(radio); Button radio = new Button(grid, SWT.RADIO);
radio.setSelection(false); type.setWidget(radio);
radio.setText(type.getUiName()); radio.setSelection(false);
radio.setToolTipText(type.getTooltip()); radio.setText(type.getUiName());
radio.addSelectionListener(radioListener); radio.setToolTipText(type.getTooltip());
radio.addSelectionListener(radioListener);
} else {
emptyCell(grid);
}
} }
} }
// label before configuration selector // label before configuration selector
label = new Label(parent, SWT.NONE); label = new Label(parent, SWT.NONE);
label.setText("What type of resource configuration would you like?"); label.setText("What type of resource configuration would you like?");
label.setLayoutData(newGridData(3)); label.setLayoutData(newGridData(NUM_COL));
// configuration selector // configuration selector
emptyCell(parent); emptyCell(parent);
@@ -527,6 +585,7 @@ class NewXmlFileCreationPage extends WizardPage {
gd.heightHint = ConfigurationSelector.HEIGHT_HINT; gd.heightHint = ConfigurationSelector.HEIGHT_HINT;
mConfigSelector.setLayoutData(gd); mConfigSelector.setLayoutData(gd);
mConfigSelector.setOnChangeListener(new onConfigSelectorUpdated()); mConfigSelector.setOnChangeListener(new onConfigSelectorUpdated());
emptyCell(parent);
// folder name // folder name
String tooltip = "The folder where the file will be generated, relative to the project."; String tooltip = "The folder where the file will be generated, relative to the project.";
@@ -542,25 +601,23 @@ class NewXmlFileCreationPage extends WizardPage {
onWsFolderPathUpdated(); onWsFolderPathUpdated();
} }
}); });
emptyCell(parent);
} }
/** /**
* Creates the root element combo. * Creates the root element combo.
* <p/> * <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) { private void createRootGroup(Composite parent) {
// separator // separator
Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); 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 // label before the root combo
String tooltip = "The root element to create in the XML file."; String tooltip = "The root element to create in the XML file.";
label = new Label(parent, SWT.NONE); label = new Label(parent, SWT.NONE);
label.setText("Select the root element for the XML file:"); label.setText("Select the root element for the XML file:");
label.setLayoutData(newGridData(3)); label.setLayoutData(newGridData(NUM_COL));
label.setToolTipText(tooltip); label.setToolTipText(tooltip);
// root combo // root combo
@@ -572,7 +629,7 @@ class NewXmlFileCreationPage extends WizardPage {
mRootElementCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mRootElementCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mRootElementCombo.setToolTipText(tooltip); mRootElementCombo.setToolTipText(tooltip);
emptyCell(parent); padWithEmptyCells(parent, 2);
} }
/** /**
@@ -690,11 +747,13 @@ class NewXmlFileCreationPage extends WizardPage {
// get the AndroidTargetData from the project // get the AndroidTargetData from the project
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
AndroidTargetData data = Sdk.getCurrent().getTargetData(target); AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
ElementDescriptor descriptor = data.getDescriptorProvider(
(Integer)rootSeed).getDescriptor();
HashSet<ElementDescriptor> visited = new HashSet<ElementDescriptor>(); IDescriptorProvider provider = data.getDescriptorProvider((Integer)rootSeed);
initRootElementDescriptor(roots, descriptor, visited); ElementDescriptor descriptor = provider.getDescriptor();
if (descriptor != null) {
HashSet<ElementDescriptor> visited = new HashSet<ElementDescriptor>();
initRootElementDescriptor(roots, descriptor, visited);
}
// Sort alphabetically. // Sort alphabetically.
Collections.sort(roots); Collections.sort(roots);
@@ -743,15 +802,7 @@ class NewXmlFileCreationPage extends WizardPage {
} }
if (found != mProject) { if (found != mProject) {
mProject = found; changeProject(found);
// update the Type with the new descriptors.
initializeRootValues();
// update the combo
updateRootCombo(getSelectedType());
validatePage();
} }
} }
@@ -761,17 +812,27 @@ class NewXmlFileCreationPage extends WizardPage {
private void onProjectBrowse() { private void onProjectBrowse() {
IJavaProject p = mProjectChooserHelper.chooseJavaProject(mProjectTextField.getText()); IJavaProject p = mProjectChooserHelper.chooseJavaProject(mProjectTextField.getText());
if (p != null) { if (p != null) {
mProject = p.getProject(); changeProject(p.getProject());
mProjectTextField.setText(mProject.getName()); 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();
} }
/** /**
@@ -985,6 +1046,26 @@ class NewXmlFileCreationPage extends WizardPage {
} }
} }
/**
* 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. * Validates the fields, displays errors and warnings.
* Enables the finish button if there are no errors. * Enables the finish button if there are no errors.
@@ -1017,6 +1098,22 @@ class NewXmlFileCreationPage extends WizardPage {
} }
} }
// -- 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 // -- validate folder configuration
if (error == null) { if (error == null) {
ConfigurationState state = mConfigSelector.getState(); ConfigurationState state = mConfigSelector.getState();

View File

@@ -53,6 +53,9 @@ public final class XmlDescriptors implements IDescriptorProvider {
/** The root document descriptor for preferences. */ /** The root document descriptor for preferences. */
private DocumentDescriptor mPrefDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$ 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. */ /** @return the root descriptor for both searchable and preferences. */
public DocumentDescriptor getDescriptor() { public DocumentDescriptor getDescriptor() {
return mDescriptor; return mDescriptor;
@@ -72,6 +75,11 @@ public final class XmlDescriptors implements IDescriptorProvider {
return mPrefDescriptor; return mPrefDescriptor;
} }
/** @return the root descriptor for gadget providers. */
public DocumentDescriptor getGadgetDescriptor() {
return mGadgetDescriptor;
}
public IDescriptorProvider getSearchableProvider() { public IDescriptorProvider getSearchableProvider() {
return new IDescriptorProvider() { return new IDescriptorProvider() {
public ElementDescriptor getDescriptor() { public ElementDescriptor getDescriptor() {
@@ -96,6 +104,18 @@ public final class XmlDescriptors implements IDescriptorProvider {
}; };
} }
public IDescriptorProvider getGadgetProvider() {
return new IDescriptorProvider() {
public ElementDescriptor getDescriptor() {
return mGadgetDescriptor;
}
public ElementDescriptor[] getRootElementDescriptors() {
return mGadgetDescriptor.getChildren();
}
};
}
/** /**
* Updates the document descriptor. * Updates the document descriptor.
* <p/> * <p/>
@@ -103,11 +123,13 @@ public final class XmlDescriptors implements IDescriptorProvider {
* all at once. * all at once.
* *
* @param searchableStyleMap The map style=>attributes for <searchable> from the attrs.xml file * @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 prefs The list of non-group preference descriptions
* @param prefGroups The list of preference group descriptions * @param prefGroups The list of preference group descriptions
*/ */
public synchronized void updateDescriptors( public synchronized void updateDescriptors(
Map<String, DeclareStyleableInfo> searchableStyleMap, Map<String, DeclareStyleableInfo> searchableStyleMap,
Map<String, DeclareStyleableInfo> gadgetStyleMap,
ViewClassInfo[] prefs, ViewClassInfo[] prefGroups) { ViewClassInfo[] prefs, ViewClassInfo[] prefGroups) {
XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor( XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(
@@ -115,12 +137,17 @@ public final class XmlDescriptors implements IDescriptorProvider {
SdkConstants.NS_RESOURCES); SdkConstants.NS_RESOURCES);
ElementDescriptor searchable = createSearchable(searchableStyleMap, xmlns); ElementDescriptor searchable = createSearchable(searchableStyleMap, xmlns);
ElementDescriptor gadget = createGadgetProviderInfo(gadgetStyleMap, xmlns);
ElementDescriptor preferences = createPreference(prefs, prefGroups, xmlns); ElementDescriptor preferences = createPreference(prefs, prefGroups, xmlns);
ArrayList<ElementDescriptor> list = new ArrayList<ElementDescriptor>(); ArrayList<ElementDescriptor> list = new ArrayList<ElementDescriptor>();
if (searchable != null) { if (searchable != null) {
list.add(searchable); list.add(searchable);
mSearchDescriptor.setChildren(new ElementDescriptor[]{ searchable }); mSearchDescriptor.setChildren(new ElementDescriptor[]{ searchable });
} }
if (gadget != null) {
list.add(gadget);
mGadgetDescriptor.setChildren(new ElementDescriptor[]{ gadget });
}
if (preferences != null) { if (preferences != null) {
list.add(preferences); list.add(preferences);
mPrefDescriptor.setChildren(new ElementDescriptor[]{ preferences }); mPrefDescriptor.setChildren(new ElementDescriptor[]{ preferences });
@@ -161,6 +188,28 @@ public final class XmlDescriptors implements IDescriptorProvider {
false /* mandatory */ ); false /* mandatory */ );
return searchable; 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 * Returns a new ElementDescriptor constructed from the information given here

View File

@@ -22,6 +22,13 @@ if [ "-n" == "$1" ]; then
shift shift
fi fi
DIR="frameworks"
if [ "-s" == "$1" ]; then
shift
DIR="$1"
shift
fi
SRC="$1" SRC="$1"
DST="$2" DST="$2"
@@ -36,7 +43,7 @@ function process() {
N=0 N=0
E=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 if [ -f "$i" ]; then
# look for ^package (android.view.blah);$ # look for ^package (android.view.blah);$
PACKAGE=`sed -n '/^package [^ ;]\+; */{s/[^ ]* *\([^ ;]*\).*/\1/p;q}' "$i"` PACKAGE=`sed -n '/^package [^ ;]\+; */{s/[^ ]* *\([^ ;]*\).*/\1/p;q}' "$i"`

View File

@@ -20,9 +20,9 @@ rem Set up prog to be the path of this script, including following symlinks,
rem and set up progdir to be the fully-qualified pathname of its directory. rem and set up progdir to be the fully-qualified pathname of its directory.
set prog=%~f0 set prog=%~f0
rem Change current directory to where ddms is, to avoid issues with directories rem Change current directory and drive to where the script is, to avoid
rem containing whitespaces. rem issues with directories containing whitespaces.
cd %~dp0 cd /d %~dp0
set jarfile=hierarchyviewer.jar set jarfile=hierarchyviewer.jar
set frameworkdir= set frameworkdir=

View File

@@ -32,6 +32,7 @@ import java.net.Socket;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Stack; import java.util.Stack;
import java.util.regex.Pattern;
public class ViewHierarchyLoader { public class ViewHierarchyLoader {
@SuppressWarnings("empty-statement") @SuppressWarnings("empty-statement")
@@ -109,7 +110,9 @@ public class ViewHierarchyLoader {
parent.children.add(lastNode); parent.children.add(lastNode);
} }
} }
updateIndices(scene.getRoot());
} catch (IOException ex) { } catch (IOException ex) {
Exceptions.printStackTrace(ex); Exceptions.printStackTrace(ex);
} finally { } finally {
@@ -127,10 +130,18 @@ public class ViewHierarchyLoader {
} }
System.out.println("==> DONE"); System.out.println("==> DONE");
return scene; return scene;
} }
private static void updateIndices(ViewNode root) {
root.computeIndex();
for (ViewNode node : root.children) {
updateIndices(node);
}
}
private static int countFrontWhitespace(String line) { private static int countFrontWhitespace(String line) {
int count = 0; int count = 0;
while (line.charAt(count) == ' ') { while (line.charAt(count) == ' ') {

View File

@@ -60,22 +60,25 @@ public class ViewHierarchyScene extends GraphScene<ViewNode, String> {
@Override @Override
protected Widget attachNodeWidget(ViewNode node) { 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(createSelectAction());
widget.getActions().addAction(moveAction); widget.getActions().addAction(moveAction);
widgetLayer.addChild(widget); widgetLayer.addChild(widget);
return widget; return widget;
} }
private Widget createBox(String node, String id) { private Widget createBox(ViewNode node, String nodeName, String id) {
Widget box = new GradientWidget(this); final String shortName = getShortName(nodeName);
node.setShortName(shortName);
GradientWidget box = new GradientWidget(this, node);
box.setLayout(LayoutFactory.createVerticalFlowLayout()); box.setLayout(LayoutFactory.createVerticalFlowLayout());
box.setBorder(BorderFactory.createLineBorder(2, Color.BLACK)); box.setBorder(BorderFactory.createLineBorder(2, Color.BLACK));
box.setOpaque(true); box.setOpaque(true);
LabelWidget label = new LabelWidget(this); LabelWidget label = new LabelWidget(this);
label.setFont(getDefaultFont().deriveFont(Font.PLAIN, 12.0f)); label.setFont(getDefaultFont().deriveFont(Font.PLAIN, 12.0f));
label.setLabel(getShortName(node)); label.setLabel(shortName);
label.setBorder(BorderFactory.createEmptyBorder(6, 6, 0, 6)); label.setBorder(BorderFactory.createEmptyBorder(6, 6, 0, 6));
label.setAlignment(LabelWidget.Alignment.CENTER); label.setAlignment(LabelWidget.Alignment.CENTER);
@@ -83,9 +86,11 @@ public class ViewHierarchyScene extends GraphScene<ViewNode, String> {
label = new LabelWidget(this); label = new LabelWidget(this);
label.setFont(getDefaultFont().deriveFont(Font.PLAIN, 10.0f)); 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.setBorder(BorderFactory.createEmptyBorder(3, 6, 0, 6));
label.setAlignment(LabelWidget.Alignment.CENTER); label.setAlignment(LabelWidget.Alignment.CENTER);
box.addressWidget = label;
box.addChild(label); box.addChild(label);
@@ -136,7 +141,7 @@ public class ViewHierarchyScene extends GraphScene<ViewNode, String> {
connection.setTargetAnchor(AnchorFactory.createRectangularAnchor(target)); 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( public static final GradientPaint BLUE_EXPERIENCE = new GradientPaint(
new Point2D.Double(0, 0), new Point2D.Double(0, 0),
new Color(168, 204, 241), new Color(168, 204, 241),
@@ -177,15 +182,28 @@ public class ViewHierarchyScene extends GraphScene<ViewNode, String> {
new Color(129, 138, 155), new Color(129, 138, 155),
new Point2D.Double(0, 1), new Point2D.Double(0, 1),
new Color(58, 66, 82)); 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 UNSELECTED = Color.BLACK;
private static Color SELECTED = Color.WHITE; private static Color SELECTED = Color.WHITE;
private boolean isSelected = false; private final ViewNode node;
private GradientPaint gradient = MAC_OSX_SELECTED;
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); super(scene);
this.node = node;
node.setStateListener(this);
} }
@Override @Override
@@ -193,8 +211,12 @@ public class ViewHierarchyScene extends GraphScene<ViewNode, String> {
super.notifyStateChanged(previous, state); super.notifyStateChanged(previous, state);
isSelected = state.isSelected() || state.isFocused() || state.isWidgetFocused(); isSelected = state.isSelected() || state.isFocused() || state.isWidgetFocused();
pickChildrenColor();
}
private void pickChildrenColor() {
for (Widget child : getChildren()) { for (Widget child : getChildren()) {
child.setForeground(isSelected ? SELECTED : UNSELECTED); child.setForeground(isSelected || node.filtered ? SELECTED : UNSELECTED);
} }
repaint(); repaint();
@@ -206,14 +228,35 @@ public class ViewHierarchyScene extends GraphScene<ViewNode, String> {
Graphics2D g2 = getGraphics(); Graphics2D g2 = getGraphics();
Rectangle bounds = getBounds(); Rectangle bounds = getBounds();
if (!isSelected) { 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 { } else {
g2.setPaint(new GradientPaint(bounds.x, bounds.y, gradient.getColor1(), g2.setPaint(new GradientPaint(bounds.x, bounds.y, selectedGradient.getColor1(),
bounds.x, bounds.x + bounds.height, gradient.getColor2())); bounds.x, bounds.x + bounds.height, selectedGradient.getColor2()));
} }
g2.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); 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());
}
}
} }
} }

View File

@@ -17,7 +17,6 @@
package com.android.hierarchyviewer.scene; package com.android.hierarchyviewer.scene;
import com.android.ddmlib.Device; import com.android.ddmlib.Device;
import com.android.hierarchyviewer.device.Configuration;
import com.android.hierarchyviewer.device.Window; import com.android.hierarchyviewer.device.Window;
import com.android.hierarchyviewer.device.DeviceBridge; import com.android.hierarchyviewer.device.DeviceBridge;

View File

@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern;
public class ViewNode { public class ViewNode {
public String id; public String id;
@@ -52,8 +53,15 @@ public class ViewNode {
public boolean willNotDraw; public boolean willNotDraw;
public boolean hasMargins; public boolean hasMargins;
boolean hasFocus;
int index;
public boolean decoded; public boolean decoded;
public boolean filtered;
private String shortName;
private StateListener listener;
void decode() { void decode() {
id = namedProperties.get("mID").value; id = namedProperties.get("mID").value;
@@ -73,6 +81,7 @@ public class ViewNode {
marginBottom = getInt("layout_bottomMargin", Integer.MIN_VALUE); marginBottom = getInt("layout_bottomMargin", Integer.MIN_VALUE);
baseline = getInt("getBaseline()", 0); baseline = getInt("getBaseline()", 0);
willNotDraw = getBoolean("willNotDraw()", false); willNotDraw = getBoolean("willNotDraw()", false);
hasFocus = getBoolean("hasFocus()", false);
hasMargins = marginLeft != Integer.MIN_VALUE && hasMargins = marginLeft != Integer.MIN_VALUE &&
marginRight != Integer.MIN_VALUE && marginRight != Integer.MIN_VALUE &&
@@ -101,11 +110,33 @@ public class ViewNode {
return Integer.parseInt(p.value); return Integer.parseInt(p.value);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
return defaultValue; return defaultValue;
} }
} }
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"}) @SuppressWarnings({"StringEquality"})
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
@@ -164,4 +195,9 @@ public class ViewNode {
return hash; return hash;
} }
} }
interface StateListener {
void nodeStateChanged(ViewNode node);
void nodeIndexChanged(ViewNode node);
}
} }

View File

@@ -76,6 +76,9 @@ import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.JTree; import javax.swing.JTree;
import javax.swing.Box; 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.TreePath;
import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
@@ -84,6 +87,8 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionListener; import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import javax.swing.table.DefaultTableModel; import javax.swing.table.DefaultTableModel;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.BorderLayout; import java.awt.BorderLayout;
@@ -105,6 +110,8 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
public class Workspace extends JFrame { public class Workspace extends JFrame {
@@ -156,6 +163,8 @@ public class Workspace extends JFrame {
private JTable windows; private JTable windows;
private JLabel minZoomLabel; private JLabel minZoomLabel;
private JLabel maxZoomLabel; private JLabel maxZoomLabel;
private JTextField filterText;
private JLabel filterLabel;
public Workspace() { public Workspace() {
super("Hierarchy Viewer"); super("Hierarchy Viewer");
@@ -313,10 +322,33 @@ public class Workspace extends JFrame {
graphViewButton.setSelected(true); 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 = new JLabel();
minZoomLabel.setText("20%"); minZoomLabel.setText("20%");
minZoomLabel.putClientProperty("JComponent.sizeVariant", "small"); minZoomLabel.putClientProperty("JComponent.sizeVariant", "small");
minZoomLabel.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 0)); minZoomLabel.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 0));
leftSide.add(minZoomLabel); leftSide.add(minZoomLabel);
zoomSlider = new JSlider(); zoomSlider = new JSlider();
@@ -357,12 +389,18 @@ public class Workspace extends JFrame {
statusPanel.add(rightSide, BorderLayout.LINE_END); statusPanel.add(rightSide, BorderLayout.LINE_END);
hideStatusBarComponents();
return statusPanel;
}
private void hideStatusBarComponents() {
viewCountLabel.setVisible(false); viewCountLabel.setVisible(false);
zoomSlider.setVisible(false); zoomSlider.setVisible(false);
minZoomLabel.setVisible(false); minZoomLabel.setVisible(false);
maxZoomLabel.setVisible(false); maxZoomLabel.setVisible(false);
filterLabel.setVisible(false);
return statusPanel; filterText.setVisible(false);
} }
private JToolBar buildToolBar() { private JToolBar buildToolBar() {
@@ -513,10 +551,7 @@ public class Workspace extends JFrame {
} }
private void toggleGraphView() { private void toggleGraphView() {
viewCountLabel.setVisible(true); showStatusBarComponents();
zoomSlider.setVisible(true);
minZoomLabel.setVisible(true);
maxZoomLabel.setVisible(true);
screenViewer.stop(); screenViewer.stop();
mainPanel.remove(pixelPerfectPanel); mainPanel.remove(pixelPerfectPanel);
@@ -526,6 +561,15 @@ public class Workspace extends JFrame {
repaint(); 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() { private void togglePixelPerfectView() {
if (pixelPerfectPanel == null) { if (pixelPerfectPanel == null) {
pixelPerfectPanel = buildPixelPerfectPanel(); pixelPerfectPanel = buildPixelPerfectPanel();
@@ -534,10 +578,7 @@ public class Workspace extends JFrame {
screenViewer.start(); screenViewer.start();
} }
viewCountLabel.setVisible(false); hideStatusBarComponents();
zoomSlider.setVisible(false);
minZoomLabel.setVisible(false);
maxZoomLabel.setVisible(false);
mainPanel.remove(mainSplitter); mainPanel.remove(mainSplitter);
mainPanel.add(pixelPerfectPanel, BorderLayout.CENTER); mainPanel.add(pixelPerfectPanel, BorderLayout.CENTER);
@@ -602,10 +643,7 @@ public class Workspace extends JFrame {
graphViewButton.setEnabled(true); graphViewButton.setEnabled(true);
pixelPerfectViewButton.setEnabled(true); pixelPerfectViewButton.setEnabled(true);
viewCountLabel.setVisible(true); showStatusBarComponents();
zoomSlider.setVisible(true);
minZoomLabel.setVisible(true);
maxZoomLabel.setVisible(true);
} }
sceneView = scene.createView(); sceneView = scene.createView();
@@ -776,10 +814,7 @@ public class Workspace extends JFrame {
pixelPerfectPanel = mainSplitter = null; pixelPerfectPanel = mainSplitter = null;
graphViewButton.setSelected(true); graphViewButton.setSelected(true);
viewCountLabel.setVisible(false); hideStatusBarComponents();
zoomSlider.setVisible(false);
minZoomLabel.setVisible(false);
maxZoomLabel.setVisible(false);
saveMenuItem.setEnabled(false); saveMenuItem.setEnabled(false);
showDevicesMenuItem.setEnabled(false); showDevicesMenuItem.setEnabled(false);
@@ -865,6 +900,34 @@ public class Workspace extends JFrame {
}); });
} }
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() { public void beginTask() {
progress.setVisible(true); progress.setVisible(true);
} }

View File

@@ -113,7 +113,7 @@ knownTests=(
# system-wide tests # system-wide tests
"framework frameworks/base/tests/FrameworkTest # com.android.frameworktest.AllTests com.android.frameworktest.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 #" "smoke frameworks/base/tests/SmokeTest com.android.smoketest # com.android.smoketest.tests #"
"core frameworks/base/tests/CoreTests # android.core.CoreTests android.core #" "core frameworks/base/tests/CoreTests # android.core.CoreTests android.core #"
"libcore frameworks/base/tests/CoreTests # android.core.JavaTests android.core #" "libcore frameworks/base/tests/CoreTests # android.core.JavaTests android.core #"

View File

@@ -23,9 +23,9 @@ set prog=%~f0
rem Grab current directory before we change it rem Grab current directory before we change it
set workdir=%cd% set workdir=%cd%
rem Change current directory to where ddms is, to avoid issues with directories rem Change current directory and drive to where the script is, to avoid
rem containing whitespaces. rem issues with directories containing whitespaces.
cd %~dp0 cd /d %~dp0
set jarfile=sdkmanager.jar set jarfile=sdkmanager.jar
set frameworkdir= set frameworkdir=

View File

@@ -383,24 +383,28 @@ class Main {
*/ */
private void displayAvdList() { private void displayAvdList() {
try { try {
AvdManager avdManager = new AvdManager(mSdkManager, null /* sdklog */); AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
mSdkLog.printf("Available Android Virtual Devices:\n"); mSdkLog.printf("Available Android Virtual Devices:\n");
int index = 1; AvdInfo[] avds = avdManager.getAvds();
for (AvdInfo info : avdManager.getAvds()) { for (int index = 0 ; index < avds.length ; index++) {
mSdkLog.printf("[%d] %s\n", index, info.getName()); AvdInfo info = avds[index];
mSdkLog.printf(" Path: %s\n", info.getPath()); 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 // get the target of the AVD
IAndroidTarget target = info.getTarget(); IAndroidTarget target = info.getTarget();
if (target.isPlatform()) { 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()); target.getApiVersionNumber());
} else { } else {
mSdkLog.printf(" Target: %s (%s)\n", target.getName(), target mSdkLog.printf(" Target: %s (%s)\n", target.getName(), target
.getVendor()); .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()); .getApiVersionName(), target.getApiVersionNumber());
} }
@@ -408,17 +412,15 @@ class Main {
Map<String, String> properties = info.getProperties(); Map<String, String> properties = info.getProperties();
String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME); String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME);
if (skin != null) { if (skin != null) {
mSdkLog.printf(" Skin: %s\n", skin); mSdkLog.printf(" Skin: %s\n", skin);
} }
String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE); String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE);
if (sdcard == null) { if (sdcard == null) {
sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH); sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH);
} }
if (sdcard != null) { if (sdcard != null) {
mSdkLog.printf(" Sdcard: %s\n", sdcard); mSdkLog.printf(" Sdcard: %s\n", sdcard);
} }
index++;
} }
} catch (AndroidLocationException e) { } catch (AndroidLocationException e) {
errorAndExit(e.getMessage()); errorAndExit(e.getMessage());
@@ -499,7 +501,7 @@ class Main {
// Is it NNNxMMM? // Is it NNNxMMM?
if (!valid) { if (!valid) {
valid = skin.matches("[0-9]{2,}x[0-9]{2,}"); valid = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin).matches();
} }
if (!valid) { if (!valid) {

View File

@@ -103,6 +103,8 @@ public final class SdkConstants {
public final static String FD_ASSETS = "assets"; //$NON-NLS-1$ public final static String FD_ASSETS = "assets"; //$NON-NLS-1$
/** Default source folder name, i.e. "src" */ /** Default source folder name, i.e. "src" */
public final static String FD_SOURCES = "src"; //$NON-NLS-1$ 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" /** 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 * 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 * that's what we use in ant for both .jar and .so and we need to make the 2 development ways

View File

@@ -55,6 +55,12 @@ public final class AvdManager {
public final static String AVD_INI_IMAGES_1 = "image.sysdir.1"; public final static String AVD_INI_IMAGES_1 = "image.sysdir.1";
public final static String AVD_INI_IMAGES_2 = "image.sysdir.2"; 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 USERDATA_IMG = "userdata.img";
private final static String CONFIG_INI = "config.ini"; private final static String CONFIG_INI = "config.ini";
private final static String SDCARD_IMG = "sdcard.img"; private final static String SDCARD_IMG = "sdcard.img";
@@ -255,16 +261,21 @@ public final class AvdManager {
skinName = target.getDefaultSkin(); skinName = target.getDefaultSkin();
} }
// get the path of the skin (relative to the SDK) if (NUMERIC_SKIN_SIZE.matcher(skinName).matches()) {
// assume skin name is valid // Skin name is an actual screen resolution, no skin.path
String skinPath = getSkinRelativePath(skinName, target, log); values.put(AVD_INI_SKIN_NAME, skinName);
if (skinPath == null) { } else {
needCleanup = true; // get the path of the skin (relative to the SDK)
return null; // 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_PATH, skinPath);
values.put(AVD_INI_SKIN_NAME, skinName); values.put(AVD_INI_SKIN_NAME, skinName);
}
if (sdcard != null) { if (sdcard != null) {
File sdcardFile = new File(sdcard); File sdcardFile = new File(sdcard);

View File

@@ -25,24 +25,30 @@ import java.util.Map.Entry;
* Helper class to read and write Apk Configuration into a {@link ProjectProperties} file. * Helper class to read and write Apk Configuration into a {@link ProjectProperties} file.
*/ */
public class ApkConfigurationHelper { 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. * 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. * <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) { public static Map<String, String> getConfigs(ProjectProperties properties) {
HashMap<String, String> configMap = new HashMap<String, String>(); HashMap<String, String> configMap = new HashMap<String, String>();
// get the list of configs. // get the list of configs.
String configList = properties.getProperty(ProjectProperties.PROPERTY_CONFIGS); String configList = properties.getProperty(ProjectProperties.PROPERTY_APK_CONFIGS);
if (configList != null) { if (configList != null) {
// this is a comma separated list // this is a comma separated list
String[] configs = configList.split(","); //$NON-NLS-1$ String[] configs = configList.split(","); //$NON-NLS-1$
// read the value of each config and store it in a map // read the value of each config and store it in a map
for (String config : configs) { for (String config : configs) {
String configValue = properties.getProperty(config); config = config.trim();
String configValue = properties.getProperty(CONFIG_PREFIX + config);
if (configValue != null) { if (configValue != null) {
configMap.put(config, configValue); configMap.put(config, configValue);
} }
@@ -54,6 +60,10 @@ public class ApkConfigurationHelper {
/** /**
* Writes the Apk Configurations from a given map into a {@link ProjectProperties}. * 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 * @return true if the {@link ProjectProperties} contained Apk Configuration that were not
* present in the map. * present in the map.
*/ */
@@ -62,17 +72,20 @@ public class ApkConfigurationHelper {
// in case a config was removed. // in case a config was removed.
// get the list of configs. // get the list of configs.
String configList = properties.getProperty(ProjectProperties.PROPERTY_CONFIGS); String configList = properties.getProperty(ProjectProperties.PROPERTY_APK_CONFIGS);
// this is a comma separated list
String[] configs = configList.split(","); //$NON-NLS-1$
boolean hasRemovedConfig = false; boolean hasRemovedConfig = false;
for (String config : configs) { if (configList != null) {
if (configMap.containsKey(config) == false) { // this is a comma separated list
hasRemovedConfig = true; String[] configs = configList.split(","); //$NON-NLS-1$
properties.removeProperty(config);
for (String config : configs) {
config = config.trim();
if (configMap.containsKey(config) == false) {
hasRemovedConfig = true;
properties.removeProperty(CONFIG_PREFIX + config);
}
} }
} }
@@ -84,9 +97,9 @@ public class ApkConfigurationHelper {
sb.append(","); sb.append(",");
} }
sb.append(entry.getKey()); 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; return hasRemovedConfig;
} }

View File

@@ -209,7 +209,7 @@ public class ProjectCreator {
} }
// create the source folder and the java package folders. // 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); File sourceFolder = createDirs(projectFolder, srcFolderPath);
String javaTemplate = "java_file.template"; String javaTemplate = "java_file.template";
String activityFileName = activityName + ".java"; String activityFileName = activityName + ".java";
@@ -220,6 +220,10 @@ public class ProjectCreator {
installTemplate(javaTemplate, new File(sourceFolder, activityFileName), installTemplate(javaTemplate, new File(sourceFolder, activityFileName),
keywords, target); keywords, target);
// create the generate source folder
srcFolderPath = SdkConstants.FD_GEN_SOURCES + File.separator + packagePath;
sourceFolder = createDirs(projectFolder, srcFolderPath);
// create other useful folders // create other useful folders
File resourceFodler = createDirs(projectFolder, SdkConstants.FD_RESOURCES); File resourceFodler = createDirs(projectFolder, SdkConstants.FD_RESOURCES);
createDirs(projectFolder, SdkConstants.FD_OUTPUT); createDirs(projectFolder, SdkConstants.FD_OUTPUT);

View File

@@ -33,7 +33,7 @@ import java.util.Map.Entry;
public final class ProjectProperties { public final class ProjectProperties {
/** The property name for the project target */ /** The property name for the project target */
public final static String PROPERTY_TARGET = "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 final static String PROPERTY_SDK = "sdk-location";
public static enum PropertyType { public static enum PropertyType {
@@ -98,7 +98,19 @@ public final class ProjectProperties {
// 1-------10--------20--------30--------40--------50--------60--------70--------80 // 1-------10--------20--------30--------40--------50--------60--------70--------80
COMMENT_MAP.put(PROPERTY_TARGET, COMMENT_MAP.put(PROPERTY_TARGET,
"# Project target.\n"); "# 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" + "# For customization when using a Version Control System, please read the\n" +
"# header note.\n"); "# header note.\n");
} }

View File

@@ -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;
}
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -48,32 +48,57 @@ import java.util.ArrayList;
*/ */
public class SdkTargetSelector { public class SdkTargetSelector {
private final IAndroidTarget[] mTargets; private IAndroidTarget[] mTargets;
private final boolean mAllowSelection;
private final boolean mAllowMultipleSelection; private final boolean mAllowMultipleSelection;
private SelectionListener mSelectionListener; private SelectionListener mSelectionListener;
private Table mTable; private Table mTable;
private Label mDescription; 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. * Creates a new SDK Target Selector.
* *
* @param parent The parent composite where the selector will be added. * @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. * @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 * @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, public SdkTargetSelector(Composite parent, IAndroidTarget[] targets,
boolean allowSelection,
boolean allowMultipleSelection) { boolean allowMultipleSelection) {
mTargets = targets;
// Layout has 1 column // Layout has 1 column
Composite group = new Composite(parent, SWT.NONE); mInnerGroup = new Composite(parent, SWT.NONE);
group.setLayout(new GridLayout()); mInnerGroup.setLayout(new GridLayout());
group.setLayoutData(new GridData(GridData.FILL_BOTH)); mInnerGroup.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setFont(parent.getFont()); mInnerGroup.setFont(parent.getFont());
mAllowSelection = allowSelection;
mAllowMultipleSelection = allowMultipleSelection; 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.setHeaderVisible(true);
mTable.setLinesVisible(false); mTable.setLinesVisible(false);
@@ -84,7 +109,7 @@ public class SdkTargetSelector {
data.verticalAlignment = GridData.FILL; data.verticalAlignment = GridData.FILL;
mTable.setLayoutData(data); mTable.setLayoutData(data);
mDescription = new Label(group, SWT.WRAP); mDescription = new Label(mInnerGroup, SWT.WRAP);
mDescription.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mDescription.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// create the table columns // create the table columns
@@ -93,15 +118,26 @@ public class SdkTargetSelector {
final TableColumn column1 = new TableColumn(mTable, SWT.NONE); final TableColumn column1 = new TableColumn(mTable, SWT.NONE);
column1.setText("Vendor"); column1.setText("Vendor");
final TableColumn column2 = new TableColumn(mTable, SWT.NONE); final TableColumn column2 = new TableColumn(mTable, SWT.NONE);
column2.setText("API Level"); column2.setText("Version");
final TableColumn column3 = new TableColumn(mTable, SWT.NONE); final TableColumn column3 = new TableColumn(mTable, SWT.NONE);
column3.setText("SDK"); column3.setText("API Level");
adjustColumnsWidth(mTable, column0, column1, column2, column3); adjustColumnsWidth(mTable, column0, column1, column2, column3);
setupSelectionListener(mTable); setupSelectionListener(mTable);
fillTable(mTable); setTargets(targets);
setupTooltip(mTable); 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. * Returns the list of known targets.
@@ -112,6 +148,16 @@ public class SdkTargetSelector {
return mTargets; return mTargets;
} }
/**
* 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. * Sets a selection listener. Set it to null to remove it.
* The listener will be called <em>after</em> this table processed its selection * The listener will be called <em>after</em> this table processed its selection
@@ -139,6 +185,10 @@ public class SdkTargetSelector {
* @return true if the target could be selected, false otherwise. * @return true if the target could be selected, false otherwise.
*/ */
public boolean setSelection(IAndroidTarget target) { public boolean setSelection(IAndroidTarget target) {
if (!mAllowSelection) {
return false;
}
boolean found = false; boolean found = false;
boolean modified = false; boolean modified = false;
for (TableItem i : mTable.getItems()) { for (TableItem i : mTable.getItems()) {
@@ -224,6 +274,10 @@ public class SdkTargetSelector {
* double-clicked (aka "the default selection"). * double-clicked (aka "the default selection").
*/ */
private void setupSelectionListener(final Table table) { private void setupSelectionListener(final Table table) {
if (!mAllowSelection) {
return;
}
// Add a selection listener that will check/uncheck items when they are double-clicked // Add a selection listener that will check/uncheck items when they are double-clicked
table.addSelectionListener(new SelectionListener() { table.addSelectionListener(new SelectionListener() {
/** Default selection means double-click on "most" platforms */ /** Default selection means double-click on "most" platforms */
@@ -281,6 +335,9 @@ public class SdkTargetSelector {
* </ul> * </ul>
*/ */
private void fillTable(final Table table) { private void fillTable(final Table table) {
table.removeAll();
if (mTargets != null && mTargets.length > 0) { if (mTargets != null && mTargets.length > 0) {
table.setEnabled(true); table.setEnabled(true);
for (IAndroidTarget target : mTargets) { for (IAndroidTarget target : mTargets) {

View File

@@ -20,9 +20,9 @@ rem Set up prog to be the path of this script, including following symlinks,
rem and set up progdir to be the fully-qualified pathname of its directory. rem and set up progdir to be the fully-qualified pathname of its directory.
set prog=%~f0 set prog=%~f0
rem Change current directory to where traceview is, to avoid issues with directories rem Change current directory and drive to where traceview.bat is, to avoid
rem containing whitespaces. rem issues with directories containing whitespaces.
cd %~dp0 cd /d %~dp0
set jarfile=traceview.jar set jarfile=traceview.jar
set frameworkdir= set frameworkdir=