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