Merge change 3933 into donut
* changes:
- Moved the build variants descriptions into build_system - Added Makefile stub into sensors.jd as a starting point - Small cleanup to build_new_device to reference the PRODUCT_* parameters - Fixed URL in the README for app engine testing
diff --git a/apps/Development/AndroidManifest.xml b/apps/Development/AndroidManifest.xml
index 316cbf5..4f8df3e 100644
--- a/apps/Development/AndroidManifest.xml
+++ b/apps/Development/AndroidManifest.xml
@@ -131,5 +131,11 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+ <activity android:name="PermissionDetails" android:label="Permission Info">
+ <intent-filter>
+ <action android:name="com.android.development.VIEW_PERMISSION" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/apps/Development/res/layout/permission_details.xml b/apps/Development/res/layout/permission_details.xml
new file mode 100755
index 0000000..d6635f8
--- /dev/null
+++ b/apps/Development/res/layout/permission_details.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fillViewport="true"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="4dip" >
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="4dip" >
+ <TextView android:id="@+id/perm_name_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/perm_name_text"
+ android:textStyle="bold" />
+ <TextView android:id="@+id/perm_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="4dip" >
+ <TextView android:id="@+id/perm_desc_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/perm_desc_text"
+ android:textStyle="bold" />
+ <TextView android:id="@+id/perm_desc"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="4dip" >
+ <TextView android:id="@+id/perm_group_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/perm_group_text"
+ android:textStyle="bold" />
+ <TextView android:id="@+id/perm_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="4dip" >
+ <TextView android:id="@+id/perm_protection_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/perm_protection_text"
+ android:textStyle="bold" />
+ <TextView android:id="@+id/perm_protection"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="4dip" >
+ <TextView android:id="@+id/perm_source_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/perm_source_text"
+ android:textStyle="bold" />
+ <TextView android:id="@+id/perm_source"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="4dip" >
+ <TextView android:id="@+id/source_uid_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/source_uid_text"
+ android:textStyle="bold" />
+ <TextView android:id="@+id/source_uid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+ <LinearLayout android:id="@+id/shared_pkgs_panel"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:padding="4dip" >
+ <TextView android:id="@+id/shared_pkgs_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/shared_pkgs_text"
+ android:textStyle="bold" />
+ <TextView android:id="@+id/shared_pkgs"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+ <TextView android:id="@+id/perm_list_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="3dip"
+ android:text="@string/perm_list_header_text"
+ android:textStyle="bold" />
+ <ListView android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+</ScrollView>
+
diff --git a/apps/Development/res/layout/pkg_list_item.xml b/apps/Development/res/layout/pkg_list_item.xml
new file mode 100755
index 0000000..29de423
--- /dev/null
+++ b/apps/Development/res/layout/pkg_list_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:orientation="horizontal"
+ android:paddingRight="6dip"
+ android:paddingLeft="6dip"
+ android:paddingTop="5dip"
+ android:paddingBottom="5dip"
+ android:gravity="center_vertical" >
+ <TextView android:id="@+id/pkg_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textStyle="bold"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:layout_marginBottom="2dip" />
+</LinearLayout>
+
diff --git a/apps/Development/res/values/strings.xml b/apps/Development/res/values/strings.xml
index cdfdf40..4c84548 100644
--- a/apps/Development/res/values/strings.xml
+++ b/apps/Development/res/values/strings.xml
@@ -120,4 +120,17 @@
<string name="navigation_label">navigation:</string>
<string name="five_way_nav_label">five way nav:</string>
<string name="gles_version_label">GLES Version:</string>
+
+ <!-- Permission details related string attribtues -->
+ <string name="perm_name_text">Name : </string>
+ <string name="dialog_title_error">Error</string>
+ <string name="invalid_perm_name">Invalid Permission Name</string>
+ <string name="ok">Ok</string>
+ <string name="perm_desc_text">Desc : </string>
+ <string name="perm_group_text">Group : </string>
+ <string name="perm_protection_text">Protection Level : </string>
+ <string name="perm_source_text">Source package : </string>
+ <string name="perm_list_header_text">Apps using permission </string>
+ <string name="source_uid_text">Source uid : </string>
+ <string name="shared_pkgs_text">Packages accessing via shared uid : </string>
</resources>
diff --git a/apps/Development/src/com/android/development/PermissionDetails.java b/apps/Development/src/com/android/development/PermissionDetails.java
new file mode 100644
index 0000000..26fea5d
--- /dev/null
+++ b/apps/Development/src/com/android/development/PermissionDetails.java
@@ -0,0 +1,305 @@
+/*
+**
+** Copyright 2006, 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.development;
+
+import com.android.development.R;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.AdapterView.OnItemClickListener;
+
+/**
+ * This activity displays permission details including
+ * the list of apps using a permission.
+ */
+public class PermissionDetails extends Activity implements OnCancelListener, OnItemClickListener {
+ private static final String TAG = "PermissionDetails";
+ PackageManager mPm;
+ // layout inflater object used to inflate views
+ private LayoutInflater mInflater;
+ private AppListAdapter mAdapter;
+
+ // Dialog related
+ private static final int DLG_BASE = 0;
+ private static final int DLG_ERROR = DLG_BASE + 1;
+ private static final String PROTECTION_NORMAL="Normal";
+ private static final String PROTECTION_DANGEROUS="Dangerous";
+ private static final String PROTECTION_SIGNATURE="Signature";
+ private static final String PROTECTION_SIGNATURE_OR_SYSTEM="SignatureOrSystem";
+
+ private static final String KEY_APPS_USING_PERM="AppsUsingPerm";
+
+ private static final int HANDLER_MSG_BASE = 0;
+ private static final int HANDLER_MSG_GET_APPS = HANDLER_MSG_BASE + 1;
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case HANDLER_MSG_GET_APPS:
+ ArrayList<PackageInfo> appList = msg.getData().getParcelableArrayList(KEY_APPS_USING_PERM);
+ createAppList(appList);
+ break;
+ }
+ }
+ };
+
+ // View Holder used when displaying views
+ static class AppViewHolder {
+ TextView pkgName;
+ }
+
+ class AppListAdapter extends BaseAdapter {
+ private List<PackageInfo> mList;
+
+ AppListAdapter(List<PackageInfo> list) {
+ mList = list;
+ }
+
+ public int getCount() {
+ return mList.size();
+ }
+
+ public Object getItem(int position) {
+ return mList.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public PackageInfo getPkg(int position) {
+ return mList.get(position);
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // A ViewHolder keeps references to children views to avoid unneccessary calls
+ // to findViewById() on each row.
+ AppViewHolder holder;
+
+ // When convertView is not null, we can reuse it directly, there is no need
+ // to reinflate it. We only inflate a new View when the convertView supplied
+ // by ListView is null.
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.pkg_list_item, null);
+
+ // Creates a ViewHolder and store references to the two children views
+ // we want to bind data to.
+ holder = new AppViewHolder();
+ holder.pkgName = (TextView) convertView.findViewById(R.id.pkg_name);
+ convertView.setTag(holder);
+ } else {
+ // Get the ViewHolder back to get fast access to the TextView
+ // and the ImageView.
+ holder = (AppViewHolder) convertView.getTag();
+ }
+ // Bind the data efficiently with the holder
+ PackageInfo pInfo = mList.get(position);
+ holder.pkgName.setText(pInfo.packageName);
+ return convertView;
+ }
+ }
+
+ private void createAppList(List<PackageInfo> list) {
+ Log.i(TAG, "list.size=" + list.size());
+ for (PackageInfo pkg : list) {
+ Log.i(TAG, "Adding pkg : " + pkg.packageName);
+ }
+ ListView listView = (ListView)findViewById(android.R.id.list);
+ mAdapter = new AppListAdapter(list);
+ ListView lv= (ListView) findViewById(android.R.id.list);
+ lv.setOnItemClickListener(this);
+ lv.setSaveEnabled(true);
+ lv.setItemsCanFocus(true);
+ listView.setAdapter(mAdapter);
+ }
+
+ private void getAppsUsingPerm(PermissionInfo pInfo) {
+ List<PackageInfo> list = mPm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
+ HashSet<PackageInfo> set = new HashSet<PackageInfo>();
+ for (PackageInfo pkg : list) {
+ if (pkg.requestedPermissions == null) {
+ continue;
+ }
+ for (String perm : pkg.requestedPermissions) {
+ if (perm.equalsIgnoreCase(pInfo.name)) {
+ Log.i(TAG, "Pkg:" + pkg.packageName+" uses permission");
+ set.add(pkg);
+ break;
+ }
+ }
+ }
+ ArrayList<PackageInfo> retList = new ArrayList<PackageInfo>();
+ for (PackageInfo pkg : set) {
+ retList.add(pkg);
+ }
+ Message msg = mHandler.obtainMessage(HANDLER_MSG_GET_APPS);
+ Bundle data = msg.getData();
+ data.putParcelableArrayList(KEY_APPS_USING_PERM, retList);
+ mHandler.dispatchMessage(msg);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.permission_details);
+ Intent intent = getIntent();
+ String permName = intent.getStringExtra("permission");
+ if(permName == null) {
+ showDialogInner(DLG_ERROR);
+ }
+ mPm = getPackageManager();
+ mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ PermissionInfo pInfo = null;
+ try {
+ pInfo = mPm.getPermissionInfo(permName,
+ PackageManager.GET_PERMISSIONS);
+ } catch (NameNotFoundException e) {
+ showDialogInner(DLG_ERROR);
+ }
+ setTextView(R.id.perm_name, pInfo.name);
+ setTextView(R.id.perm_desc, pInfo.descriptionRes);
+ setTextView(R.id.perm_group, pInfo.group);
+ setProtectionLevel(R.id.perm_protection, pInfo.protectionLevel);
+ setTextView(R.id.perm_source, pInfo.packageName);
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = mPm.getApplicationInfo(pInfo.packageName, 0);
+ String uidStr = mPm.getNameForUid(appInfo.uid);
+ setTextView(R.id.source_uid, uidStr);
+ } catch (NameNotFoundException e) {
+ }
+ boolean sharedVisibility = false;
+ // List of apps acquiring access via shared user id
+ LinearLayout sharedPanel = (LinearLayout) findViewById(R.id.shared_pkgs_panel);
+ if (appInfo != null) {
+ String[] sharedList = mPm.getPackagesForUid(appInfo.uid);
+ if ((sharedList != null) && (sharedList.length > 1)) {
+ sharedVisibility = true;
+ TextView label = (TextView) sharedPanel.findViewById(R.id.shared_pkgs_label);
+ TextView sharedView = (TextView) sharedPanel.findViewById(R.id.shared_pkgs);
+ label.setVisibility(View.VISIBLE);
+ StringBuilder buff = new StringBuilder();
+ buff.append(sharedList[0]);
+ for (int i = 1; i < sharedList.length; i++) {
+ buff.append(", ");
+ buff.append(sharedList[i]);
+ }
+ sharedView.setText(buff.toString());
+ }
+ }
+ if (sharedVisibility) {
+ sharedPanel.setVisibility(View.VISIBLE);
+ } else {
+ sharedPanel.setVisibility(View.GONE);
+ }
+ getAppsUsingPerm(pInfo);
+ }
+
+ private void setProtectionLevel(int viewId, int protectionLevel) {
+ String levelStr = "";
+ if (protectionLevel == PermissionInfo.PROTECTION_NORMAL) {
+ levelStr = PROTECTION_NORMAL;
+ } else if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) {
+ levelStr = PROTECTION_DANGEROUS;
+ } else if (protectionLevel == PermissionInfo.PROTECTION_SIGNATURE) {
+ levelStr = PROTECTION_SIGNATURE;
+ } else if (protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) {
+ levelStr = PROTECTION_SIGNATURE_OR_SYSTEM;
+ } else {
+ levelStr = "Invalid";
+ }
+ setTextView(viewId, levelStr);
+ }
+
+ private void setTextView(int viewId, int textId) {
+ TextView view = (TextView)findViewById(viewId);
+ view.setText(textId);
+ }
+
+ private void setTextView(int viewId, String text) {
+ TextView view = (TextView)findViewById(viewId);
+ view.setText(text);
+ }
+
+ @Override
+ public Dialog onCreateDialog(int id) {
+ if (id == DLG_ERROR) {
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.dialog_title_error)
+ .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }})
+ .setMessage(R.string.invalid_perm_name)
+ .setOnCancelListener(this)
+ .create();
+ }
+ return null;
+ }
+
+ private void showDialogInner(int id) {
+ // TODO better fix for this? Remove dialog so that it gets created again
+ removeDialog(id);
+ showDialog(id);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ // TODO Launch app details activity
+ }
+}
diff --git a/apps/Fallback/res/values-pt/strings.xml b/apps/Fallback/res/values-pt/strings.xml
new file mode 100644
index 0000000..999450c
--- /dev/null
+++ b/apps/Fallback/res/values-pt/strings.xml
@@ -0,0 +1,21 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="appTitle">"Fallback"</string>
+ <string name="title">"Ação não suportada"</string>
+ <string name="error">"Esta ação não é suportada no momento."</string>
+</resources>
diff --git a/apps/Term/src/com/android/term/Term.java b/apps/Term/src/com/android/term/Term.java
index 1f43843..34cd7e1 100644
--- a/apps/Term/src/com/android/term/Term.java
+++ b/apps/Term/src/com/android/term/Term.java
@@ -38,7 +38,6 @@
import android.os.Exec;
import android.os.Handler;
import android.os.Message;
-import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.Log;
@@ -54,7 +53,6 @@
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -105,8 +103,6 @@
*/
private FileDescriptor mTermFd;
- private boolean mShellRunning;
-
/**
* Used to send data to the remote process.
*/
@@ -185,7 +181,9 @@
mKeyListener = new TermKeyListener();
mEmulatorView.setFocusable(true);
+ mEmulatorView.setFocusableInTouchMode(true);
mEmulatorView.requestFocus();
+ mEmulatorView.register(mKeyListener);
updatePrefs();
}
@@ -194,14 +192,11 @@
int[] processId = new int[1];
createSubprocess(processId);
- mShellRunning = true;
-
final int procId = processId[0];
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
- mShellRunning = false;
}
};
@@ -1357,7 +1352,7 @@
printableB = ' ';
}
Log.w(Term.LOG_TAG, "'" + Character.toString(printableB)
- + "' (" + Integer.toString((int) b) + ")");
+ + "' (" + Integer.toString(b) + ")");
}
process(b);
mProcessedCharCount++;
@@ -2084,7 +2079,7 @@
buf.append(" char: '");
buf.append((char) b);
buf.append("' (");
- buf.append((int) b);
+ buf.append(b);
buf.append(")");
boolean firstArg = true;
for (int i = 0; i <= mArgIndex; i++) {
@@ -2604,6 +2599,7 @@
private GestureDetector mGestureDetector;
private float mScrollRemainder;
+ private TermKeyListener mKeyListener;
/**
* Our message handler class. Implements a periodic callback.
@@ -2615,6 +2611,7 @@
*
* @param msg The callback message.
*/
+ @Override
public void handleMessage(Message msg) {
if (msg.what == UPDATE) {
update();
@@ -2627,6 +2624,10 @@
commonConstructor();
}
+ public void register(TermKeyListener listener) {
+ mKeyListener = listener;
+ }
+
public void setColors(int foreground, int background) {
mForeground = foreground;
mBackground = background;
@@ -2651,52 +2652,64 @@
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new BaseInputConnection(this, false) {
+ @Override
public boolean beginBatchEdit() {
return true;
}
+ @Override
public boolean clearMetaKeyStates(int states) {
return true;
}
+ @Override
public boolean commitCompletion(CompletionInfo text) {
return true;
}
+ @Override
public boolean commitText(CharSequence text, int newCursorPosition) {
sendText(text);
return true;
}
+ @Override
public boolean deleteSurroundingText(int leftLength, int rightLength) {
return true;
}
+ @Override
public boolean endBatchEdit() {
return true;
}
+ @Override
public boolean finishComposingText() {
return true;
}
+ @Override
public int getCursorCapsMode(int reqModes) {
return 0;
}
+ @Override
public ExtractedText getExtractedText(ExtractedTextRequest request,
int flags) {
return null;
}
+ @Override
public CharSequence getTextAfterCursor(int n, int flags) {
return null;
}
+ @Override
public CharSequence getTextBeforeCursor(int n, int flags) {
return null;
}
+ @Override
public boolean performEditorAction(int actionCode) {
if(actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) {
// The "return" key has been pressed on the IME.
@@ -2706,14 +2719,17 @@
return false;
}
+ @Override
public boolean performContextMenuAction(int id) {
return true;
}
+ @Override
public boolean performPrivateCommand(String action, Bundle data) {
return true;
}
+ @Override
public boolean sendKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch(event.getKeyCode()) {
@@ -2725,17 +2741,19 @@
return true;
}
+ @Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
return true;
}
+ @Override
public boolean setSelection(int start, int end) {
return true;
}
private void sendChar(int c) {
try {
- mTermOut.write(c);
+ mapAndSend(c);
} catch (IOException ex) {
}
@@ -2745,11 +2763,16 @@
try {
for(int i = 0; i < n; i++) {
char c = text.charAt(i);
- mTermOut.write(c);
+ mapAndSend(c);
}
} catch (IOException e) {
}
}
+
+ private void mapAndSend(int c) throws IOException {
+ mTermOut.write(
+ mKeyListener.mapControlChar(c));
+ }
};
}
@@ -3161,6 +3184,35 @@
}
}
+ public int mapControlChar(int ch) {
+ int result = ch;
+ if (mControlKey.isActive()) {
+ // Search is the control key.
+ if (result >= 'a' && result <= 'z') {
+ result = (char) (result - 'a' + '\001');
+ } else if (result == ' ') {
+ result = 0;
+ } else if ((result == '[') || (result == '1')) {
+ result = 27;
+ } else if ((result == '\\') || (result == '.')) {
+ result = 28;
+ } else if ((result == ']') || (result == '0')) {
+ result = 29;
+ } else if ((result == '^') || (result == '6')) {
+ result = 30; // control-^
+ } else if ((result == '_') || (result == '5')) {
+ result = 31;
+ }
+ }
+
+ if (result > -1) {
+ mAltKey.adjustAfterKeypress();
+ mCapKey.adjustAfterKeypress();
+ mControlKey.adjustAfterKeypress();
+ }
+ return result;
+ }
+
/**
* Handle a keyDown event.
*
@@ -3201,30 +3253,7 @@
}
}
- if (mControlKey.isActive()) {
- // Search is the control key.
- if (result >= 'a' && result <= 'z') {
- result = (char) (result - 'a' + '\001');
- } else if (result == ' ') {
- result = 0;
- } else if ((result == '[') || (result == '1')) {
- result = 27;
- } else if ((result == '\\') || (result == '.')) {
- result = 28;
- } else if ((result == ']') || (result == '0')) {
- result = 29;
- } else if ((result == '^') || (result == '6')) {
- result = 30; // control-^
- } else if ((result == '_') || (result == '5')) {
- result = 31;
- }
- }
-
- if (result > -1) {
- mAltKey.adjustAfterKeypress();
- mCapKey.adjustAfterKeypress();
- mControlKey.adjustAfterKeypress();
- }
+ result = mapControlChar(result);
return result;
}
diff --git a/ide/eclipse/.classpath b/ide/eclipse/.classpath
index 281349b..71e8d29 100644
--- a/ide/eclipse/.classpath
+++ b/ide/eclipse/.classpath
@@ -66,6 +66,7 @@
<classpathentry kind="src" path="development/samples/SkeletonApp/tests/src"/>
<classpathentry kind="src" path="development/samples/Snake/src"/>
<classpathentry kind="src" path="development/samples/Snake/tests/src"/>
+ <classpathentry kind="src" path="development/apps/Term/src"/>
<classpathentry kind="src" path="dalvik/libcore/annotation/src/main/java"/>
<classpathentry kind="src" path="dalvik/libcore/archive/src/main/java"/>
<classpathentry kind="src" path="dalvik/libcore/auth/src/main/java"/>
diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml
index 6fbe7cd..d11e5ee 100644
--- a/testrunner/test_defs.xml
+++ b/testrunner/test_defs.xml
@@ -173,7 +173,7 @@
<test name="cts-permission"
build_path="cts/tests"
package="com.android.cts.permission"
- runner="android.test.InstrumentationCtsTestRunner"
+ runner="android.test.InstrumentationTestRunner"
coverage_target="framework"
continuous="true"
cts="true" />
@@ -385,6 +385,12 @@
class="com.android.email.SmallTests"
coverage_target="Email" />
+<test name="globalsearch"
+ build_path="packages/apps/GlobalSearch"
+ package="com.android.globalsearch.tests"
+ coverage_target="GlobalSearch"
+ continuous="true" />
+
<test name="media"
build_path="frameworks/base/media/tests/MediaFrameworkTest"
package="com.android.mediaframeworktest"
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
index 1f3cdb3..0fe592a 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
@@ -192,7 +192,7 @@
*/
public void setProject(IProject project, IAndroidTarget target,
Map<String, String> apkConfigMap) {
- synchronized (mProjectTargetMap) {
+ synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
boolean resolveProject = false;
boolean compileProject = false;
boolean cleanProject = false;
@@ -270,7 +270,7 @@
* Returns the {@link IAndroidTarget} object associated with the given {@link IProject}.
*/
public IAndroidTarget getTarget(IProject project) {
- synchronized (mProjectTargetMap) {
+ synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
IAndroidTarget target = mProjectTargetMap.get(project);
if (target == null) {
// get the value from the project persistent property.
@@ -313,10 +313,12 @@
}
if (sdkStorage != null) {
- Map<String, String> configMap = ApkConfigurationHelper.getConfigs(properties);
+ synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
+ Map<String, String> configMap = ApkConfigurationHelper.getConfigs(properties);
- if (configMap != null) {
- sdkStorage.mProjectApkConfigMap.put(project, configMap);
+ if (configMap != null) {
+ sdkStorage.mProjectApkConfigMap.put(project, configMap);
+ }
}
}
@@ -368,7 +370,7 @@
* Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}.
*/
public AndroidTargetData getTargetData(IAndroidTarget target) {
- synchronized (mTargetDataMap) {
+ synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
return mTargetDataMap.get(target);
}
}
@@ -379,7 +381,9 @@
* config values. The config value can be passed directly to aapt through the -c option.
*/
public Map<String, String> getProjectApkConfigs(IProject project) {
- return mProjectApkConfigMap.get(project);
+ synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
+ return mProjectApkConfigMap.get(project);
+ }
}
/**
@@ -411,7 +415,7 @@
}
void setTargetData(IAndroidTarget target, AndroidTargetData data) {
- synchronized (mTargetDataMap) {
+ synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
mTargetDataMap.put(target, data);
}
}
@@ -455,7 +459,7 @@
public void projectClosed(IProject project) {
// get the target project
- synchronized (mProjectTargetMap) {
+ synchronized (AdtPlugin.getDefault().getSdkLockObject()) {
IAndroidTarget target = mProjectTargetMap.get(project);
if (target != null) {
// get the bridge for the target, and clear the cache for this project.
diff --git a/tools/scripts/app_engine_server/gae_shell/shell.py~ b/tools/scripts/app_engine_server/gae_shell/shell.py~
deleted file mode 100755
index dee9fdb..0000000
--- a/tools/scripts/app_engine_server/gae_shell/shell.py~
+++ /dev/null
@@ -1,308 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2007 Google Inc.
-#
-# 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.
-
-"""
-An interactive, stateful AJAX shell that runs Python code on the server.
-
-Part of http://code.google.com/p/google-app-engine-samples/.
-
-May be run as a standalone app or in an existing app as an admin-only handler.
-Can be used for system administration tasks, as an interactive way to try out
-APIs, or as a debugging aid during development.
-
-The logging, os, sys, db, and users modules are imported automatically.
-
-Interpreter state is stored in the datastore so that variables, function
-definitions, and other values in the global and local namespaces can be used
-across commands.
-
-To use the shell in your app, copy shell.py, static/*, and templates/* into
-your app's source directory. Then, copy the URL handlers from app.yaml into
-your app.yaml.
-
-TODO: unit tests!
-"""
-
-import logging
-import new
-import os
-import pickle
-import sys
-import traceback
-import types
-import wsgiref.handlers
-
-from google.appengine.api import users
-from google.appengine.ext import db
-from google.appengine.ext import webapp
-from google.appengine.ext.webapp import template
-
-
-# Set to True if stack traces should be shown in the browser, etc.
-_DEBUG = True
-
-# The entity kind for shell sessions. Feel free to rename to suit your app.
-_SESSION_KIND = '_Shell_Session'
-
-# Types that can't be pickled.
-UNPICKLABLE_TYPES = (
- types.ModuleType,
- types.TypeType,
- types.ClassType,
- types.FunctionType,
- )
-
-# Unpicklable statements to seed new sessions with.
-INITIAL_UNPICKLABLES = [
- 'import logging',
- 'import os',
- 'import sys',
- 'from google.appengine.ext import db',
- 'from google.appengine.api import users',
- ]
-
-
-class Session(db.Model):
- """A shell session. Stores the session's globals.
-
- Each session globals is stored in one of two places:
-
- If the global is picklable, it's stored in the parallel globals and
- global_names list properties. (They're parallel lists to work around the
- unfortunate fact that the datastore can't store dictionaries natively.)
-
- If the global is not picklable (e.g. modules, classes, and functions), or if
- it was created by the same statement that created an unpicklable global,
- it's not stored directly. Instead, the statement is stored in the
- unpicklables list property. On each request, before executing the current
- statement, the unpicklable statements are evaluated to recreate the
- unpicklable globals.
-
- The unpicklable_names property stores all of the names of globals that were
- added by unpicklable statements. When we pickle and store the globals after
- executing a statement, we skip the ones in unpicklable_names.
-
- Using Text instead of string is an optimization. We don't query on any of
- these properties, so they don't need to be indexed.
- """
- global_names = db.ListProperty(db.Text)
- globals = db.ListProperty(db.Blob)
- unpicklable_names = db.ListProperty(db.Text)
- unpicklables = db.ListProperty(db.Text)
-
- def set_global(self, name, value):
- """Adds a global, or updates it if it already exists.
-
- Also removes the global from the list of unpicklable names.
-
- Args:
- name: the name of the global to remove
- value: any picklable value
- """
- blob = db.Blob(pickle.dumps(value))
-
- if name in self.global_names:
- index = self.global_names.index(name)
- self.globals[index] = blob
- else:
- self.global_names.append(db.Text(name))
- self.globals.append(blob)
-
- self.remove_unpicklable_name(name)
-
- def remove_global(self, name):
- """Removes a global, if it exists.
-
- Args:
- name: string, the name of the global to remove
- """
- if name in self.global_names:
- index = self.global_names.index(name)
- del self.global_names[index]
- del self.globals[index]
-
- def globals_dict(self):
- """Returns a dictionary view of the globals.
- """
- return dict((name, pickle.loads(val))
- for name, val in zip(self.global_names, self.globals))
-
- def add_unpicklable(self, statement, names):
- """Adds a statement and list of names to the unpicklables.
-
- Also removes the names from the globals.
-
- Args:
- statement: string, the statement that created new unpicklable global(s).
- names: list of strings; the names of the globals created by the statement.
- """
- self.unpicklables.append(db.Text(statement))
-
- for name in names:
- self.remove_global(name)
- if name not in self.unpicklable_names:
- self.unpicklable_names.append(db.Text(name))
-
- def remove_unpicklable_name(self, name):
- """Removes a name from the list of unpicklable names, if it exists.
-
- Args:
- name: string, the name of the unpicklable global to remove
- """
- if name in self.unpicklable_names:
- self.unpicklable_names.remove(name)
-
-
-class FrontPageHandler(webapp.RequestHandler):
- """Creates a new session and renders the shell.html template.
- """
-
- def get(self):
- # set up the session. TODO: garbage collect old shell sessions
- session_key = self.request.get('session')
- if session_key:
- session = Session.get(session_key)
- else:
- # create a new session
- session = Session()
- session.unpicklables = [db.Text(line) for line in INITIAL_UNPICKLABLES]
- session_key = session.put()
-
- template_file = os.path.join(os.path.dirname(__file__), 'templates',
- 'shell.html')
- session_url = '/?session=%s' % session_key
- vars = { 'server_software': os.environ['SERVER_SOFTWARE'],
- 'python_version': sys.version,
- 'session': str(session_key),
- 'user': users.get_current_user(),
- 'login_url': users.create_login_url(session_url),
- 'logout_url': users.create_logout_url(session_url),
- }
- rendered = webapp.template.render(template_file, vars, debug=_DEBUG)
- self.response.out.write(rendered)
-
-
-class StatementHandler(webapp.RequestHandler):
- """Evaluates a python statement in a given session and returns the result.
- """
-
- def get(self):
- self.response.headers['Content-Type'] = 'text/plain'
-
- # extract the statement to be run
- statement = self.request.get('statement')
- if not statement:
- return
-
- # the python compiler doesn't like network line endings
- statement = statement.replace('\r\n', '\n')
-
- # add a couple newlines at the end of the statement. this makes
- # single-line expressions such as 'class Foo: pass' evaluate happily.
- statement += '\n\n'
-
- # log and compile the statement up front
- try:
- logging.info('Compiling and evaluating:\n%s' % statement)
- compiled = compile(statement, '<string>', 'single')
- except:
- self.response.out.write(traceback.format_exc())
- return
-
- # create a dedicated module to be used as this statement's __main__
- statement_module = new.module('__main__')
-
- # use this request's __builtin__, since it changes on each request.
- # this is needed for import statements, among other things.
- import __builtin__
- statement_module.__builtins__ = __builtin__
-
- # load the session from the datastore
- session = Session.get(self.request.get('session'))
-
- # swap in our custom module for __main__. then unpickle the session
- # globals, run the statement, and re-pickle the session globals, all
- # inside it.
- old_main = sys.modules.get('__main__')
- try:
- sys.modules['__main__'] = statement_module
- statement_module.__name__ = '__main__'
-
- # re-evaluate the unpicklables
- for code in session.unpicklables:
- exec code in statement_module.__dict__
-
- # re-initialize the globals
- for name, val in session.globals_dict().items():
- try:
- statement_module.__dict__[name] = val
- except:
- msg = 'Dropping %s since it could not be unpickled.\n' % name
- self.response.out.write(msg)
- logging.warning(msg + traceback.format_exc())
- session.remove_global(name)
-
- # run!
- old_globals = dict(statement_module.__dict__)
- try:
- old_stdout = sys.stdout
- old_stderr = sys.stderr
- try:
- sys.stdout = self.response.out
- sys.stderr = self.response.out
- exec compiled in statement_module.__dict__
- finally:
- sys.stdout = old_stdout
- sys.stderr = old_stderr
- except:
- self.response.out.write(traceback.format_exc())
- return
-
- # extract the new globals that this statement added
- new_globals = {}
- for name, val in statement_module.__dict__.items():
- if name not in old_globals or val != old_globals[name]:
- new_globals[name] = val
-
- if True in [isinstance(val, UNPICKLABLE_TYPES)
- for val in new_globals.values()]:
- # this statement added an unpicklable global. store the statement and
- # the names of all of the globals it added in the unpicklables.
- session.add_unpicklable(statement, new_globals.keys())
- logging.debug('Storing this statement as an unpicklable.')
-
- else:
- # this statement didn't add any unpicklables. pickle and store the
- # new globals back into the datastore.
- for name, val in new_globals.items():
- if not name.startswith('__'):
- session.set_global(name, val)
-
- finally:
- sys.modules['__main__'] = old_main
-
- session.put()
-
-
-def main():
- application = webapp.WSGIApplication(
- [('/', FrontPageHandler),
- ('/shell.do', StatementHandler)], debug=_DEBUG)
- wsgiref.handlers.CGIHandler().run(application)
-
-
-if __name__ == '__main__':
- main()
diff --git a/tools/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java b/tools/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
index 4a3c16c..d4da21b 100644
--- a/tools/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
+++ b/tools/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
@@ -85,13 +85,13 @@
mLog = logger;
mActions = actions;
- define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE,
+ define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE,
"Verbose mode: errors, warnings and informational messages are printed.",
false);
- define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT,
+ define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT,
"Silent mode: only errors are printed out.",
false);
- define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP,
+ define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP,
"This help.",
false);
}
@@ -507,7 +507,7 @@
}
} else if (arg.getDefaultValue() != null) {
Object v = arg.getDefaultValue();
- if (arg.getMode() != MODE.BOOLEAN || v.equals(Boolean.TRUE)) {
+ if (arg.getMode() != Mode.BOOLEAN || v.equals(Boolean.TRUE)) {
value = v.toString();
}
}
@@ -537,7 +537,7 @@
* The mode of an argument specifies the type of variable it represents,
* whether an extra parameter is required after the flag and how to parse it.
*/
- static enum MODE {
+ static enum Mode {
/** Argument value is a Boolean. Default value is a Boolean. */
BOOLEAN {
@Override
@@ -628,7 +628,7 @@
* An argument accepted by the command-line, also called "a flag".
* Arguments must have a short version (one letter), a long version name and a description.
* They can have a default value, or it can be null.
- * Depending on the {@link MODE}, the default value can be a Boolean, an Integer, a String
+ * Depending on the {@link Mode}, the default value can be a Boolean, an Integer, a String
* or a String array (in which case the first item is the current by default.)
*/
static class Arg {
@@ -645,7 +645,7 @@
/** A default value. Can be null. */
private final Object mDefaultValue;
/** The argument mode (type + process method). Never null. */
- private final MODE mMode;
+ private final Mode mMode;
/** True if this argument is mandatory for this verb/directobject. */
private final boolean mMandatory;
/** Current value. Initially set to the default value. */
@@ -656,15 +656,15 @@
/**
* Creates a new argument flag description.
*
- * @param mode The {@link MODE} for the argument.
+ * @param mode The {@link Mode} for the argument.
* @param mandatory True if this argument is mandatory for this action.
* @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
* @param shortName The one-letter short argument name. Cannot be empty nor null.
* @param longName The long argument name. Cannot be empty nor null.
* @param description The description. Cannot be null.
- * @param defaultValue The default value (or values), which depends on the selected {@link MODE}.
+ * @param defaultValue The default value (or values), which depends on the selected {@link Mode}.
*/
- public Arg(MODE mode,
+ public Arg(Mode mode,
boolean mandatory,
String verb,
String directObject,
@@ -734,7 +734,7 @@
}
/** Returns the argument mode (type + process method). Never null. */
- public MODE getMode() {
+ public Mode getMode() {
return mMode;
}
@@ -752,21 +752,21 @@
/**
* Internal helper to define a new argument for a give action.
*
- * @param mode The {@link MODE} for the argument.
+ * @param mode The {@link Mode} for the argument.
* @param verb The verb name. Can be #INTERNAL_VERB.
* @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
* @param shortName The one-letter short argument name. Cannot be empty nor null.
* @param longName The long argument name. Cannot be empty nor null.
* @param description The description. Cannot be null.
- * @param defaultValue The default value (or values), which depends on the selected {@link MODE}.
+ * @param defaultValue The default value (or values), which depends on the selected {@link Mode}.
*/
- protected void define(MODE mode,
+ protected void define(Mode mode,
boolean mandatory,
String verb,
String directObject,
String shortName, String longName,
String description, Object defaultValue) {
- assert(mandatory || mode == MODE.BOOLEAN); // a boolean mode cannot be mandatory
+ assert(mandatory || mode == Mode.BOOLEAN); // a boolean mode cannot be mandatory
if (directObject == null) {
directObject = NO_VERB_OBJECT;
diff --git a/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java b/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java
index 1836d33..b5988ff 100644
--- a/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java
+++ b/tools/sdkmanager/app/src/com/android/sdkmanager/Main.java
@@ -337,6 +337,33 @@
creator.updateProject(projectDir,
target,
mSdkCommandLine.getParamName());
+
+ boolean doSubProjects = mSdkCommandLine.getParamSubProject();
+ boolean couldHaveDone = false;
+
+ // If there are any sub-folders with a manifest, try to update them as projects
+ // too. This will take care of updating any underlying test project even if the
+ // user changed the folder name.
+ File[] files = new File(projectDir).listFiles();
+ if (files != null) {
+ for (File dir : files) {
+ if (dir.isDirectory() &&
+ new File(dir, SdkConstants.FN_ANDROID_MANIFEST_XML).isFile()) {
+ if (doSubProjects) {
+ creator.updateProject(dir.getPath(),
+ target,
+ mSdkCommandLine.getParamName());
+ } else {
+ couldHaveDone = true;
+ }
+ }
+ }
+ }
+
+ if (couldHaveDone) {
+ mSdkLog.printf("It seems that there are sub-projects. If you want to update them\nplease use the --%1$s parameter.",
+ SdkCommandLine.KEY_SUBPROJECTS);
+ }
}
/**
diff --git a/tools/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java b/tools/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
index 2ff0c53..c48a386 100644
--- a/tools/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
+++ b/tools/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
@@ -38,20 +38,21 @@
public static final String OBJECT_PROJECT = "project";
public static final String OBJECT_ADB = "adb";
- public static final String ARG_ALIAS = "alias";
- public static final String ARG_ACTIVITY = "activity";
+ public static final String ARG_ALIAS = "alias";
+ public static final String ARG_ACTIVITY = "activity";
- public static final String KEY_ACTIVITY = ARG_ACTIVITY;
- public static final String KEY_PACKAGE = "package";
- public static final String KEY_MODE = "mode";
- public static final String KEY_TARGET_ID = OBJECT_TARGET;
- public static final String KEY_NAME = "name";
- public static final String KEY_PATH = "path";
- public static final String KEY_FILTER = "filter";
- public static final String KEY_SKIN = "skin";
- public static final String KEY_SDCARD = "sdcard";
- public static final String KEY_FORCE = "force";
- public static final String KEY_RENAME = "rename";
+ public static final String KEY_ACTIVITY = ARG_ACTIVITY;
+ public static final String KEY_PACKAGE = "package";
+ public static final String KEY_MODE = "mode";
+ public static final String KEY_TARGET_ID = OBJECT_TARGET;
+ public static final String KEY_NAME = "name";
+ public static final String KEY_PATH = "path";
+ public static final String KEY_FILTER = "filter";
+ public static final String KEY_SKIN = "skin";
+ public static final String KEY_SDCARD = "sdcard";
+ public static final String KEY_FORCE = "force";
+ public static final String KEY_RENAME = "rename";
+ public static final String KEY_SUBPROJECTS = "subprojects";
/**
* Action definitions for SdkManager command line.
@@ -97,46 +98,46 @@
// --- create avd ---
- define(MODE.STRING, false,
+ define(Mode.STRING, false,
VERB_CREATE, OBJECT_AVD, "p", KEY_PATH,
"Location path of the directory where the new AVD will be created", null);
- define(MODE.STRING, true,
+ define(Mode.STRING, true,
VERB_CREATE, OBJECT_AVD, "n", KEY_NAME,
"Name of the new AVD", null);
- define(MODE.INTEGER, true,
+ define(Mode.INTEGER, true,
VERB_CREATE, OBJECT_AVD, "t", KEY_TARGET_ID,
"Target id of the new AVD", null);
- define(MODE.STRING, false,
+ define(Mode.STRING, false,
VERB_CREATE, OBJECT_AVD, "s", KEY_SKIN,
"Skin of the new AVD", null);
- define(MODE.STRING, false,
+ define(Mode.STRING, false,
VERB_CREATE, OBJECT_AVD, "c", KEY_SDCARD,
"Path to a shared SD card image, or size of a new sdcard for the new AVD", null);
- define(MODE.BOOLEAN, false,
+ define(Mode.BOOLEAN, false,
VERB_CREATE, OBJECT_AVD, "f", KEY_FORCE,
"Force creation (override an existing AVD)", false);
// --- delete avd ---
- define(MODE.STRING, true,
+ define(Mode.STRING, true,
VERB_DELETE, OBJECT_AVD, "n", KEY_NAME,
"Name of the AVD to delete", null);
// --- move avd ---
- define(MODE.STRING, true,
+ define(Mode.STRING, true,
VERB_MOVE, OBJECT_AVD, "n", KEY_NAME,
"Name of the AVD to move or rename", null);
- define(MODE.STRING, false,
+ define(Mode.STRING, false,
VERB_MOVE, OBJECT_AVD, "r", KEY_RENAME,
"New name of the AVD to rename", null);
- define(MODE.STRING, false,
+ define(Mode.STRING, false,
VERB_MOVE, OBJECT_AVD, "p", KEY_PATH,
"New location path of the directory where to move the AVD", null);
// --- update avd ---
- define(MODE.STRING, true,
+ define(Mode.STRING, true,
VERB_UPDATE, OBJECT_AVD, "n", KEY_NAME,
"Name of the AVD to update", null);
@@ -145,41 +146,45 @@
/* Disabled for ADT 0.9 / Cupcake SDK 1.5_r1 release. [bug #1795718].
This currently does not work, the alias build rules need to be fixed.
- define(MODE.ENUM, true,
+ define(Mode.ENUM, true,
VERB_CREATE, OBJECT_PROJECT, "m", KEY_MODE,
"Project mode", new String[] { ARG_ACTIVITY, ARG_ALIAS });
*/
- define(MODE.STRING, true,
+ define(Mode.STRING, true,
VERB_CREATE, OBJECT_PROJECT,
"p", KEY_PATH,
"Location path of new project", null);
- define(MODE.INTEGER, true,
+ define(Mode.INTEGER, true,
VERB_CREATE, OBJECT_PROJECT, "t", KEY_TARGET_ID,
"Target id of the new project", null);
- define(MODE.STRING, true,
+ define(Mode.STRING, true,
VERB_CREATE, OBJECT_PROJECT, "k", KEY_PACKAGE,
"Package name", null);
- define(MODE.STRING, true,
+ define(Mode.STRING, true,
VERB_CREATE, OBJECT_PROJECT, "a", KEY_ACTIVITY,
"Activity name", null);
- define(MODE.STRING, false,
+ define(Mode.STRING, false,
VERB_CREATE, OBJECT_PROJECT, "n", KEY_NAME,
"Project name", null);
// --- update project ---
- define(MODE.STRING, true,
+ define(Mode.STRING, true,
VERB_UPDATE, OBJECT_PROJECT,
"p", KEY_PATH,
"Location path of the project", null);
- define(MODE.INTEGER, true,
+ define(Mode.INTEGER, true,
VERB_UPDATE, OBJECT_PROJECT,
"t", KEY_TARGET_ID,
"Target id to set for the project", -1);
- define(MODE.STRING, false,
+ define(Mode.STRING, false,
VERB_UPDATE, OBJECT_PROJECT,
"n", KEY_NAME,
"Project name", null);
+ define(Mode.BOOLEAN, false,
+ VERB_UPDATE, OBJECT_PROJECT,
+ "s", KEY_SUBPROJECTS,
+ "Also update any projects in sub-folders, such as test projects.", false);
}
@Override
@@ -234,8 +239,13 @@
return ((String) getValue(null, OBJECT_PROJECT, KEY_PACKAGE));
}
- /** Helper to retrieve the --activity for the new project action. */
+ /** Helper to retrieve the --activity for any project action. */
public String getParamProjectActivity() {
return ((String) getValue(null, OBJECT_PROJECT, KEY_ACTIVITY));
}
+
+ /** Helper to retrieve the --subprojects for any project action. */
+ public boolean getParamSubProject() {
+ return ((Boolean) getValue(null, OBJECT_PROJECT, KEY_SUBPROJECTS)).booleanValue();
+ }
}
diff --git a/tools/sdkmanager/app/src/com/android/sdkmanager/internal/repository/SettingsPage.java b/tools/sdkmanager/app/src/com/android/sdkmanager/internal/repository/SettingsPage.java
index df72cf9..8ab1364 100755
--- a/tools/sdkmanager/app/src/com/android/sdkmanager/internal/repository/SettingsPage.java
+++ b/tools/sdkmanager/app/src/com/android/sdkmanager/internal/repository/SettingsPage.java
@@ -16,7 +16,13 @@
package com.android.sdkmanager.internal.repository;
+import com.android.sdkuilib.internal.repository.ISettingsPage;
+
import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
@@ -25,22 +31,30 @@
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
-/*
- * TODO list
- * - The window should probably set a callback to be notified when settings are changed.
- * - Actually use the settings.
- */
+import java.util.Properties;
-public class SettingsPage extends Composite {
+public class SettingsPage extends Composite implements ISettingsPage {
+
+ // data members
+ private SettingsChangedCallback mSettingsChangedCallback;
+
+ // UI widgets
private Group mProxySettingsGroup;
- private Group mPlaceholderGroup;
+ private Group mMiscGroup;
private Button mApplyButton;
- private Label mSomeMoreSettings;
private Label mProxyServerLabel;
private Label mProxyPortLabel;
private Text mProxyServerText;
private Text mProxyPortText;
+ private Button mForceHttpCheck;
+
+ private ModifyListener mSetApplyDirty = new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ mApplyButton.setEnabled(true);
+ }
+ };
+
/**
* Create the composite.
@@ -62,6 +76,7 @@
mProxyServerText = new Text(mProxySettingsGroup, SWT.BORDER);
mProxyServerText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mProxyServerText.addModifyListener(mSetApplyDirty);
mProxyPortLabel = new Label(mProxySettingsGroup, SWT.NONE);
mProxyPortLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
@@ -69,18 +84,31 @@
mProxyPortText = new Text(mProxySettingsGroup, SWT.BORDER);
mProxyPortText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mProxyPortText.addModifyListener(mSetApplyDirty);
- mPlaceholderGroup = new Group(this, SWT.NONE);
- mPlaceholderGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
- mPlaceholderGroup.setText("Placeholder");
- mPlaceholderGroup.setLayout(new GridLayout(1, false));
+ mMiscGroup = new Group(this, SWT.NONE);
+ mMiscGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+ mMiscGroup.setText("Misc");
+ mMiscGroup.setLayout(new GridLayout(2, false));
- mSomeMoreSettings = new Label(mPlaceholderGroup, SWT.NONE);
- mSomeMoreSettings.setText("Some more settings here");
+ mForceHttpCheck = new Button(mMiscGroup, SWT.CHECK);
+ mForceHttpCheck.setText("Force https://... sources to be fetched using http://...");
+ mForceHttpCheck.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onForceHttpSelected(); //$hide$
+ }
+ });
mApplyButton = new Button(this, SWT.NONE);
mApplyButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
- mApplyButton.setText("Apply");
+ mApplyButton.setText("Save && Apply");
+ mApplyButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onApplySelected(); //$hide$
+ }
+ });
postCreate(); //$hide$
}
@@ -94,6 +122,8 @@
// Disable the check that prevents subclassing of SWT components
}
+
+
// -- Start of internal part ----------
// Hide everything down-below from SWT designer
//$hide>>$
@@ -104,6 +134,52 @@
private void postCreate() {
}
+ /** Loads settings from the given {@link Properties} container and update the page UI. */
+ public void loadSettings(Properties in_settings) {
+ mProxyServerText.setText(in_settings.getProperty(KEY_HTTP_PROXY_HOST, "")); //$NON-NLS-1$
+ mProxyPortText.setText( in_settings.getProperty(KEY_HTTP_PROXY_PORT, "")); //$NON-NLS-1$
+ mForceHttpCheck.setSelection(Boolean.parseBoolean(in_settings.getProperty(KEY_FORCE_HTTP)));
+
+ // We loaded fresh settings so there's nothing dirty to apply
+ mApplyButton.setEnabled(false);
+ }
+
+ /** Called by the application to retrieve settings from the UI and store them in
+ * the given {@link Properties} container. */
+ public void retrieveSettings(Properties out_settings) {
+ out_settings.setProperty(KEY_HTTP_PROXY_HOST, mProxyServerText.getText());
+ out_settings.setProperty(KEY_HTTP_PROXY_PORT, mProxyPortText.getText());
+ out_settings.setProperty(KEY_FORCE_HTTP,
+ Boolean.toString(mForceHttpCheck.getSelection()));
+ }
+
+ /**
+ * Called by the application to give a callback that the page should invoke when
+ * settings must be applied. The page does not apply the settings itself, instead
+ * it notifies the application.
+ */
+ public void setOnSettingsChanged(SettingsChangedCallback settingsChangedCallback) {
+ mSettingsChangedCallback = settingsChangedCallback;
+ }
+
+ /**
+ * Callback invoked when user presses the "Save and Apply" button.
+ * Notify the application that settings have changed.
+ */
+ private void onApplySelected() {
+ if (mSettingsChangedCallback != null) {
+ mSettingsChangedCallback.onSettingsChanged(this);
+ mApplyButton.setEnabled(false);
+ }
+ }
+
+ /**
+ * Callback invoked when the users presses the Force HTTPS checkbox.
+ */
+ private void onForceHttpSelected() {
+ mSetApplyDirty.modifyText(null);
+ }
+
// End of hiding from SWT Designer
//$hide<<$
}
diff --git a/tools/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java b/tools/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java
index 918591b..a213652 100644
--- a/tools/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java
+++ b/tools/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java
@@ -41,9 +41,9 @@
{ "verb1", "action1", "Some action" },
{ "verb1", "action2", "Another action" },
});
- define(MODE.STRING, false /*mandatory*/,
+ define(Mode.STRING, false /*mandatory*/,
"verb1", "action1", "1", "first", "non-mandatory flag", null);
- define(MODE.STRING, true /*mandatory*/,
+ define(Mode.STRING, true /*mandatory*/,
"verb1", "action1", "2", "second", "mandatory flag", null);
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
index 7a0b06b..b09018b 100644
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
@@ -101,8 +101,8 @@
try {
SdkManager manager = new SdkManager(sdkLocation);
ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
- manager.loadPlatforms(list, log);
- manager.loadAddOns(list, log);
+ loadPlatforms(sdkLocation, list, log);
+ loadAddOns(sdkLocation, list, log);
// sort the targets/add-ons
Collections.sort(list);
@@ -184,6 +184,25 @@
}
}
+ /**
+ * Reloads the content of the SDK.
+ * @param log the ISdkLog object receiving warning/error from the parsing.
+ */
+ public void reloadSdk(ISdkLog log) {
+ // get the current target list.
+ ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
+ loadPlatforms(mSdkLocation, list, log);
+ loadAddOns(mSdkLocation, list, log);
+
+ // For now replace the old list with the new one.
+ // In the future we may want to keep the current objects, so that ADT doesn't have to deal
+ // with new IAndroidTarget objects when a target didn't actually change.
+
+ // sort the targets/add-ons
+ Collections.sort(list);
+ setTargets(list.toArray(new IAndroidTarget[list.size()]));
+ }
+
private SdkManager(String sdkLocation) {
mSdkLocation = sdkLocation;
}
@@ -194,11 +213,13 @@
/**
* Loads the Platforms from the SDK.
+ * @param location Location of the SDK
* @param list the list to fill with the platforms.
* @param log the ISdkLog object receiving warning/error from the parsing.
*/
- private void loadPlatforms(ArrayList<IAndroidTarget> list, ISdkLog log) {
- File platformFolder = new File(mSdkLocation, SdkConstants.FD_PLATFORMS);
+ private static void loadPlatforms(String location, ArrayList<IAndroidTarget> list,
+ ISdkLog log) {
+ File platformFolder = new File(location, SdkConstants.FD_PLATFORMS);
if (platformFolder.isDirectory()) {
File[] platforms = platformFolder.listFiles();
@@ -232,7 +253,7 @@
* @param platform the location of the platform.
* @param log the ISdkLog object receiving warning/error from the parsing.
*/
- private PlatformTarget loadPlatform(File platform, ISdkLog log) {
+ private static PlatformTarget loadPlatform(File platform, ISdkLog log) {
File buildProp = new File(platform, SdkConstants.FN_BUILD_PROP);
if (buildProp.isFile()) {
@@ -342,11 +363,12 @@
/**
* Loads the Add-on from the SDK.
+ * @param location Location of the SDK
* @param list the list to fill with the add-ons.
* @param log the ISdkLog object receiving warning/error from the parsing.
*/
- private void loadAddOns(ArrayList<IAndroidTarget> list, ISdkLog log) {
- File addonFolder = new File(mSdkLocation, SdkConstants.FD_ADDONS);
+ private static void loadAddOns(String location, ArrayList<IAndroidTarget> list, ISdkLog log) {
+ File addonFolder = new File(location, SdkConstants.FD_ADDONS);
if (addonFolder.isDirectory()) {
File[] addons = addonFolder.listFiles();
@@ -380,7 +402,8 @@
* @param targetList The list of Android target that were already loaded from the SDK.
* @param log the ISdkLog object receiving warning/error from the parsing.
*/
- private AddOnTarget loadAddon(File addon, ArrayList<IAndroidTarget> targetList, ISdkLog log) {
+ private static AddOnTarget loadAddon(File addon, ArrayList<IAndroidTarget> targetList,
+ ISdkLog log) {
File addOnManifest = new File(addon, SdkConstants.FN_MANIFEST_INI);
if (addOnManifest.isFile()) {
@@ -539,7 +562,7 @@
* @param value the string to convert.
* @return the int value, or {@link IAndroidTarget#NO_USB_ID} if the convertion failed.
*/
- private int convertId(String value) {
+ private static int convertId(String value) {
if (value != null && value.length() > 0) {
if (PATTERN_USB_IDS.matcher(value).matches()) {
String v = value.substring(2);
@@ -555,7 +578,7 @@
return IAndroidTarget.NO_USB_ID;
}
- private void displayAddonManifestError(ISdkLog log, String addonName, String valueName) {
+ private static void displayAddonManifestError(ISdkLog log, String addonName, String valueName) {
if (log != null) {
log.error(null, "Ignoring add-on '%1$s': '%2$s' is missing from %3$s.",
addonName, valueName, SdkConstants.FN_MANIFEST_INI);
@@ -568,7 +591,7 @@
* <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe),
* aidl(.exe), dx(.bat), and dx.jar
*/
- private boolean checkPlatformContent(File platform, ISdkLog log) {
+ private static boolean checkPlatformContent(File platform, ISdkLog log) {
for (String relativePath : sPlatformContentList) {
File f = new File(platform, relativePath);
if (f.exists() == false) {
@@ -645,7 +668,7 @@
* Parses the skin folder and builds the skin list.
* @param osPath The path of the skin root folder.
*/
- private String[] parseSkinFolder(String osPath) {
+ private static String[] parseSkinFolder(String osPath) {
File skinRootFolder = new File(osPath);
if (skinRootFolder.isDirectory()) {
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java
index a33eaaa..1e16a83 100644
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java
@@ -50,7 +50,7 @@
* @hide
*/
public class ProjectCreator {
-
+
/** Package path substitution string used in template files, i.e. "PACKAGE_PATH" */
private final static String PH_JAVA_FOLDER = "PACKAGE_PATH";
/** Package name substitution string used in template files, i.e. "PACKAGE" */
@@ -59,9 +59,9 @@
private final static String PH_ACTIVITY_NAME = "ACTIVITY_NAME";
/** Project name substitution string used in template files, i.e. "PROJECT_NAME". */
private final static String PH_PROJECT_NAME = "PROJECT_NAME";
-
+
private final static String FOLDER_TESTS = "tests";
-
+
/** Pattern for characters accepted in a project name. Since this will be used as a
* directory name, we're being a bit conservative on purpose: dot and space cannot be used. */
public static final Pattern RE_PROJECT_NAME = Pattern.compile("[a-zA-Z0-9_]+");
@@ -69,7 +69,7 @@
public final static String CHARS_PROJECT_NAME = "a-z A-Z 0-9 _";
/** Pattern for characters accepted in a package name. A package is list of Java identifier
- * separated by a dot. We need to have at least one dot (e.g. a two-level package name).
+ * separated by a dot. We need to have at least one dot (e.g. a two-level package name).
* A Java identifier cannot start by a digit. */
public static final Pattern RE_PACKAGE_NAME =
Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+");
@@ -82,7 +82,7 @@
/** List of valid characters for a project name. Used for display purposes. */
public final static String CHARS_ACTIVITY_NAME = "a-z A-Z 0-9 _";
-
+
public enum OutputLevel {
/** Silent mode. Project creation will only display errors. */
SILENT,
@@ -100,11 +100,11 @@
private static class ProjectCreateException extends Exception {
/** default UID. This will not be serialized anyway. */
private static final long serialVersionUID = 1L;
-
+
ProjectCreateException(String message) {
super(message);
}
-
+
ProjectCreateException(Throwable t, String format, Object... args) {
super(format != null ? String.format(format, args) : format, t);
}
@@ -113,23 +113,23 @@
super(String.format(format, args));
}
}
-
+
private final OutputLevel mLevel;
private final ISdkLog mLog;
private final String mSdkFolder;
-
+
public ProjectCreator(String sdkFolder, OutputLevel level, ISdkLog log) {
mSdkFolder = sdkFolder;
mLevel = level;
mLog = log;
}
-
+
/**
* Creates a new project.
* <p/>
* The caller should have already checked and sanitized the parameters.
- *
+ *
* @param folderPath the folder of the project to create.
* @param projectName the name of the project. The name must match the
* {@link #RE_PROJECT_NAME} regex.
@@ -137,14 +137,16 @@
* {@link #RE_PACKAGE_NAME} regex.
* @param activityName the activity of the project as it will appear in the manifest. Can be
* null if no activity should be created. The name must match the
- * {@link #RE_ACTIVITY_NAME} regex.
+ * {@link #RE_ACTIVITY_NAME} regex.
* @param target the project target.
- * @param isTestProject whether the project to create is a test project.
+ * @param isTestProject whether the project to create is a test project. Caller should
+ * initially call this will false. The method will call itself back to create
+ * a test project as needed.
*/
public void createProject(String folderPath, String projectName,
String packageName, String activityName, IAndroidTarget target,
boolean isTestProject) {
-
+
// create project folder if it does not exist
File projectFolder = new File(folderPath);
if (!projectFolder.exists()) {
@@ -156,7 +158,7 @@
} catch (Exception e) {
t = e;
}
-
+
if (created) {
println("Created project directory: %1$s", projectFolder);
} else {
@@ -176,7 +178,7 @@
} catch (Exception e1) {
e = e1;
}
-
+
if (e != null || error != null) {
mLog.error(e, error, projectFolder, SdkConstants.androidCmdName());
}
@@ -196,7 +198,7 @@
PropertyType.DEFAULT);
defaultProperties.setAndroidTarget(target);
defaultProperties.save();
-
+
// create an empty build.properties
ProjectProperties buildProperties = ProjectProperties.create(folderPath,
PropertyType.BUILD);
@@ -225,16 +227,16 @@
keywords.put(PH_PROJECT_NAME, projectName);
} else {
if (activityName != null) {
- // Use the activity as project name
+ // Use the activity as project name
keywords.put(PH_PROJECT_NAME, activityName);
} else {
// We need a project name. Just pick up the basename of the project
// directory.
projectName = projectFolder.getName();
- keywords.put(PH_PROJECT_NAME, projectName);
+ keywords.put(PH_PROJECT_NAME, projectName);
}
}
-
+
// create the source folder and the java package folders.
String srcFolderPath = SdkConstants.FD_SOURCES + File.separator + packagePath;
File sourceFolder = createDirs(projectFolder, srcFolderPath);
@@ -270,13 +272,13 @@
/* Make AndroidManifest.xml and build.xml files */
String manifestTemplate = "AndroidManifest.template";
if (isTestProject) {
- manifestTemplate = "AndroidManifest.tests.template";
+ manifestTemplate = "AndroidManifest.tests.template";
}
installTemplate(manifestTemplate,
new File(projectFolder, SdkConstants.FN_ANDROID_MANIFEST_XML),
keywords, target);
-
+
installTemplate("build.template",
new File(projectFolder, SdkConstants.FN_BUILD_XML),
keywords);
@@ -286,7 +288,7 @@
// create the test project folder.
createDirs(projectFolder, FOLDER_TESTS);
File testProjectFolder = new File(folderPath, FOLDER_TESTS);
-
+
createProject(testProjectFolder.getAbsolutePath(), projectName, packageName,
activityName, target, true /*isTestProject*/);
}
@@ -296,7 +298,7 @@
mLog.error(e, null);
}
}
-
+
/**
* Updates an existing project.
* <p/>
@@ -308,12 +310,12 @@
* <li> Refresh/create "sdk" in local.properties
* <li> Build.xml: create if not present or no <androidinit(\w|/>) in it
* </ul>
- *
+ *
* @param folderPath the folder of the project to update. This folder must exist.
* @param target the project target. Can be null.
* @param projectName The project name from --name. Can be null.
*/
- public void updateProject(String folderPath, IAndroidTarget target, String projectName ) {
+ public void updateProject(String folderPath, IAndroidTarget target, String projectName) {
// project folder must exist and be a directory, since this is an update
File projectFolder = new File(folderPath);
if (!projectFolder.isDirectory()) {
@@ -331,7 +333,7 @@
folderPath);
return;
}
-
+
// Check there's a default.properties with a target *or* --target was specified
ProjectProperties props = ProjectProperties.load(folderPath, PropertyType.DEFAULT);
if (props == null || props.getProperty(ProjectProperties.PROPERTY_TARGET) == null) {
@@ -351,7 +353,7 @@
if (props == null) {
props = ProjectProperties.create(folderPath, PropertyType.DEFAULT);
}
-
+
// set or replace the target
props.setAndroidTarget(target);
try {
@@ -364,7 +366,7 @@
return;
}
}
-
+
// Refresh/create "sdk" in local.properties
// because the file may already exists and contain other values (like apk config),
// we first try to load it.
@@ -372,7 +374,7 @@
if (props == null) {
props = ProjectProperties.create(folderPath, PropertyType.LOCAL);
}
-
+
// set or replace the sdk location.
props.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
try {
@@ -384,20 +386,25 @@
folderPath);
return;
}
-
+
// Build.xml: create if not present or no <androidinit/> in it
File buildXml = new File(projectFolder, SdkConstants.FN_BUILD_XML);
boolean needsBuildXml = projectName != null || !buildXml.exists();
if (!needsBuildXml) {
- // Note that "<androidinit" must be followed by either a whitespace, a "/" (for the
+ // Look for for a classname="com.android.ant.SetupTask" attribute
+ needsBuildXml = !checkFileContainsRegexp(buildXml,
+ "classname=\"com.android.ant.SetupTask\""); //$NON-NLS-1$
+ }
+ if (!needsBuildXml) {
+ // Note that "<setup" must be followed by either a whitespace, a "/" (for the
// XML /> closing tag) or an end-of-line. This way we know the XML tag is really this
// one and later we will be able to use an "androidinit2" tag or such as necessary.
- needsBuildXml = !checkFileContainsRegexp(buildXml, "<androidinit(?:\\s|/|$)");
- if (needsBuildXml) {
- println("File %1$s is too old and needs to be updated.", SdkConstants.FN_BUILD_XML);
- }
+ needsBuildXml = !checkFileContainsRegexp(buildXml, "<setup(?:\\s|/|$)"); //$NON-NLS-1$
}
-
+ if (needsBuildXml) {
+ println("File %1$s is too old and needs to be updated.", SdkConstants.FN_BUILD_XML);
+ }
+
if (needsBuildXml) {
// create the map for place-holders of values to replace in the templates
final HashMap<String, String> keywords = new HashMap<String, String>();
@@ -408,13 +415,13 @@
} else {
extractPackageFromManifest(androidManifest, keywords);
if (keywords.containsKey(PH_ACTIVITY_NAME)) {
- // Use the activity as project name
+ // Use the activity as project name
keywords.put(PH_PROJECT_NAME, keywords.get(PH_ACTIVITY_NAME));
} else {
// We need a project name. Just pick up the basename of the project
// directory.
projectName = projectFolder.getName();
- keywords.put(PH_PROJECT_NAME, projectName);
+ keywords.put(PH_PROJECT_NAME, projectName);
}
}
@@ -423,7 +430,7 @@
SdkConstants.FN_BUILD_XML,
keywords.get(PH_PROJECT_NAME));
}
-
+
try {
installTemplate("build.template",
new File(projectFolder, SdkConstants.FN_BUILD_XML),
@@ -443,18 +450,18 @@
try {
BufferedReader in = new BufferedReader(new FileReader(file));
String line;
-
+
while ((line = in.readLine()) != null) {
if (p.matcher(line).find()) {
return true;
}
}
-
+
in.close();
} catch (Exception e) {
// ignore
}
-
+
return false;
}
@@ -464,8 +471,8 @@
* The keywords dictionary is always filed the package name under the key {@link #PH_PACKAGE}.
* If an activity name can be found, it is filed under the key {@link #PH_ACTIVITY_NAME}.
* When no activity is found, this key is not created.
- *
- * @param manifestFile The AndroidManifest.xml file
+ *
+ * @param manifestFile The AndroidManifest.xml file
* @param outKeywords Place where to put the out parameters: package and activity names.
* @return True if the package/activity was parsed and updated in the keyword dictionary.
*/
@@ -474,9 +481,9 @@
try {
final String nsPrefix = "android";
final String nsURI = SdkConstants.NS_RESOURCES;
-
+
XPath xpath = XPathFactory.newInstance().newXPath();
-
+
xpath.setNamespaceContext(new NamespaceContext() {
public String getNamespaceURI(String prefix) {
if (nsPrefix.equals(prefix)) {
@@ -501,18 +508,18 @@
}
return null;
}
-
+
});
-
+
InputSource source = new InputSource(new FileReader(manifestFile));
String packageName = xpath.evaluate("/manifest/@package", source);
- source = new InputSource(new FileReader(manifestFile));
-
+ source = new InputSource(new FileReader(manifestFile));
+
// Select the "android:name" attribute of all <activity> nodes but only if they
// contain a sub-node <intent-filter><action> with an "android:name" attribute which
// is 'android.intent.action.MAIN' and an <intent-filter><category> with an
- // "android:name" attribute which is 'android.intent.category.LAUNCHER'
+ // "android:name" attribute which is 'android.intent.category.LAUNCHER'
String expression = String.format("/manifest/application/activity" +
"[intent-filter/action/@%1$s:name='android.intent.action.MAIN' and " +
"intent-filter/category/@%1$s:name='android.intent.category.LAUNCHER']" +
@@ -524,7 +531,7 @@
// If we get here, both XPath expressions were valid so we're most likely dealing
// with an actual AndroidManifest.xml file. The nodes may not have the requested
// attributes though, if which case we should warn.
-
+
if (packageName == null || packageName.length() == 0) {
mLog.error(null,
"Missing <manifest package=\"...\"> in '%1$s'",
@@ -545,14 +552,14 @@
"Only the first one will be used. If this is not appropriate, you need\n" +
"to specify one of these values manually instead:",
manifestFile.getName());
-
+
for (int i = 0; i < activityNames.getLength(); i++) {
String name = activityNames.item(i).getNodeValue();
name = combinePackageActivityNames(packageName, name);
println("- %1$s", name);
}
}
-
+
if (activityName.length() == 0) {
mLog.warning("Missing <activity %1$s:name=\"...\"> in '%2$s'.\n" +
"No activity will be generated.",
@@ -563,7 +570,7 @@
outKeywords.put(PH_PACKAGE, packageName);
return true;
-
+
} catch (IOException e) {
mLog.error(e, "Failed to read %1$s", manifestFile.getName());
} catch (XPathExpressionException e) {
@@ -572,10 +579,10 @@
"Failed to parse %1$s",
manifestFile.getName());
}
-
+
return false;
}
-
+
private String combinePackageActivityNames(String packageName, String activityName) {
// Activity Name can have 3 forms:
// - ".Name" means this is a class name in the given package name.
@@ -585,7 +592,7 @@
// To be valid, the package name should have at least two components. This is checked
// later during the creation of the build.xml file, so we just need to detect there's
// a dot but not at pos==0.
-
+
int pos = activityName.indexOf('.');
if (pos == 0) {
return packageName + activityName;
@@ -600,12 +607,12 @@
* Installs a new file that is based on a template file provided by a given target.
* Each match of each key from the place-holder map in the template will be replaced with its
* corresponding value in the created file.
- *
+ *
* @param templateName the name of to the template file
* @param destFile the path to the destination file, relative to the project
* @param placeholderMap a map of (place-holder, value) to create the file from the template.
* @param target the Target of the project that will be providing the template.
- * @throws ProjectCreateException
+ * @throws ProjectCreateException
*/
private void installTemplate(String templateName, File destFile,
Map<String, String> placeholderMap, IAndroidTarget target)
@@ -621,11 +628,11 @@
* Installs a new file that is based on a template file provided by the tools folder.
* Each match of each key from the place-holder map in the template will be replaced with its
* corresponding value in the created file.
- *
+ *
* @param templateName the name of to the template file
* @param destFile the path to the destination file, relative to the project
* @param placeholderMap a map of (place-holder, value) to create the file from the template.
- * @throws ProjectCreateException
+ * @throws ProjectCreateException
*/
private void installTemplate(String templateName, File destFile,
Map<String, String> placeholderMap)
@@ -641,38 +648,38 @@
* Installs a new file that is based on a template.
* Each match of each key from the place-holder map in the template will be replaced with its
* corresponding value in the created file.
- *
+ *
* @param sourcePath the full path to the source template file
* @param destFile the destination file
* @param placeholderMap a map of (place-holder, value) to create the file from the template.
- * @throws ProjectCreateException
+ * @throws ProjectCreateException
*/
private void installFullPathTemplate(String sourcePath, File destFile,
Map<String, String> placeholderMap) throws ProjectCreateException {
-
+
boolean existed = destFile.exists();
-
+
try {
BufferedWriter out = new BufferedWriter(new FileWriter(destFile));
BufferedReader in = new BufferedReader(new FileReader(sourcePath));
String line;
-
+
while ((line = in.readLine()) != null) {
for (String key : placeholderMap.keySet()) {
line = line.replace(key, placeholderMap.get(key));
}
-
+
out.write(line);
out.newLine();
}
-
+
out.close();
in.close();
} catch (Exception e) {
throw new ProjectCreateException(e, "Could not access %1$s: %2$s",
destFile, e.getMessage());
}
-
+
println("%1$s file %2$s",
existed ? "Updated" : "Added",
destFile);
@@ -683,7 +690,7 @@
* <p/>
* This is just a convenience wrapper around {@link ISdkLog#printf(String, Object...)} from
* {@link #mLog} after testing if ouput level is {@link OutputLevel#VERBOSE}.
- *
+ *
* @param format Format for String.format
* @param args Arguments for String.format
*/
@@ -698,10 +705,10 @@
/**
* Creates a new folder, along with any parent folders that do not exists.
- *
+ *
* @param parent the parent folder
* @param name the name of the directory to create.
- * @throws ProjectCreateException
+ * @throws ProjectCreateException
*/
private File createDirs(File parent, String name) throws ProjectCreateException {
final File newFolder = new File(parent, name);
@@ -730,7 +737,7 @@
"Could not determine canonical path of created directory", e);
}
}
-
+
return newFolder;
}
@@ -738,7 +745,7 @@
* Strips the string of beginning and trailing characters (multiple
* characters will be stripped, example stripString("..test...", '.')
* results in "test";
- *
+ *
* @param s the string to strip
* @param strip the character to strip from beginning and end
* @return the stripped string or the empty string if everything is stripped.
@@ -746,24 +753,24 @@
private static String stripString(String s, char strip) {
final int sLen = s.length();
int newStart = 0, newEnd = sLen - 1;
-
+
while (newStart < sLen && s.charAt(newStart) == strip) {
newStart++;
}
while (newEnd >= 0 && s.charAt(newEnd) == strip) {
newEnd--;
}
-
+
/*
* newEnd contains a char we want, and substring takes end as being
* exclusive
*/
newEnd++;
-
+
if (newStart >= sLen || newEnd < 0) {
return "";
}
-
+
return s.substring(newStart, newEnd);
}
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
index 0c666d2..f204ba5 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java
@@ -76,7 +76,8 @@
/**
* Creates a new platform package based on an actual {@link IAndroidTarget} (which
* {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.
- * This is used to list local SDK folders.
+ * This is used to list local SDK folders in which case there is one archive which
+ * URL is the actual target location.
*/
AddonPackage(IAndroidTarget target) {
super( null, //source
@@ -86,9 +87,7 @@
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
- "", //archiveUrl //$NON-NLS-1$
- 0, //archiveSize
- null //archiveChecksum
+ target.getLocation() //archiveOsPath
);
mApiLevel = target.getApiVersionNumber();
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
index a67d6b2..08a536b 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java
@@ -154,17 +154,43 @@
private final String mChecksum;
private final ChecksumType mChecksumType = ChecksumType.SHA1;
private final Package mPackage;
+ private final String mLocalOsPath;
+ private final boolean mIsLocal;
/**
- * Creates a new archive.
+ * Creates a new remote archive.
*/
Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) {
mPackage = pkg;
mOs = os;
mArch = arch;
mUrl = url;
+ mLocalOsPath = null;
mSize = size;
mChecksum = checksum;
+ mIsLocal = false;
+ }
+
+ /**
+ * Creates a new local archive.
+ */
+ Archive(Package pkg, Os os, Arch arch, String localOsPath) {
+ mPackage = pkg;
+ mOs = os;
+ mArch = arch;
+ mUrl = null;
+ mLocalOsPath = localOsPath;
+ mSize = 0;
+ mChecksum = "";
+ mIsLocal = true;
+ }
+
+ /**
+ * Returns true if this is a locally installed archive.
+ * Returns false if this is a remote archive that needs to be downloaded.
+ */
+ public boolean isLocal() {
+ return mIsLocal;
}
/**
@@ -200,13 +226,23 @@
/**
* Returns the download archive URL, either absolute or relative to the repository xml.
- * For a local installed folder, an URL is frabricated from the folder path.
+ * Always return null for a local installed folder.
+ * @see #getLocalOsPath()
*/
public String getUrl() {
return mUrl;
}
/**
+ * Returns the local OS folder where a local archive is installed.
+ * Always return null for remote archives.
+ * @see #getUrl()
+ */
+ public String getLocalOsPath() {
+ return mLocalOsPath;
+ }
+
+ /**
* Returns the archive {@link Os} enum.
* Can be null for a local installed folder on an unknown OS.
*/
@@ -291,18 +327,34 @@
}
/**
+ * Delete the archive folder if this is a local archive.
+ */
+ public void deleteLocal() {
+ if (isLocal()) {
+ deleteFileOrFolder(new File(getLocalOsPath()));
+ }
+ }
+
+ /**
* Install this {@link Archive}s.
* The archive will be skipped if it is incompatible.
*
* @return True if the archive was installed, false otherwise.
*/
- public boolean install(String osSdkRoot, ITaskMonitor monitor) {
+ public boolean install(String osSdkRoot, boolean forceHttp, ITaskMonitor monitor) {
File archiveFile = null;
try {
String name = getParentPackage().getShortDescription();
- // TODO: we should not see this test fail if we had the filter UI above.
+ if (isLocal()) {
+ // This should never happen.
+ monitor.setResult("Skipping already installed archive: %1$s for %2$s",
+ name,
+ getOsDescription());
+ return false;
+ }
+
if (!isCompatible()) {
monitor.setResult("Skipping incompatible archive: %1$s for %2$s",
name,
@@ -310,7 +362,7 @@
return false;
}
- archiveFile = downloadFile(monitor);
+ archiveFile = downloadFile(monitor, forceHttp);
if (archiveFile != null) {
if (unarchive(osSdkRoot, archiveFile, monitor)) {
monitor.setResult("Installed: %1$s", name);
@@ -330,7 +382,7 @@
* Downloads an archive and returns the temp file with it.
* Caller is responsible with deleting the temp file when done.
*/
- private File downloadFile(ITaskMonitor monitor) {
+ private File downloadFile(ITaskMonitor monitor, boolean forceHttp) {
File tmpFileToDelete = null;
try {
@@ -362,6 +414,10 @@
link = base + link;
}
+ if (forceHttp) {
+ link = link.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
if (fetchUrl(tmpFile, link, desc, monitor)) {
// Fetching was successful, don't delete the temp file here!
tmpFileToDelete = null;
@@ -534,12 +590,12 @@
destFolder.getPath());
return false;
}
+ }
- if (!unzipDestFolder.mkdirs()) {
- monitor.setResult("Failed to create temp directory %1$s",
- unzipDestFolder.getPath());
- return false;
- }
+ if (!unzipDestFolder.mkdirs()) {
+ monitor.setResult("Failed to create directory %1$s",
+ unzipDestFolder.getPath());
+ return false;
}
if (!unzipFolder(archiveFile, getSize(), unzipDestFolder, desc, monitor)) {
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
index e2c2cf5..4770765 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java
@@ -44,7 +44,8 @@
/**
* Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
*/
DocPackage(RepoSource source,
int apiLevel,
@@ -54,9 +55,7 @@
String descUrl,
Os archiveOs,
Arch archiveArch,
- String archiveUrl,
- long archiveSize,
- String archiveChecksum) {
+ String archiveOsPath) {
super(source,
revision,
license,
@@ -64,9 +63,7 @@
descUrl,
archiveOs,
archiveArch,
- archiveUrl,
- archiveSize,
- archiveChecksum);
+ archiveOsPath);
mApiLevel = apiLevel;
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java
index c8f7c06..85596ba 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java
@@ -45,18 +45,27 @@
/**
* Sets the max value of the progress bar.
* This method can be invoked from a non-UI thread.
+ *
+ * This method MUST be invoked once before using {@link #incProgress(int)} or
+ * {@link #getProgress()} or {@link #createSubMonitor(int)}. Callers are
+ * discouraged from using more than once -- implementations can either discard
+ * the next calls or behave incoherently.
*/
public void setProgressMax(int max);
/**
* Increments the current value of the progress bar.
* This method can be invoked from a non-UI thread.
+ *
+ * Callers MUST use setProgressMax before using this method.
*/
public void incProgress(int delta);
/**
* Returns the current value of the progress bar,
* between 0 and up to {@link #setProgressMax(int)} - 1.
+ *
+ * Callers MUST use setProgressMax before using this method.
*/
public int getProgress();
@@ -66,4 +75,10 @@
* as possible.
*/
public boolean isCancelRequested();
+
+ /**
+ * Creates a sub-monitor that will use up to tickCount on the progress bar.
+ * tickCount must be 1 or more.
+ */
+ public ITaskMonitor createSubMonitor(int tickCount);
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
index 8d067f2..d52cce6 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
@@ -35,7 +35,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
-import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
@@ -60,7 +59,7 @@
private Package[] mPackages;
public LocalSdkParser() {
- // TODO Auto-generated constructor stub
+ // pass
}
/**
@@ -172,9 +171,7 @@
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
- "", //archiveUrl //$NON-NLS-1$
- 0, //archiveSize
- null //archiveChecksum
+ toolFolder.getPath() //archiveOsPath
);
}
@@ -219,13 +216,6 @@
// Create a pkg if we don't have one yet.
if (pkg == null) {
- String url = null;
- try {
- url = docFolder.toURI().toURL().toString();
- } catch (MalformedURLException e) {
- // ignore
- }
-
pkg = new DocPackage(
null, //source
0, //apiLevel
@@ -235,9 +225,7 @@
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
- url, //archiveUrl
- 0, //archiveSize
- null //archiveChecksum
+ docFolder.getPath() //archiveOsPath
);
}
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
index 4d28f08..64ff007 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java
@@ -62,7 +62,8 @@
/**
* Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
*/
public Package(RepoSource source,
int revision,
@@ -71,9 +72,7 @@
String descUrl,
Os archiveOs,
Arch archiveArch,
- String archiveUrl,
- long archiveSize,
- String archiveChecksum) {
+ String archiveOsPath) {
mSource = source;
mRevision = revision;
mLicense = license;
@@ -83,9 +82,7 @@
mArchives[0] = new Archive(this,
archiveOs,
archiveArch,
- archiveUrl,
- archiveSize,
- archiveChecksum);
+ archiveOsPath);
}
/**
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
index ae6bc77..0724e30 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java
@@ -49,7 +49,8 @@
/**
* Creates a new platform package based on an actual {@link IAndroidTarget} (which
* must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.
- * This is used to list local SDK folders.
+ * This is used to list local SDK folders in which case there is one archive which
+ * URL is the actual target location.
*/
PlatformPackage(IAndroidTarget target) {
super( null, //source
@@ -59,9 +60,7 @@
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
- "", //archiveUrl //$NON-NLS-1$
- 0, //archiveSize
- null //archiveChecksum
+ target.getLocation() //archiveOsPath
);
mApiLevel = target.getApiVersionNumber();
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java
index d8ab806..258ecbc 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java
@@ -68,7 +68,7 @@
}
/**
- * Returns the list of known packages found by the last call to {@link #load(ITaskFactory)}.
+ * Returns the list of known packages found by the last call to load().
* This is null when the source hasn't been loaded yet.
*/
public Package[] getPackages() {
@@ -77,7 +77,7 @@
/**
* Clear the internal packages list. After this call, {@link #getPackages()} will return
- * null till {@link #load(ITaskFactory)} is called.
+ * null till load() is called.
*/
public void clearPackages() {
mPackages = null;
@@ -94,47 +94,48 @@
/**
* Tries to fetch the repository index for the given URL.
*/
- public void load(ITaskFactory taskFactory) {
+ public void load(ITaskMonitor monitor, boolean forceHttp) {
- taskFactory.start("Init SDK Updater", new ITask() {
- public void run(ITaskMonitor monitor) {
- monitor.setProgressMax(4);
+ monitor.setProgressMax(4);
- setDefaultDescription();
+ setDefaultDescription();
- monitor.setDescription("Fetching %1$s", mUrl);
- monitor.incProgress(1);
+ String url = mUrl;
+ if (forceHttp) {
+ url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
- String xml = fetchUrl(mUrl, monitor);
+ monitor.setDescription("Fetching %1$s", url);
+ monitor.incProgress(1);
- if (xml == null) {
- mDescription += String.format("\nFailed to fetch URL %1$s", mUrl);
- return;
- }
+ String xml = fetchUrl(url, monitor);
- monitor.setDescription("Validate XML");
- monitor.incProgress(1);
+ if (xml == null) {
+ mDescription += String.format("\nFailed to fetch URL %1$s", url);
+ return;
+ }
- if (!validateXml(xml, monitor)) {
- mDescription += String.format("\nFailed to validate XML at %1$s", mUrl);
- return;
- }
+ monitor.setDescription("Validate XML");
+ monitor.incProgress(1);
- monitor.setDescription("Parse XML");
- monitor.incProgress(1);
- parsePackages(xml, monitor);
- if (mPackages.length == 0) {
- mDescription += "\nNo packages found.";
- } else if (mPackages.length == 1) {
- mDescription += "\nOne package found.";
- } else {
- mDescription += String.format("\n%1$d packages found.", mPackages.length);
- }
+ if (!validateXml(xml, monitor)) {
+ mDescription += String.format("\nFailed to validate XML at %1$s", url);
+ return;
+ }
- // done
- monitor.incProgress(1);
- }
- });
+ monitor.setDescription("Parse XML");
+ monitor.incProgress(1);
+ parsePackages(xml, monitor);
+ if (mPackages.length == 0) {
+ mDescription += "\nNo packages found.";
+ } else if (mPackages.length == 1) {
+ mDescription += "\nOne package found.";
+ } else {
+ mDescription += String.format("\n%1$d packages found.", mPackages.length);
+ }
+
+ // done
+ monitor.incProgress(1);
}
private void setDefaultDescription() {
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java
index 0a70953..2e126e9 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSources.java
@@ -24,19 +24,10 @@
public class RepoSources {
private ArrayList<RepoSource> mSources = new ArrayList<RepoSource>();
- private ITaskFactory mTaskFactory;
public RepoSources() {
}
- public void setTaskFactory(ITaskFactory taskFactory) {
- mTaskFactory = taskFactory;
- }
-
- public ITaskFactory getTaskFactory() {
- return mTaskFactory;
- }
-
public void add(RepoSource source) {
mSources.add(source);
}
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
index 4cac706..1b23e8a 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java
@@ -40,7 +40,8 @@
/**
* Manually create a new package with one archive and the given attributes.
- * This is used to create packages from local directories.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
*/
ToolPackage(RepoSource source,
int revision,
@@ -49,9 +50,7 @@
String descUrl,
Os archiveOs,
Arch archiveArch,
- String archiveUrl,
- long archiveSize,
- String archiveChecksum) {
+ String archiveOsPath) {
super(source,
revision,
license,
@@ -59,9 +58,7 @@
descUrl,
archiveOs,
archiveArch,
- archiveUrl,
- archiveSize,
- archiveChecksum);
+ archiveOsPath);
}
/** Returns a short description for an {@link IDescription}. */
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
index d3dfb25..cd6d45b 100755
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java
@@ -26,7 +26,7 @@
/** The URL of the official Google sdk-repository site. */
public static final String URL_GOOGLE_SDK_REPO_SITE =
- "https://dl.google.com/android/eclipse/repository/repository.xml"; //$NON-NLS-1$
+ "https://dl.google.com/android/repository/repository.xml"; //$NON-NLS-1$
/** The XML namespace of the sdk-repository XML. */
public static final String NS_SDK_REPOSITORY =
diff --git a/tools/sdkmanager/libs/sdkuilib/.classpath b/tools/sdkmanager/libs/sdkuilib/.classpath
index 5e78ee7..5a1790f 100644
--- a/tools/sdkmanager/libs/sdkuilib/.classpath
+++ b/tools/sdkmanager/libs/sdkuilib/.classpath
@@ -1,8 +1,9 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
- <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/sdkmanager/libs/sdkuilib/src/Android.mk b/tools/sdkmanager/libs/sdkuilib/src/Android.mk
index 282de28..970c122 100644
--- a/tools/sdkmanager/libs/sdkuilib/src/Android.mk
+++ b/tools/sdkmanager/libs/sdkuilib/src/Android.mk
@@ -8,6 +8,7 @@
LOCAL_JAVA_LIBRARIES := \
sdklib \
+ androidprefs \
swt \
org.eclipse.jface_3.4.2.M20090107-0800 \
org.eclipse.equinox.common_3.4.0.v20080421-2006 \
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java
new file mode 100755
index 0000000..da5e2df
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java
@@ -0,0 +1,164 @@
+/*
+ * 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.internal.repository;
+
+import com.android.sdklib.internal.avd.AvdManager;
+import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
+import com.android.sdkuilib.internal.repository.UpdaterData.ISdkListener;
+import com.android.sdkuilib.internal.widgets.AvdSelector;
+import com.android.sdkuilib.internal.widgets.AvdSelector.SelectionMode;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+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.Label;
+
+import java.util.HashSet;
+
+public class AvdManagerPage extends Composite implements ISdkListener {
+
+ private Button mRefreshButton;
+ private AvdSelector mAvdSelector;
+
+ private final HashSet<String> mKnownAvdNames = new HashSet<String>();
+ private final UpdaterData mUpdaterData;
+
+ /**
+ * Create the composite.
+ * @param parent The parent of the composite.
+ * @param updaterData An instance of {@link UpdaterData}.
+ */
+ public AvdManagerPage(Composite parent, UpdaterData updaterData) {
+ super(parent, SWT.BORDER);
+
+ mUpdaterData = updaterData;
+ mUpdaterData.addListeners(this);
+
+ createContents(this);
+ postCreate(); //$hide$
+ }
+
+ private void createContents(Composite parent) {
+ parent.setLayout(new GridLayout(3, false));
+
+ Label label = new Label(parent, SWT.NONE);
+ label.setText("List of existing Android Virtual Devices:");
+ label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 2, 1));
+
+ mRefreshButton = new Button(parent, SWT.PUSH);
+ mRefreshButton.setText("Refresh");
+ mRefreshButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
+ mRefreshButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onRefreshSelected(); //$hide$
+ }
+ });
+
+ Composite group = new Composite(parent, SWT.NONE);
+ group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
+ GridLayout gl;
+ group.setLayout(gl = new GridLayout(1, false /*makeColumnsEqualWidth*/));
+ gl.marginHeight = gl.marginWidth = 0;
+
+ mAvdSelector = new AvdSelector(group,
+ SelectionMode.SELECT,
+ new AvdSelector.IExtraAction() {
+ public String label() {
+ return "Delete AVD...";
+ }
+
+ public boolean isEnabled() {
+ return mAvdSelector != null && mAvdSelector.getSelected() != null;
+ }
+
+ public void run() {
+ //TODO onDelete();
+ }
+ });
+ }
+
+ @Override
+ public void dispose() {
+ mUpdaterData.removeListener(this);
+ super.dispose();
+ }
+
+ @Override
+ protected void checkSubclass() {
+ // Disable the check that prevents subclassing of SWT components
+ }
+
+ // -- Start of internal part ----------
+ // Hide everything down-below from SWT designer
+ //$hide>>$
+
+ /**
+ * Called by the constructor right after {@link #createContents(Composite)}.
+ */
+ private void postCreate() {
+ reloadAvdList();
+ }
+
+ /**
+ * Reloads the AVD list in the AVD selector.
+ * Tries to preserve the selection.
+ */
+ private void reloadAvdList() {
+ AvdInfo selected = mAvdSelector.getSelected();
+
+ AvdInfo[] avds = null;
+
+ AvdManager manager = mUpdaterData.getAvdManager();
+ if (manager != null) {
+ avds = manager.getValidAvds();
+ }
+
+ mAvdSelector.setAvds(avds, null /*filter*/);
+
+ // Keep the list of known AVD names to check if they exist quickly. however
+ // use the list of all AVDs, including broken ones (unless we don't know their
+ // name).
+ mKnownAvdNames.clear();
+ if (manager != null) {
+ for (AvdInfo avd : manager.getAllAvds()) {
+ String name = avd.getName();
+ if (name != null) {
+ mKnownAvdNames.add(name);
+ }
+ }
+ }
+
+ mAvdSelector.setSelection(selected);
+ }
+
+ public void onSdkChange() {
+ reloadAvdList();
+ }
+
+ private void onRefreshSelected() {
+ mUpdaterData.reloadAvds();
+ reloadAvdList();
+ }
+
+ // End of hiding from SWT Designer
+ //$hide<<$
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ISettingsPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ISettingsPage.java
new file mode 100755
index 0000000..3930bf1
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ISettingsPage.java
@@ -0,0 +1,59 @@
+/*
+ * 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.internal.repository;
+
+import java.net.URL;
+import java.util.Properties;
+
+/**
+ * Interface that a settings page must implement.
+ */
+public interface ISettingsPage {
+
+ /** Java system setting picked up by {@link URL} for http proxy port. Type: String. */
+ public static final String KEY_HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$
+ /** Java system setting picked up by {@link URL} for http proxy host. Type: String. */
+ public static final String KEY_HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$
+ /** Setting to force using http:// instead of https:// connections. Type: Boolean. */
+ public static final String KEY_FORCE_HTTP = "sdkman.force.http"; //$NON-NLS-1$
+
+ /** Loads settings from the given {@link Properties} container and update the page UI. */
+ public abstract void loadSettings(Properties in_settings);
+
+ /** Called by the application to retrieve settings from the UI and store them in
+ * the given {@link Properties} container. */
+ public abstract void retrieveSettings(Properties out_settings);
+
+ /**
+ * Called by the application to give a callback that the page should invoke when
+ * settings have changed.
+ */
+ public abstract void setOnSettingsChanged(SettingsChangedCallback settingsChangedCallback);
+
+ /**
+ * Callback used to notify the application that settings have changed and need to be
+ * applied.
+ */
+ public interface SettingsChangedCallback {
+ /**
+ * Invoked by the settings page when settings have changed and need to be
+ * applied. The application will call {@link ISettingsPage#retrieveSettings(Properties)}
+ * and apply the new settings.
+ */
+ public abstract void onSettingsChanged(ISettingsPage page);
+ }
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java
index abc729e..1f0b23f 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java
@@ -16,10 +16,12 @@
package com.android.sdkuilib.internal.repository;
+import com.android.sdklib.internal.repository.Archive;
import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.ITask;
-import com.android.sdklib.internal.repository.ITaskMonitor;
+import com.android.sdklib.internal.repository.Package;
+import com.android.sdkuilib.internal.repository.UpdaterData.ISdkListener;
+import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
@@ -39,6 +41,8 @@
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
+import java.io.File;
+
/*
* TODO list
* - select => update desc, enable update + delete, enable home page if url
@@ -48,8 +52,9 @@
* - refresh callback
*/
-public class LocalPackagesPage extends Composite {
- private UpdaterData mUpdaterData;
+public class LocalPackagesPage extends Composite implements ISdkListener {
+
+ private final UpdaterData mUpdaterData;
private Label mSdkLocLabel;
private Text mSdkLocText;
@@ -63,19 +68,20 @@
private Label mPlaceholder1;
private Button mDeleteButton;
private Label mPlaceholder2;
- private Button mHomePageButton;
+ private Button mRefreshButton;
private Label mDescriptionLabel;
+
/**
* Create the composite.
* @param parent The parent of the composite.
- * @param updaterData An instance of {@link UpdaterData}. If null, a local
- * one will be allocated just to help with the SWT Designer.
+ * @param updaterData An instance of {@link UpdaterData}.
*/
public LocalPackagesPage(Composite parent, UpdaterData updaterData) {
super(parent, SWT.BORDER);
- mUpdaterData = updaterData != null ? updaterData : new UpdaterData();
+ mUpdaterData = updaterData;
+ mUpdaterData.addListeners(this);
createContents(this);
postCreate(); //$hide$
@@ -127,15 +133,27 @@
mPlaceholder1.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
mDeleteButton = new Button(mContainerButtons, SWT.NONE);
+ mDeleteButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onDeleteSelected(); //$hide$ (hide from SWT designer)
+ }
+ });
mDeleteButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));
mDeleteButton.setText("Delete...");
mPlaceholder2 = new Label(mContainerButtons, SWT.NONE);
mPlaceholder2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
- mHomePageButton = new Button(mContainerButtons, SWT.NONE);
- mHomePageButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
- mHomePageButton.setText("Home Page...");
+ mRefreshButton = new Button(mContainerButtons, SWT.NONE);
+ mRefreshButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onRefreshSelected(); //$hide$ (hide from SWT designer)
+ }
+ });
+ mRefreshButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ mRefreshButton.setText("Refresh");
}
private void createSdkLocation(Composite parent) {
@@ -163,6 +181,12 @@
}
@Override
+ public void dispose() {
+ mUpdaterData.removeListener(this);
+ super.dispose();
+ }
+
+ @Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
@@ -172,16 +196,6 @@
//$hide>>$
/**
- * Must be called once to set the adapter input for the package table viewer.
- */
- public void setInput(LocalSdkAdapter localSdkAdapter) {
- mTableViewerPackages.setLabelProvider( localSdkAdapter.getLabelProvider());
- mTableViewerPackages.setContentProvider(localSdkAdapter.getContentProvider());
- mTableViewerPackages.setInput(localSdkAdapter);
- onTreeSelected();
- }
-
- /**
* Called by the constructor right after {@link #createContents(Composite)}.
*/
private void postCreate() {
@@ -225,25 +239,63 @@
}
private void onUpdateInstalledPackage() {
- // TODO just a test, needs to be removed later.
- new ProgressTask(getShell(), "Test", new ITask() {
- public void run(ITaskMonitor monitor) {
- monitor.setDescription("Test");
- monitor.setProgressMax(100);
- int n = 0;
- int d = 1;
- while(!monitor.isCancelRequested()) {
- monitor.incProgress(d);
- n += d;
- if (n == 0 || n == 100) d = -d;
- try {
- Thread.sleep(5);
- } catch (InterruptedException e) {
- // ignore
+ if (mUpdaterData != null) {
+ mUpdaterData.reloadSdk();
+ }
+ }
+
+ private void onDeleteSelected() {
+ ISelection sel = mTableViewerPackages.getSelection();
+ if (sel instanceof IStructuredSelection) {
+ Object elem = ((IStructuredSelection) sel).getFirstElement();
+ if (elem instanceof Package) {
+
+ String title = "Delete SDK Package";
+ String error = null;
+
+ Package p = (Package) elem;
+ Archive[] archives = p.getArchives();
+ if (archives.length == 1 && archives[0] != null && archives[0].isLocal()) {
+ Archive archive = archives[0];
+ String osPath = archive.getLocalOsPath();
+
+ File dir = new File(osPath);
+ if (dir.isDirectory()) {
+ String msg = String.format("Are you sure you want to delete '%1$s' at '%2$s'? This cannot be undone.",
+ p.getShortDescription(), osPath);
+
+ if (MessageDialog.openQuestion(getShell(), title, msg)) {
+ archive.deleteLocal();
+
+ // refresh list
+ onRefreshSelected();
+ }
+ } else {
+ error = "Directory not found for this package";
}
+ } else {
+ error = "No local archive found for this package";
}
+
+ if (error != null) {
+ MessageDialog.openError(getShell(), title, error);
+ }
+
+ return;
}
- });
+ }
+ }
+
+ private void onRefreshSelected() {
+ mUpdaterData.reloadSdk();
+ }
+
+ public void onSdkChange() {
+ LocalSdkAdapter localSdkAdapter = mUpdaterData.getLocalSdkAdapter();
+ mTableViewerPackages.setLabelProvider( localSdkAdapter.getLabelProvider());
+ mTableViewerPackages.setContentProvider(localSdkAdapter.getContentProvider());
+ mTableViewerPackages.setInput(localSdkAdapter);
+ onTreeSelected();
}
// End of hiding from SWT Designer
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalSdkAdapter.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalSdkAdapter.java
index 330be18..1deee92 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalSdkAdapter.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalSdkAdapter.java
@@ -33,16 +33,10 @@
*/
class LocalSdkAdapter {
- private final LocalSdkParser mLocalSdkParser;
- private String mOsSdkRoot;
+ private final UpdaterData mUpdaterData;
- public LocalSdkAdapter(LocalSdkParser localSdkParser) {
- mLocalSdkParser = localSdkParser;
- }
-
- public void setSdkRoot(String osSdkRoot) {
- mOsSdkRoot = osSdkRoot;
- mLocalSdkParser.clearPackages();
+ public LocalSdkAdapter(UpdaterData updaterData) {
+ mUpdaterData = updaterData;
}
public ILabelProvider getLabelProvider() {
@@ -95,13 +89,13 @@
public Object[] getElements(Object inputElement) {
if (inputElement instanceof LocalSdkAdapter) {
LocalSdkAdapter adapter = (LocalSdkAdapter) inputElement;
- LocalSdkParser parser = adapter.mLocalSdkParser;
+ LocalSdkParser parser = adapter.mUpdaterData.getLocalSdkParser();
Package[] packages = parser.getPackages();
if (packages == null) {
// load on demand the first time
- packages = parser.parseSdk(adapter.mOsSdkRoot);
+ packages = parser.parseSdk(adapter.mUpdaterData.getOsSdkRoot());
}
if (packages != null) {
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressDialog.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressDialog.java
index a5cb86f..ca847c3 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressDialog.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressDialog.java
@@ -320,15 +320,15 @@
}
/**
- * Increments the current value of the progress bar.
+ * Sets the current value of the progress bar.
* This method can be invoked from a non-UI thread.
*/
- public void incProgress(final int delta) {
+ public void setProgress(final int value) {
if (!mDialogShell.isDisposed()) {
mDialogShell.getDisplay().syncExec(new Runnable() {
public void run() {
if (!mProgressBar.isDisposed()) {
- mProgressBar.setSelection(mProgressBar.getSelection() + delta);
+ mProgressBar.setSelection(value);
}
}
});
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java
index 709cce0..1ec7287 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ProgressTask.java
@@ -28,8 +28,12 @@
*/
class ProgressTask implements ITaskMonitor {
- private ProgressDialog mDialog;
+ private static final double MAX_COUNT = 10000.0;
+
+ private final ProgressDialog mDialog;
private boolean mAutomaticallyCloseOnTaskCompletion = true;
+ private double mIncCoef = 0;
+ private double mValue = 0;
/**
@@ -46,29 +50,39 @@
/**
* Sets the description in the current task dialog.
- * This method can be invoke from a non-UI thread.
+ * This method can be invoked from a non-UI thread.
*/
- public void setDescription(final String descriptionFormat, final Object...args) {
+ public void setDescription(String descriptionFormat, Object...args) {
mDialog.setDescription(descriptionFormat, args);
}
/**
* Sets the description in the current task dialog.
- * This method can be invoke from a non-UI thread.
+ * This method can be invoked from a non-UI thread.
*/
- public void setResult(final String resultFormat, final Object...args) {
+ public void setResult(String resultFormat, Object...args) {
mAutomaticallyCloseOnTaskCompletion = false;
mDialog.setResult(resultFormat, args);
}
/**
* Sets the max value of the progress bar.
- * This method can be invoke from a non-UI thread.
+ * This method can be invoked from a non-UI thread.
+ *
+ * Weird things will happen if setProgressMax is called multiple times
+ * *after* {@link #incProgress(int)}: we don't try to adjust it on the
+ * fly.
*
* @see ProgressBar#setMaximum(int)
*/
- public void setProgressMax(final int max) {
- mDialog.setProgressMax(max);
+ public void setProgressMax(int max) {
+ assert max > 0;
+ // Always set the dialog's progress max to 10k since it only handles
+ // integers and we want to have a better inner granularity. Instead
+ // we use the max to compute a coefficient for inc deltas.
+ mDialog.setProgressMax((int) MAX_COUNT);
+ mIncCoef = max > 0 ? MAX_COUNT / max : 0;
+ assert mIncCoef > 0;
}
/**
@@ -76,8 +90,15 @@
*
* This method can be invoked from a non-UI thread.
*/
- public void incProgress(final int delta) {
- mDialog.incProgress(delta);
+ public void incProgress(int delta) {
+ assert mIncCoef > 0;
+ assert delta > 0;
+ internalIncProgress(delta * mIncCoef);
+ }
+
+ private void internalIncProgress(double realDelta) {
+ mValue += realDelta;
+ mDialog.setProgress((int)mValue);
}
/**
@@ -87,7 +108,8 @@
* This method can be invoked from a non-UI thread.
*/
public int getProgress() {
- return mDialog.getProgress();
+ assert mIncCoef > 0;
+ return mIncCoef > 0 ? (int)(mDialog.getProgress() / mIncCoef) : 0;
}
/**
@@ -119,4 +141,95 @@
}
return null;
}
+
+ /**
+ * Creates a sub-monitor that will use up to tickCount on the progress bar.
+ * tickCount must be 1 or more.
+ */
+ public ITaskMonitor createSubMonitor(int tickCount) {
+ assert mIncCoef > 0;
+ assert tickCount > 0;
+ return new SubTaskMonitor(this, null, mValue, tickCount * mIncCoef);
+ }
+
+ private interface ISubTaskMonitor extends ITaskMonitor {
+ public void subIncProgress(double realDelta);
+ }
+
+ private static class SubTaskMonitor implements ISubTaskMonitor {
+
+ private final ProgressTask mRoot;
+ private final ISubTaskMonitor mParent;
+ private final double mStart;
+ private final double mSpan;
+ private double mSubValue;
+ private double mSubCoef;
+
+ /**
+ * Creates a new sub task monitor which will work for the given range [start, start+span]
+ * in its parent.
+ *
+ * @param root The ProgressTask root
+ * @param parent The immediate parent. Can be the null or another sub task monitor.
+ * @param start The start value in the root's coordinates
+ * @param span The span value in the root's coordinates
+ */
+ public SubTaskMonitor(ProgressTask root,
+ ISubTaskMonitor parent,
+ double start,
+ double span) {
+ mRoot = root;
+ mParent = parent;
+ mStart = start;
+ mSpan = span;
+ mSubValue = start;
+ }
+
+ public boolean isCancelRequested() {
+ return mRoot.isCancelRequested();
+ }
+
+ public void setDescription(String descriptionFormat, Object... args) {
+ mRoot.setDescription(descriptionFormat, args);
+ }
+
+ public void setResult(String resultFormat, Object... args) {
+ mRoot.setResult(resultFormat, args);
+ }
+
+ public void setProgressMax(int max) {
+ assert max > 0;
+ mSubCoef = max > 0 ? mSpan / max : 0;
+ assert mSubCoef > 0;
+ }
+
+ public int getProgress() {
+ assert mSubCoef > 0;
+ return mSubCoef > 0 ? (int)((mSubValue - mStart) / mSubCoef) : 0;
+ }
+
+ public void incProgress(int delta) {
+ assert mSubCoef > 0;
+ subIncProgress(delta * mSubCoef);
+ }
+
+ public void subIncProgress(double realDelta) {
+ mSubValue += realDelta;
+ if (mParent != null) {
+ mParent.subIncProgress(realDelta);
+ } else {
+ mRoot.internalIncProgress(realDelta);
+ }
+ }
+
+ public ITaskMonitor createSubMonitor(int tickCount) {
+ assert mSubCoef > 0;
+ assert tickCount > 0;
+ return new SubTaskMonitor(mRoot,
+ this,
+ mSubValue,
+ tickCount * mSubCoef);
+ }
+ }
+
}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java
index 1fe5ca4..e497462 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java
@@ -19,6 +19,9 @@
import com.android.sdklib.internal.repository.Archive;
import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.Package;
+import com.android.sdklib.internal.repository.RepoSource;
+import com.android.sdkuilib.internal.repository.UpdaterData.ISdkListener;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
@@ -26,6 +29,7 @@
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
@@ -57,9 +61,8 @@
* - install selected callback
*/
-public class RemotePackagesPage extends Composite {
+public class RemotePackagesPage extends Composite implements ISdkListener {
- private final UpdaterWindowImpl mUpdaterWindow;
private final UpdaterData mUpdaterData;
private CheckboxTreeViewer mTreeViewerSources;
@@ -77,17 +80,13 @@
/**
* Create the composite.
* @param parent The parent of the composite.
- * @param updaterData An instance of {@link UpdaterData}. If null, a local
- * one will be allocated just to help with the SWT Designer.
- * @param updaterWindow The parent window.
+ * @param updaterData An instance of {@link UpdaterData}.
*/
- RemotePackagesPage(Composite parent,
- UpdaterData updaterData,
- UpdaterWindowImpl updaterWindow) {
+ RemotePackagesPage(Composite parent, UpdaterData updaterData) {
super(parent, SWT.BORDER);
- mUpdaterWindow = updaterWindow;
- mUpdaterData = updaterData != null ? updaterData : new UpdaterData();
+ mUpdaterData = updaterData;
+ mUpdaterData.addListeners(this);
createContents(this);
postCreate(); //$hide$
@@ -131,15 +130,33 @@
mDescriptionLabel.setText("Line1\nLine2\nLine3");
mAddSiteButton = new Button(parent, SWT.NONE);
+ mAddSiteButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onAddSiteSelected(); //$hide$
+ }
+ });
mAddSiteButton.setText("Add Site...");
mRemoveSiteButton = new Button(parent, SWT.NONE);
+ mRemoveSiteButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onRemoveSiteSelected(); //$hide$
+ }
+ });
mRemoveSiteButton.setText("Delete Site...");
mPlaceholder3 = new Label(parent, SWT.NONE);
mPlaceholder3.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 1, 1));
mRefreshButton = new Button(parent, SWT.NONE);
+ mRefreshButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onRefreshSelected(); //$hide$
+ }
+ });
mRefreshButton.setText("Refresh");
mInstallSelectedButton = new Button(parent, SWT.NONE);
@@ -153,6 +170,12 @@
}
@Override
+ public void dispose() {
+ mUpdaterData.removeListener(this);
+ super.dispose();
+ }
+
+ @Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
@@ -162,16 +185,6 @@
//$hide>>$
/**
- * Must be called once to set the adapter input for the sources tree viewer.
- */
- public void setInput(RepoSourcesAdapter sources) {
- mTreeViewerSources.setContentProvider(sources.getContentProvider());
- mTreeViewerSources.setLabelProvider( sources.getLabelProvider());
- mTreeViewerSources.setInput(sources);
- onTreeSelected();
- }
-
- /**
* Called by the constructor right after {@link #createContents(Composite)}.
*/
private void postCreate() {
@@ -214,15 +227,56 @@
mDescriptionLabel.setText(""); //$NON-NLS1-$
}
+ /**
+ * Handle checking and unchecking of the tree items.
+ *
+ * When unchecking, all sub-tree items checkboxes are cleared too.
+ * When checking a source, all of its packages are checked too.
+ * When checking a package, only its compatible archives are checked.
+ */
private void onTreeCheckStateChanged(CheckStateChangedEvent event) {
boolean b = event.getChecked();
Object elem = event.getElement(); // Will be Archive or Package or RepoSource
- Object src = event.getSource();
- // TODO
+
+ assert event.getSource() == mTreeViewerSources;
+
+ // when deselecting, we just deselect all children too
+ if (b == false) {
+ mTreeViewerSources.setSubtreeChecked(elem, b);
+ return;
+ }
+
+ ITreeContentProvider provider =
+ (ITreeContentProvider) mTreeViewerSources.getContentProvider();
+
+ // When selecting, we want to only select compatible archives.
+ if (elem instanceof RepoSource) {
+ mTreeViewerSources.setExpandedState(elem, true);
+ for (Object pkg : provider.getChildren(elem)) {
+ mTreeViewerSources.setChecked(pkg, true);
+ selectCompatibleArchives(pkg, provider);
+ }
+ } else if (elem instanceof Package) {
+ selectCompatibleArchives(elem, provider);
+ }
+ }
+
+ private void selectCompatibleArchives(Object pkg, ITreeContentProvider provider) {
+ mTreeViewerSources.setExpandedState(pkg, true);
+ for (Object archive : provider.getChildren(pkg)) {
+ if (archive instanceof Archive) {
+ if (((Archive) archive).isCompatible()) {
+ mTreeViewerSources.setChecked(archive, true);
+ } else {
+ mTreeViewerSources.setChecked(archive, false);
+ // TODO change the item image to mark it incompatible
+ }
+ }
+ }
}
private void onTreeDoubleClick(DoubleClickEvent event) {
- // TODO
+ // TODO use or remove
}
private void onInstallSelectedArchives() {
@@ -234,7 +288,31 @@
}
}
- mUpdaterWindow.installArchives(archives);
+ if (mUpdaterData != null) {
+ mUpdaterData.installArchives(archives);
+ }
+ }
+
+ private void onAddSiteSelected() {
+ // TODO prompt for new addon site URL, store, refresh
+ }
+
+ private void onRemoveSiteSelected() {
+ // TODO prompt for removing addon site URL, store, refresh
+ }
+
+ private void onRefreshSelected() {
+ if (mUpdaterData != null) {
+ mUpdaterData.refreshSources(false /*forceFetching*/, null /*monitor*/);
+ }
+ }
+
+ public void onSdkChange() {
+ RepoSourcesAdapter sources = mUpdaterData.getSourcesAdapter();
+ mTreeViewerSources.setContentProvider(sources.getContentProvider());
+ mTreeViewerSources.setLabelProvider( sources.getLabelProvider());
+ mTreeViewerSources.setInput(sources);
+ onTreeSelected();
}
// End of hiding from SWT Designer
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java
index 3f020d1..fa530fa 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java
@@ -16,11 +16,17 @@
package com.android.sdkuilib.internal.repository;
+import com.android.sdklib.internal.repository.AddonPackage;
import com.android.sdklib.internal.repository.Archive;
+import com.android.sdklib.internal.repository.DocPackage;
import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.ITask;
+import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.Package;
+import com.android.sdklib.internal.repository.PlatformPackage;
import com.android.sdklib.internal.repository.RepoSource;
-import com.android.sdklib.internal.repository.RepoSources;
+import com.android.sdklib.internal.repository.ToolPackage;
+import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
@@ -36,10 +42,10 @@
*/
class RepoSourcesAdapter {
- private final RepoSources mRepoSources;
+ private final UpdaterData mUpdaterData;
- public RepoSourcesAdapter(RepoSources repoSources) {
- mRepoSources = repoSources;
+ public RepoSourcesAdapter(UpdaterData updaterData) {
+ mUpdaterData = updaterData;
}
public ILabelProvider getLabelProvider() {
@@ -53,10 +59,42 @@
// ------------
- public static class ViewerLabelProvider extends LabelProvider {
+ public class ViewerLabelProvider extends LabelProvider {
+
/** Returns null by default */
@Override
public Image getImage(Object element) {
+
+ ImageFactory imgFactory = mUpdaterData.getImageFactory();
+
+ if (imgFactory != null) {
+ if (element instanceof RepoSource) {
+ return imgFactory.getImage("source_icon16.png");
+
+ } else if (element instanceof PlatformPackage) {
+ return imgFactory.getImage("red_ball_icon16.png");
+
+ } else if (element instanceof AddonPackage) {
+ return imgFactory.getImage("green_ball_icon16.png");
+
+ } else if (element instanceof ToolPackage) {
+ return imgFactory.getImage("blue_ball_icon16.png");
+
+ } else if (element instanceof DocPackage) {
+ return imgFactory.getImage("purple_ball_icon16.png");
+
+ } else if (element instanceof Package) {
+ return imgFactory.getImage("gray_ball_icon16.png");
+
+ } else if (element instanceof Archive) {
+ if (((Archive) element).isCompatible()) {
+ return imgFactory.getImage("archive_icon16.png");
+ } else {
+ return imgFactory.getImage("incompat_icon16.png");
+ }
+ }
+ }
+
return super.getImage(element);
}
@@ -72,9 +110,7 @@
// ------------
- private static class TreeContentProvider implements ITreeContentProvider {
-
- private RepoSourcesAdapter mInput;
+ private class TreeContentProvider implements ITreeContentProvider {
// Called when the viewer is disposed
public void dispose() {
@@ -83,9 +119,7 @@
// Called when the input is set or changed on the provider
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
- assert newInput == null || newInput instanceof RepoSourcesAdapter;
- mInput = (RepoSourcesAdapter) newInput;
- // pass
+ assert newInput == RepoSourcesAdapter.this;
}
/**
@@ -106,15 +140,23 @@
* For a {@link Package}, returns an array of {@link Archive}s.
*/
public Object[] getChildren(Object parentElement) {
- if (parentElement instanceof RepoSourcesAdapter) {
- return ((RepoSourcesAdapter) parentElement).mRepoSources.getSources().toArray();
+ if (parentElement == RepoSourcesAdapter.this) {
+ return mUpdaterData.getSources().getSources().toArray();
} else if (parentElement instanceof RepoSource) {
- RepoSource source = (RepoSource) parentElement;
+ final RepoSource source = (RepoSource) parentElement;
Package[] packages = source.getPackages();
if (packages == null) {
- source.load(mInput.mRepoSources.getTaskFactory());
+
+ final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp();
+
+ mUpdaterData.getTaskFactory().start("Loading Source", new ITask() {
+ public void run(ITaskMonitor monitor) {
+ source.load(monitor, forceHttp);
+ }
+ });
+
packages = source.getPackages();
}
if (packages != null) {
@@ -135,7 +177,7 @@
public Object getParent(Object element) {
if (element instanceof RepoSource) {
- return mInput;
+ return RepoSourcesAdapter.this;
} else if (element instanceof Package) {
return ((Package) element).getParentSource();
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SettingsController.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SettingsController.java
new file mode 100755
index 0000000..086e547
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SettingsController.java
@@ -0,0 +1,150 @@
+/*
+ * 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.internal.repository;
+
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ *
+ */
+public class SettingsController {
+
+ private static final String SETTINGS_FILENAME = "androidtool.cfg"; //$NON-NLS-1$
+
+ private final Properties mProperties = new Properties();
+
+ private ISettingsPage mSettingsPage;
+
+ public SettingsController() {
+ }
+
+ //--- Access to settings ------------
+
+ public boolean getForceHttp() {
+ return Boolean.parseBoolean(mProperties.getProperty(ISettingsPage.KEY_FORCE_HTTP));
+ }
+
+ //--- Controller methods -------------
+
+ /**
+ * Associate the given {@link ISettingsPage} with this {@link SettingsController}.
+ *
+ * This loads the current properties into the setting page UI.
+ * It then associates the SettingsChanged callback with this controller.
+ */
+ public void setSettingsPage(ISettingsPage settingsPage) {
+
+ mSettingsPage = settingsPage;
+ mSettingsPage.loadSettings(mProperties);
+
+ settingsPage.setOnSettingsChanged(new ISettingsPage.SettingsChangedCallback() {
+ public void onSettingsChanged(ISettingsPage page) {
+ SettingsController.this.onSettingsChanged();
+ }
+ });
+ }
+
+ /**
+ * Load settings from the settings file.
+ */
+ public void loadSettings() {
+ FileInputStream fis = null;
+ try {
+ String folder = AndroidLocation.getFolder();
+ File f = new File(folder, SETTINGS_FILENAME);
+ if (f.exists()) {
+ fis = new FileInputStream(f);
+
+ mProperties.load(fis);
+ }
+
+ } catch (AndroidLocationException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Saves settings to the settings file.
+ */
+ public void saveSettings() {
+
+ FileOutputStream fos = null;
+ try {
+ String folder = AndroidLocation.getFolder();
+ File f = new File(folder, SETTINGS_FILENAME);
+
+ fos = new FileOutputStream(f);
+
+ mProperties.store( fos, "## Settings for Android Tool"); //$NON-NLS-1$
+
+ } catch (AndroidLocationException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * When settings have changed: retrieve the new settings, apply them and save them.
+ *
+ * This updats Java system properties for the HTTP proxy.
+ */
+ private void onSettingsChanged() {
+ if (mSettingsPage == null) {
+ return;
+ }
+
+ mSettingsPage.retrieveSettings(mProperties);
+ applySettings();
+ saveSettings();
+ }
+
+ /**
+ * Applies the current settings.
+ */
+ public void applySettings() {
+ Properties props = System.getProperties();
+ props.setProperty(ISettingsPage.KEY_HTTP_PROXY_HOST,
+ mProperties.getProperty(ISettingsPage.KEY_HTTP_PROXY_HOST, "")); //$NON-NLS-1$
+ props.setProperty(ISettingsPage.KEY_HTTP_PROXY_PORT,
+ mProperties.getProperty(ISettingsPage.KEY_HTTP_PROXY_PORT, "")); //$NON-NLS-1$
+ }
+
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
index 47ce3ac..ad38a82 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
@@ -16,32 +16,79 @@
package com.android.sdkuilib.internal.repository;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.ISdkLog;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.avd.AvdManager;
+import com.android.sdklib.internal.repository.Archive;
+import com.android.sdklib.internal.repository.ITask;
+import com.android.sdklib.internal.repository.ITaskFactory;
+import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.LocalSdkParser;
+import com.android.sdklib.internal.repository.RepoSource;
import com.android.sdklib.internal.repository.RepoSources;
+import com.android.sdkuilib.internal.repository.icons.ImageFactory;
+
+import org.eclipse.swt.widgets.Display;
+
+import java.util.ArrayList;
+import java.util.Collection;
/**
* Data shared between {@link UpdaterWindowImpl} and its pages.
*/
class UpdaterData {
- private ISdkLog mSdkLog;
private String mOsSdkRoot;
+
+ private final ISdkLog mSdkLog;
+ private ITaskFactory mTaskFactory;
private boolean mUserCanChangeSdkRoot;
+ private SdkManager mSdkManager;
+ private AvdManager mAvdManager;
+
private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();
private final RepoSources mSources = new RepoSources();
- private final LocalSdkAdapter mLocalSdkAdapter = new LocalSdkAdapter(mLocalSdkParser);
- private final RepoSourcesAdapter mSourcesAdapter = new RepoSourcesAdapter(mSources);
+ private final LocalSdkAdapter mLocalSdkAdapter = new LocalSdkAdapter(this);
+ private final RepoSourcesAdapter mSourcesAdapter = new RepoSourcesAdapter(this);
+
+ private ImageFactory mImageFactory;
+
+ private final SettingsController mSettingsController = new SettingsController();
+
+ private final ArrayList<ISdkListener> mListeners = new ArrayList<ISdkListener>();
+
+ public interface ISdkListener {
+ void onSdkChange();
+ }
+
+ public UpdaterData(String osSdkRoot, ISdkLog sdkLog) {
+ mOsSdkRoot = osSdkRoot;
+ mSdkLog = sdkLog;
+
+ initSdk();
+ }
public void setOsSdkRoot(String osSdkRoot) {
- mOsSdkRoot = osSdkRoot;
+ if (mOsSdkRoot == null || mOsSdkRoot.equals(osSdkRoot) == false) {
+ mOsSdkRoot = osSdkRoot;
+ initSdk();
+ }
}
public String getOsSdkRoot() {
return mOsSdkRoot;
}
+ public void setTaskFactory(ITaskFactory taskFactory) {
+ mTaskFactory = taskFactory;
+ }
+
+ public ITaskFactory getTaskFactory() {
+ return mTaskFactory;
+ }
+
public void setUserCanChangeSdkRoot(boolean userCanChangeSdkRoot) {
mUserCanChangeSdkRoot = userCanChangeSdkRoot;
}
@@ -66,11 +113,236 @@
return mLocalSdkAdapter;
}
- public void setSdkLog(ISdkLog sdkLog) {
- mSdkLog = sdkLog;
- }
-
public ISdkLog getSdkLog() {
return mSdkLog;
}
+
+ public void setImageFactory(ImageFactory imageFactory) {
+ mImageFactory = imageFactory;
+ }
+
+ public ImageFactory getImageFactory() {
+ return mImageFactory;
+ }
+
+ public SdkManager getSdkManager() {
+ return mSdkManager;
+ }
+
+ public AvdManager getAvdManager() {
+ return mAvdManager;
+ }
+
+ public SettingsController getSettingsController() {
+ return mSettingsController;
+ }
+
+ public void addListeners(ISdkListener listener) {
+ if (mListeners.contains(listener) == false) {
+ mListeners.add(listener);
+ }
+ }
+
+ public void removeListener(ISdkListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Reloads the SDK content (targets).
+ * <p/> This also reloads the AVDs in case their status changed.
+ * <p/>This does not notify the listeners ({@link ISdkListener}).
+ */
+ public void reloadSdk() {
+ // reload SDK
+ mSdkManager.reloadSdk(mSdkLog);
+
+ // reload AVDs
+ if (mAvdManager != null) {
+ try {
+ mAvdManager.reloadAvds();
+ } catch (AndroidLocationException e) {
+ // FIXME
+ }
+ }
+
+ // notify adapters?
+ // TODO
+
+ // notify listeners
+ notifyListeners();
+ }
+
+ /**
+ * Reloads the AVDs.
+ * <p/>This does not notify the listeners.
+ */
+ public void reloadAvds() {
+ // reload AVDs
+ if (mAvdManager != null) {
+ try {
+ mAvdManager.reloadAvds();
+ } catch (AndroidLocationException e) {
+ // FIXME
+ }
+ }
+ }
+
+ /**
+ * Notify the listeners ({@link ISdkListener}) that the SDK was reloaded.
+ * <p/>This can be called from any thread.
+ */
+ public void notifyListeners() {
+ Display display = Display.getCurrent();
+ if (display != null && mListeners.size() > 0) {
+ display.syncExec(new Runnable() {
+ public void run() {
+ for (ISdkListener listener : mListeners) {
+ try {
+ listener.onSdkChange();
+ } catch (Throwable t) {
+ // TODO: log error
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Install the list of given {@link Archive}s. This is invoked by the user selecting some
+ * packages in the remote page and then clicking "install selected".
+ *
+ * @param archives The archives to install. Incompatible ones will be skipped.
+ */
+ public void installArchives(final Collection<Archive> archives) {
+ if (mTaskFactory == null) {
+ throw new IllegalArgumentException("Task Factory is null");
+ }
+
+ final boolean forceHttp = getSettingsController().getForceHttp();
+
+ // TODO filter the archive list to: a/ display a list of what is going to be installed,
+ // b/ display licenses and c/ check that the selected packages are actually upgrades
+ // or ask user to confirm downgrades. All this should be done in a separate class+window
+ // which will then call this method with the final list.
+
+ mTaskFactory.start("Installing Archives", new ITask() {
+ public void run(ITaskMonitor monitor) {
+
+ final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;
+ monitor.setProgressMax(archives.size() * progressPerArchive);
+ monitor.setDescription("Preparing to install archives");
+
+ int numInstalled = 0;
+ for (Archive archive : archives) {
+
+ int nextProgress = monitor.getProgress() + progressPerArchive;
+ try {
+ if (monitor.isCancelRequested()) {
+ break;
+ }
+
+ if (archive.install(mOsSdkRoot, forceHttp, monitor)) {
+ numInstalled++;
+ }
+
+ } catch (Throwable t) {
+ // Display anything unexpected in the monitor.
+ monitor.setResult("Unexpected Error: %1$s", t.getMessage());
+
+ } finally {
+
+ // Always move the progress bar to the desired position.
+ // This allows internal methods to not have to care in case
+ // they abort early
+ monitor.incProgress(nextProgress - monitor.getProgress());
+ }
+ }
+
+ if (numInstalled == 0) {
+ monitor.setDescription("Done. Nothing was installed.");
+ } else {
+ monitor.setDescription("Done. %1$d %2$s installed.",
+ numInstalled,
+ numInstalled == 1 ? "package" : "packages");
+ }
+ }
+ });
+ }
+
+ /**
+ * Tries to update all the *existing* local packages.
+ * This first refreshes all sources, then compares the available remote packages when
+ * the current local ones and suggest updates to be done to the user. Finally all
+ * selected updates are installed.
+ */
+ public void updateAll() {
+ assert mTaskFactory != null;
+
+ mTaskFactory.start("Update Archives", new ITask() {
+ public void run(ITaskMonitor monitor) {
+ monitor.setProgressMax(3);
+
+ monitor.setDescription("Refresh sources");
+ refreshSources(true, monitor.createSubMonitor(1));
+
+ // TODO compare available vs local
+ // TODO suggest update packages to user (also validate license click-through)
+ // TODO install selected packages
+ }
+ });
+ }
+
+ /**
+ * Refresh all sources. This is invoked either internally (reusing an existing monitor)
+ * or as a UI callback on the remote page "Refresh" button (in which case the monitor is
+ * null and a new task should be created.)
+ *
+ * @param forceFetching When true, load sources that haven't been loaded yet. When
+ * false, only refresh sources that have been loaded yet.
+ */
+ public void refreshSources(final boolean forceFetching, ITaskMonitor monitor) {
+ assert mTaskFactory != null;
+
+ final boolean forceHttp = getSettingsController().getForceHttp();
+
+ ITask task = new ITask() {
+ public void run(ITaskMonitor monitor) {
+ ArrayList<RepoSource> sources = mSources.getSources();
+ monitor.setProgressMax(sources.size());
+ for (RepoSource source : sources) {
+ if (forceFetching || source.getPackages() != null) {
+ source.load(monitor.createSubMonitor(1), forceHttp);
+ }
+ monitor.incProgress(1);
+
+ }
+ }
+ };
+
+ if (monitor != null) {
+ task.run(monitor);
+ } else {
+ mTaskFactory.start("Refresh Sources", task);
+ }
+ }
+
+ /**
+ * Initializes the {@link SdkManager} and the {@link AvdManager}.
+ */
+ private void initSdk() {
+ mSdkManager = SdkManager.createManager(mOsSdkRoot, mSdkLog);
+ try {
+ mAvdManager = null; // remove the old one if needed.
+ mAvdManager = new AvdManager(mSdkManager, mSdkLog);
+ } catch (AndroidLocationException e) {
+ mSdkLog.error(e, "Unable to read AVDs");
+ }
+
+ // notify adapters/parsers
+ // TODO
+
+ // notify listeners.
+ notifyListeners();
+ }
}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
index e4d15f5..0d1ead0 100755
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java
@@ -19,22 +19,17 @@
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
-import com.android.sdklib.internal.repository.Archive;
-import com.android.sdklib.internal.repository.ITask;
-import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.RepoSource;
import com.android.sdklib.repository.SdkRepository;
+import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import org.eclipse.swt.SWT;
-import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
@@ -42,10 +37,8 @@
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
-import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
-import java.util.Collection;
/**
* This is the private implementation of the UpdateWindow.
@@ -53,7 +46,7 @@
public class UpdaterWindowImpl {
/** Internal data shared between the window and its pages. */
- private final UpdaterData mUpdaterData = new UpdaterData();
+ private final UpdaterData mUpdaterData;
/** The array of pages instances. Only one is visible at a time. */
private ArrayList<Composite> mPages = new ArrayList<Composite>();
/** Indicates a page change is due to an internal request. Prevents callbacks from looping. */
@@ -73,12 +66,11 @@
private Composite mPagesRootComposite;
private LocalPackagesPage mLocalPackagePage;
private RemotePackagesPage mRemotePackagesPage;
+ private AvdManagerPage mAvdManagerPage;
private StackLayout mStackLayout;
- private Image mIconImage;
public UpdaterWindowImpl(ISdkLog sdkLog, String osSdkRoot, boolean userCanChangeSdkRoot) {
- mUpdaterData.setSdkLog(sdkLog);
- mUpdaterData.setOsSdkRoot(osSdkRoot);
+ mUpdaterData = new UpdaterData(osSdkRoot, sdkLog);
mUpdaterData.setUserCanChangeSdkRoot(userCanChangeSdkRoot);
}
@@ -108,7 +100,6 @@
*/
protected void createContents() {
mAndroidSdkUpdater = new Shell();
- setWindowImage(mAndroidSdkUpdater);
mAndroidSdkUpdater.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
onAndroidSdkUpdaterDispose(); //$hide$ (hide from SWT designer)
@@ -136,8 +127,9 @@
mStackLayout = new StackLayout();
mPagesRootComposite.setLayout(mStackLayout);
+ mAvdManagerPage = new AvdManagerPage(mPagesRootComposite, mUpdaterData);
mLocalPackagePage = new LocalPackagesPage(mPagesRootComposite, mUpdaterData);
- mRemotePackagesPage = new RemotePackagesPage(mPagesRootComposite, mUpdaterData, this);
+ mRemotePackagesPage = new RemotePackagesPage(mPagesRootComposite, mUpdaterData);
mSashForm.setWeights(new int[] {150, 576});
}
@@ -177,33 +169,27 @@
* Callback called when the window shell is disposed.
*/
private void onAndroidSdkUpdaterDispose() {
- if (mIconImage != null) {
- mIconImage.dispose();
- mIconImage = null;
+ if (mUpdaterData != null) {
+ ImageFactory imgFactory = mUpdaterData.getImageFactory();
+ if (imgFactory != null) {
+ imgFactory.dispose();
+ }
}
}
/**
* Creates the icon of the window shell.
- * The icon is disposed by {@link #onAndroidSdkUpdaterDispose()}.
*/
private void setWindowImage(Shell androidSdkUpdater) {
- String image = "android_icon_16.png"; //$NON-NLS-1$
+ String imageName = "android_icon_16.png"; //$NON-NLS-1$
if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
- image = "android_icon_128.png"; //$NON-NLS-1$
+ imageName = "android_icon_128.png"; //$NON-NLS-1$
}
- InputStream stream = getClass().getResourceAsStream(image);
- if (stream != null) {
- try {
- ImageData imgData = new ImageData(stream);
- mIconImage = new Image(mAndroidSdkUpdater.getDisplay(),
- imgData,
- imgData.getTransparencyMask());
- mAndroidSdkUpdater.setImage(mIconImage);
- } catch (SWTException e) {
- mUpdaterData.getSdkLog().error(e, "Failed to set window icon"); //$NON-NLS-1$
- } catch (IllegalArgumentException e) {
- mUpdaterData.getSdkLog().error(e, "Failed to set window icon"); //$NON-NLS-1$
+
+ if (mUpdaterData != null) {
+ ImageFactory imgFactory = mUpdaterData.getImageFactory();
+ if (imgFactory != null) {
+ mAndroidSdkUpdater.setImage(imgFactory.getImage(imageName));
}
}
}
@@ -214,7 +200,12 @@
*/
private void firstInit() {
mTaskFactory = new ProgressTaskFactory(getShell());
+ mUpdaterData.setTaskFactory(mTaskFactory);
+ mUpdaterData.setImageFactory(new ImageFactory(getShell().getDisplay()));
+ setWindowImage(mAndroidSdkUpdater);
+
+ addPage(mAvdManagerPage, "Virtual Devices");
addPage(mLocalPackagePage, "Installed Packages");
addPage(mRemotePackagesPage, "Available Packages");
addExtraPages();
@@ -222,10 +213,10 @@
displayPage(0);
mPageList.setSelection(0);
- // TODO read and apply settings
// TODO read add-on sources from some file
setupSources();
- scanLocalSdkFolders();
+ initializeSettings();
+ mUpdaterData.notifyListeners();
}
// --- page switching ---
@@ -311,82 +302,39 @@
* Used to initialize the sources.
*/
private void setupSources() {
- mUpdaterData.getSources().setTaskFactory(mTaskFactory);
-
mUpdaterData.getSources().add(
new RepoSource(SdkRepository.URL_GOOGLE_SDK_REPO_SITE, false /* addonOnly */));
- String url = System.getenv("TEMP_SDK_URL"); // TODO STOPSHIP temporary remove before shipping
- if (url != null) {
- mUpdaterData.getSources().add(new RepoSource(url, false /* addonOnly */));
+ String str = System.getenv("TEMP_SDK_URL"); // TODO STOPSHIP temporary remove before shipping
+ if (str != null) {
+ String[] urls = str.split(";");
+ for (String url : urls) {
+ mUpdaterData.getSources().add(new RepoSource(url, false /* addonOnly */));
+ }
}
- mRemotePackagesPage.setInput(mUpdaterData.getSourcesAdapter());
+ mRemotePackagesPage.onSdkChange();
}
/**
- * Used to scan the local SDK folders the first time.
+ * Initializes settings.
+ * This must be called after addExtraPages(), which created a settings page.
+ * Iterate through all the pages to find the first (and supposedly unique) setting page,
+ * and use it to load and apply these settings.
*/
- private void scanLocalSdkFolders() {
- mUpdaterData.getLocalSdkAdapter().setSdkRoot(mUpdaterData.getOsSdkRoot());
+ private void initializeSettings() {
+ SettingsController c = mUpdaterData.getSettingsController();
+ c.loadSettings();
+ c.applySettings();
- mLocalPackagePage.setInput(mUpdaterData.getLocalSdkAdapter());
- }
+ for (Object page : mPages) {
+ if (page instanceof ISettingsPage) {
+ ISettingsPage settingsPage = (ISettingsPage) page;
- /**
- * Install the list of given {@link Archive}s.
- * @param archives The archives to install. Incompatible ones will be skipped.
- */
- public void installArchives(final Collection<Archive> archives) {
-
- // TODO filter the archive list to: a/ display a list of what is going to be installed,
- // b/ display licenses and c/ check that the selected packages are actually upgrades
- // or ask user to confirm downgrades. All this should be done in a separate class+window
- // which will then call this method with the final list.
-
- // TODO move most parts to SdkLib, maybe as part of Archive, making archives self-installing.
- mTaskFactory.start("Installing Archives", new ITask() {
- public void run(ITaskMonitor monitor) {
-
- final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;
- monitor.setProgressMax(archives.size() * progressPerArchive);
- monitor.setDescription("Preparing to install archives");
-
- int numInstalled = 0;
- for (Archive archive : archives) {
-
- int nextProgress = monitor.getProgress() + progressPerArchive;
- try {
- if (monitor.isCancelRequested()) {
- break;
- }
-
- if (archive.install(mUpdaterData.getOsSdkRoot(), monitor)) {
- numInstalled++;
- }
-
- } catch (Throwable t) {
- // Display anything unexpected in the monitor.
- monitor.setResult("Unexpected Error: %1$s", t.getMessage());
-
- } finally {
-
- // Always move the progress bar to the desired position.
- // This allows internal methods to not have to care in case
- // they abort early
- monitor.incProgress(nextProgress - monitor.getProgress());
- }
- }
-
- if (numInstalled == 0) {
- monitor.setDescription("Done. Nothing was installed.");
- } else {
- monitor.setDescription("Done. %1$d %2$s installed.",
- numInstalled,
- numInstalled == 1 ? "package" : "packages");
- }
+ c.setSettingsPage(settingsPage);
+ break;
}
- });
+ }
}
// End of hiding from SWT Designer
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/ImageFactory.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/ImageFactory.java
new file mode 100755
index 0000000..a601b1a
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/ImageFactory.java
@@ -0,0 +1,85 @@
+/*
+ * 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.internal.repository.icons;
+
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.widgets.Display;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+
+
+/**
+ * An utility class to serve {@link Image} correspond to the various icons
+ * present in this package and dispose of them correctly at the end.
+ */
+public class ImageFactory {
+
+ private final Display mDisplay;
+ private final HashMap<String, Image> mImages = new HashMap<String, Image>();
+
+ public ImageFactory(Display display) {
+ mDisplay = display;
+ }
+
+ /**
+ * Loads an image given its filename (with its extension).
+ * Might return null if the image cannot be loaded.
+ */
+ public Image getImage(String imageName) {
+
+ Image image = mImages.get(imageName);
+ if (image != null) {
+ return image;
+ }
+
+ InputStream stream = getClass().getResourceAsStream(imageName);
+ if (stream != null) {
+ try {
+ ImageData imgData = new ImageData(stream);
+ image = new Image(mDisplay, imgData, imgData.getTransparencyMask());
+ } catch (SWTException e) {
+ // ignore
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ }
+
+ // Store the image in the hash, even if this failed. If it fails now, it will fail later.
+ mImages.put(imageName, image);
+
+ return image;
+ }
+
+ /**
+ * Dispose all the images created by this factory so far.
+ */
+ public void dispose() {
+ Iterator<Image> it = mImages.values().iterator();
+ while(it.hasNext()) {
+ Image img = it.next();
+ if (img != null) {
+ img.dispose();
+ }
+ it.remove();
+ }
+ }
+
+}
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/android_icon_128.png b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/android_icon_128.png
similarity index 100%
rename from tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/android_icon_128.png
rename to tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/android_icon_128.png
Binary files differ
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/android_icon_16.png b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/android_icon_16.png
similarity index 100%
rename from tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/android_icon_16.png
rename to tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/android_icon_16.png
Binary files differ
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/archive_icon16.png b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/archive_icon16.png
new file mode 100755
index 0000000..be5edd7
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/archive_icon16.png
Binary files differ
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/blue_ball_icon16.png b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/blue_ball_icon16.png
new file mode 100755
index 0000000..2936a0a
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/blue_ball_icon16.png
Binary files differ
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/gray_ball_icon16.png b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/gray_ball_icon16.png
new file mode 100755
index 0000000..23ef0d2
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/gray_ball_icon16.png
Binary files differ
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/green_ball_icon16.png b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/green_ball_icon16.png
new file mode 100755
index 0000000..12a1157
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/green_ball_icon16.png
Binary files differ
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/incompat_icon16.png b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/incompat_icon16.png
new file mode 100755
index 0000000..2a307e9
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/incompat_icon16.png
Binary files differ
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/purple_ball_icon16.png b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/purple_ball_icon16.png
new file mode 100755
index 0000000..e317f59
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/purple_ball_icon16.png
Binary files differ
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/red_ball_icon16.png b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/red_ball_icon16.png
new file mode 100755
index 0000000..a4c18d8
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/red_ball_icon16.png
Binary files differ
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/source_icon16.png b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/source_icon16.png
new file mode 100755
index 0000000..5eb1ead
--- /dev/null
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/source_icon16.png
Binary files differ
diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java
index 0d77ccb..2056008 100644
--- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java
+++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java
@@ -124,7 +124,9 @@
// Layout has 2 columns
Composite group = new Composite(parent, SWT.NONE);
- group.setLayout(new GridLayout(NUM_COL, false /*makeColumnsEqualWidth*/));
+ GridLayout gl;
+ group.setLayout(gl = new GridLayout(NUM_COL, false /*makeColumnsEqualWidth*/));
+ gl.marginHeight = gl.marginWidth = 0;
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setFont(parent.getFont());