Add debug app / wait for debugger dev options.

Re-organize dev options a bit.

Change-Id: I291b177c87cb8fb4bd8316d05aa6eadfaaf5f0d2
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5185f9a..e8ddf2d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1019,6 +1019,9 @@
                 android:resource="@id/development_settings" />
         </activity-alias>
 
+        <activity android:name=".AppPicker" android:label="@string/select_application"
+                android:theme="@android:style/Theme.Holo.Dialog" />
+
         <activity android:name="Settings$UsbSettingsActivity"
                 android:label="@string/storage_title_usb"
                 android:clearTaskOnLaunch="true">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 258df34..a936af5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3480,6 +3480,27 @@
     <!-- HDCP checking dialog title, used for debug purposes only. [CHAR LIMIT=25] -->
     <string name="hdcp_checking_dialog_title">Set HDCP checking behavior</string>
 
+    <!-- Preference category for app debugging development settings. [CHAR LIMIT=50] -->
+    <string name="debug_debugging_category">Debugging</string>
+
+    <!-- UI debug setting: select current app to debug [CHAR LIMIT=50] -->
+    <string name="debug_app">Select debug app</string>
+    <!-- UI debug setting: no debug app has been set [CHAR LIMIT=50] -->
+    <string name="debug_app_not_set">No debug application set</string>
+    <!-- UI debug setting: debug app has been set [CHAR LIMIT=50] -->
+    <string name="debug_app_set">Debugging application: <xliff:g id="app_name">%1$s</xliff:g></string>
+
+    <!-- UI debug setting: title for app picker dialog [CHAR LIMIT=50] -->
+    <string name="select_application">Select application</string>
+    <!-- UI debug setting: label for app picker to select no applicatiojn [CHAR LIMIT=50] -->
+    <string name="no_application">Nothing</string>
+
+    <!-- UI debug setting: wait for debugger to attach to debugging process? [CHAR LIMIT=50] -->
+    <string name="wait_for_debugger">Wait for debugger</string>
+    <!-- UI debug setting: wait for debugger to attach to debugging process summary [CHAR LIMIT=50] -->
+    <string name="wait_for_debugger_summary">Debugged application waits for debugger to
+            attach before executing</string>
+
     <!-- Preference category for user interface debugging development settings. [CHAR LIMIT=25] -->
     <string name="debug_ui_category">User interface</string>
 
diff --git a/res/xml/development_prefs.xml b/res/xml/development_prefs.xml
index d0c01a7..fc41f5e 100644
--- a/res/xml/development_prefs.xml
+++ b/res/xml/development_prefs.xml
@@ -16,11 +16,6 @@
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
         android:title="@string/development_settings_title">
-        
-    <CheckBoxPreference 
-        android:key="enable_adb" 
-        android:title="@string/enable_adb" 
-        android:summary="@string/enable_adb_summary"/>
 
     <Preference
         android:key="verifier_device_identifier"
@@ -29,23 +24,6 @@
         android:summary="@string/verifier_device_identifier_not_available"
         android:persistent="false" />
 
-    <CheckBoxPreference 
-        android:key="keep_screen_on" 
-        android:title="@string/keep_screen_on" 
-        android:summary="@string/keep_screen_on_summary"/>
-
-    <CheckBoxPreference 
-        android:key="allow_mock_location" 
-        android:title="@string/allow_mock_location" 
-        android:summary="@string/allow_mock_location_summary"/>
-
-    <ListPreference
-        android:key="hdcp_checking"
-        android:title="@string/hdcp_checking_title"
-        android:dialogTitle="@string/hdcp_checking_dialog_title"
-        android:entries="@array/hdcp_checking_titles"
-        android:entryValues="@array/hdcp_checking_values" />
-
     <PreferenceScreen
             android:key="local_backup_password"
             android:title="@string/local_backup_password_title"
@@ -57,6 +35,41 @@
                 android:targetClass="com.android.settings.SetFullBackupPassword" />
     </PreferenceScreen>
 
+    <CheckBoxPreference 
+        android:key="keep_screen_on" 
+        android:title="@string/keep_screen_on" 
+        android:summary="@string/keep_screen_on_summary"/>
+
+    <ListPreference
+        android:key="hdcp_checking"
+        android:title="@string/hdcp_checking_title"
+        android:dialogTitle="@string/hdcp_checking_dialog_title"
+        android:entries="@array/hdcp_checking_titles"
+        android:entryValues="@array/hdcp_checking_values" />
+
+    <PreferenceCategory android:key="debug_debugging_category"
+            android:title="@string/debug_debugging_category">
+
+        <CheckBoxPreference
+            android:key="enable_adb"
+            android:title="@string/enable_adb"
+            android:summary="@string/enable_adb_summary"/>
+
+        <CheckBoxPreference
+            android:key="allow_mock_location"
+            android:title="@string/allow_mock_location"
+            android:summary="@string/allow_mock_location_summary"/>
+
+        <PreferenceScreen android:key="debug_app"
+                android:title="@string/debug_app" />
+
+        <CheckBoxPreference
+            android:key="wait_for_debugger"
+            android:title="@string/wait_for_debugger"
+            android:summary="@string/wait_for_debugger_summary"/>
+
+    </PreferenceCategory>
+
     <PreferenceCategory android:key="debug_ui_category"
             android:title="@string/debug_ui_category">
 
diff --git a/src/com/android/settings/AppPicker.java b/src/com/android/settings/AppPicker.java
new file mode 100644
index 0000000..e58b835
--- /dev/null
+++ b/src/com/android/settings/AppPicker.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import com.android.settings.applications.AppViewHolder;
+
+import android.app.ActivityManagerNative;
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+public class AppPicker extends ListActivity {
+    private AppListAdapter mAdapter;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mAdapter = new AppListAdapter(this);
+        if (mAdapter.getCount() <= 0) {
+            finish();
+        } else {
+            setListAdapter(mAdapter);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        MyApplicationInfo app = mAdapter.getItem(position);
+        Intent intent = new Intent();
+        if (app.info != null) intent.setAction(app.info.packageName);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
+    class MyApplicationInfo {
+        ApplicationInfo info;
+        CharSequence label;
+    }
+
+    public class AppListAdapter extends ArrayAdapter<MyApplicationInfo> {
+        private final List<MyApplicationInfo> mPackageInfoList = new ArrayList<MyApplicationInfo>();
+        private final LayoutInflater mInflater;
+
+        public AppListAdapter(Context context) {
+            super(context, 0);
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            List<ApplicationInfo> pkgs = context.getPackageManager().getInstalledApplications(0);
+            for (int i=0; i<pkgs.size(); i++) {
+                ApplicationInfo ai = pkgs.get(i);
+                if (ai.uid == Process.SYSTEM_UID) {
+                    continue;
+                }
+                // On a user build, we only allow debugging of apps that
+                // are marked as debuggable.  Otherwise (for platform development)
+                // we allow all apps.
+                if ((ai.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0
+                        && "user".equals(Build.TYPE)) {
+                    continue;
+                }
+                MyApplicationInfo info = new MyApplicationInfo();
+                info.info = ai;
+                info.label = info.info.loadLabel(getPackageManager()).toString();
+                mPackageInfoList.add(info);
+            }
+            Collections.sort(mPackageInfoList, sDisplayNameComparator);
+            MyApplicationInfo info = new MyApplicationInfo();
+            info.label = context.getText(R.string.no_application);
+            mPackageInfoList.add(0, info);
+            addAll(mPackageInfoList);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            // A ViewHolder keeps references to children views to avoid unnecessary calls
+            // to findViewById() on each row.
+            AppViewHolder holder = AppViewHolder.createOrRecycle(mInflater, convertView);
+            convertView = holder.rootView;
+            MyApplicationInfo info = getItem(position);
+            holder.appName.setText(info.label);
+            if (info.info != null) {
+                holder.appIcon.setImageDrawable(info.info.loadIcon(getPackageManager()));
+                holder.appSize.setText(info.info.packageName);
+            } else {
+                holder.appIcon.setImageDrawable(null);
+                holder.appSize.setText("");
+            }
+            holder.disabled.setVisibility(View.GONE);
+            holder.checkBox.setVisibility(View.GONE);
+            return convertView;
+        }
+    }
+
+    private final static Comparator<MyApplicationInfo> sDisplayNameComparator
+            = new Comparator<MyApplicationInfo>() {
+        public final int
+        compare(MyApplicationInfo a, MyApplicationInfo b) {
+            return collator.compare(a.label, b.label);
+        }
+
+        private final Collator collator = Collator.getInstance();
+    };
+}
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index 7b4e0eb..d235633 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.VerifierDeviceIdentity;
 import android.os.BatteryManager;
@@ -69,6 +70,8 @@
     private static final String LOCAL_BACKUP_PASSWORD = "local_backup_password";
     private static final String HARDWARE_UI_PROPERTY = "persist.sys.ui.hw";
 
+    private static final String DEBUG_APP_KEY = "debug_app";
+    private static final String WAIT_FOR_DEBUGGER_KEY = "wait_for_debugger";
     private static final String STRICT_MODE_KEY = "strict_mode";
     private static final String POINTER_LOCATION_KEY = "pointer_location";
     private static final String SHOW_TOUCHES_KEY = "show_touches";
@@ -86,6 +89,8 @@
 
     private static final String SHOW_ALL_ANRS_KEY = "show_all_anrs";
 
+    private static final int RESULT_DEBUG_APP = 1000;
+
     private IWindowManager mWindowManager;
     private IBackupManager mBackupManager;
 
@@ -97,6 +102,10 @@
     private CheckBoxPreference mAllowMockLocation;
     private PreferenceScreen mPassword;
 
+    private String mDebugApp;
+    private Preference mDebugAppPref;
+    private CheckBoxPreference mWaitForDebugger;
+
     private CheckBoxPreference mStrictMode;
     private CheckBoxPreference mPointerLocation;
     private CheckBoxPreference mShowTouches;
@@ -144,10 +153,15 @@
         mPassword = (PreferenceScreen) findPreference(LOCAL_BACKUP_PASSWORD);
         mAllPrefs.add(mPassword);
 
+        mDebugAppPref = findPreference(DEBUG_APP_KEY);
+        mAllPrefs.add(mDebugAppPref);
+        mWaitForDebugger = (CheckBoxPreference) findPreference(WAIT_FOR_DEBUGGER_KEY);
+        mAllPrefs.add(mWaitForDebugger);
+        mResetCbPrefs.add(mWaitForDebugger);
+
         mStrictMode = (CheckBoxPreference) findPreference(STRICT_MODE_KEY);
         mAllPrefs.add(mStrictMode);
         mResetCbPrefs.add(mStrictMode);
-        mResetCbPrefs.add(mAllowMockLocation);
         mPointerLocation = (CheckBoxPreference) findPreference(POINTER_LOCATION_KEY);
         mAllPrefs.add(mPointerLocation);
         mResetCbPrefs.add(mPointerLocation);
@@ -251,6 +265,7 @@
         for (int i=0; i<mAllPrefs.size(); i++) {
             mAllPrefs.get(i).setEnabled(enabled);
         }
+        updateAllOptions();
     }
 
     @Override
@@ -262,7 +277,6 @@
                 Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
         mEnabledSwitch.setChecked(mLastEnabledState);
         setPrefsEnabledState(mLastEnabledState);
-        updateAllOptions();
     }
 
     private void updateAllOptions() {
@@ -275,6 +289,7 @@
                 Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0);
         updateHdcpValues();
         updatePasswordSummary();
+        updateDebuggerOptions();
         updateStrictModeVisualOptions();
         updatePointerLocationOptions();
         updateShowTouchesOptions();
@@ -295,6 +310,7 @@
                 onPreferenceTreeClick(null, cb);
             }
         }
+        resetDebuggerOptions();
         writeAnimationScaleOption(0, mWindowAnimationScale, null);
         writeAnimationScaleOption(1, mTransitionAnimationScale, null);
         writeAnimationScaleOption(2, mAnimatorDurationScale, null);
@@ -333,6 +349,45 @@
         }
     }
 
+    private void writeDebuggerOptions() {
+        try {
+            ActivityManagerNative.getDefault().setDebugApp(
+                mDebugApp, mWaitForDebugger.isChecked(), true);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    private void resetDebuggerOptions() {
+        try {
+            ActivityManagerNative.getDefault().setDebugApp(
+                    null, false, true);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    private void updateDebuggerOptions() {
+        mDebugApp = Settings.System.getString(
+                getActivity().getContentResolver(), Settings.System.DEBUG_APP);
+        mWaitForDebugger.setChecked(Settings.System.getInt(
+                getActivity().getContentResolver(), Settings.System.WAIT_FOR_DEBUGGER, 0) != 0);
+        if (mDebugApp != null && mDebugApp.length() > 0) {
+            String label;
+            try {
+                ApplicationInfo ai = getActivity().getPackageManager().getApplicationInfo(mDebugApp,
+                        PackageManager.GET_DISABLED_COMPONENTS);
+                CharSequence lab = getActivity().getPackageManager().getApplicationLabel(ai);
+                label = lab != null ? lab.toString() : mDebugApp;
+            } catch (PackageManager.NameNotFoundException e) {
+                label = mDebugApp;
+            }
+            mDebugAppPref.setSummary(getResources().getString(R.string.debug_app_set, label));
+            mWaitForDebugger.setEnabled(true);
+        } else {
+            mDebugAppPref.setSummary(getResources().getString(R.string.debug_app_not_set));
+            mWaitForDebugger.setEnabled(false);
+        }
+    }
+
     // Returns the current state of the system property that controls
     // strictmode flashes.  One of:
     //    0: not explicitly set one way or another
@@ -576,6 +631,19 @@
     }
 
     @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == RESULT_DEBUG_APP) {
+            if (resultCode == Activity.RESULT_OK) {
+                mDebugApp = data.getAction();
+                writeDebuggerOptions();
+                updateDebuggerOptions();
+            }
+        } else {
+            super.onActivityResult(requestCode, resultCode, data);
+        }
+    }
+
+    @Override
     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
 
         if (Utils.isMonkeyRunning()) {
@@ -607,6 +675,10 @@
             Settings.Secure.putInt(getActivity().getContentResolver(),
                     Settings.Secure.ALLOW_MOCK_LOCATION,
                     mAllowMockLocation.isChecked() ? 1 : 0);
+        } else if (preference == mDebugAppPref) {
+            startActivityForResult(new Intent(getActivity(), AppPicker.class), RESULT_DEBUG_APP);
+        } else if (preference == mWaitForDebugger) {
+            writeDebuggerOptions();
         } else if (preference == mStrictMode) {
             writeStrictModeVisualOptions();
         } else if (preference == mPointerLocation) {
diff --git a/src/com/android/settings/applications/AppViewHolder.java b/src/com/android/settings/applications/AppViewHolder.java
new file mode 100644
index 0000000..2a12f9b
--- /dev/null
+++ b/src/com/android/settings/applications/AppViewHolder.java
@@ -0,0 +1,64 @@
+package com.android.settings.applications;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+// View Holder used when displaying views
+public class AppViewHolder {
+    public ApplicationsState.AppEntry entry;
+    public View rootView;
+    public TextView appName;
+    public ImageView appIcon;
+    public TextView appSize;
+    public TextView disabled;
+    public CheckBox checkBox;
+
+    static public AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView) {
+        if (convertView == null) {
+            convertView = inflater.inflate(R.layout.manage_applications_item, null);
+
+            // Creates a ViewHolder and store references to the two children views
+            // we want to bind data to.
+            AppViewHolder holder = new AppViewHolder();
+            holder.rootView = convertView;
+            holder.appName = (TextView) convertView.findViewById(R.id.app_name);
+            holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
+            holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
+            holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled);
+            holder.checkBox = (CheckBox) convertView.findViewById(R.id.app_on_sdcard);
+            convertView.setTag(holder);
+            return holder;
+        } else {
+            // Get the ViewHolder back to get fast access to the TextView
+            // and the ImageView.
+            return (AppViewHolder)convertView.getTag();
+        }
+    }
+
+    void updateSizeText(ManageApplications ma, int whichSize) {
+        if (ManageApplications.DEBUG) Log.i(ManageApplications.TAG, "updateSizeText of " + entry.label + " " + entry
+                + ": " + entry.sizeStr);
+        if (entry.sizeStr != null) {
+            switch (whichSize) {
+                case ManageApplications.SIZE_INTERNAL:
+                    appSize.setText(entry.internalSizeStr);
+                    break;
+                case ManageApplications.SIZE_EXTERNAL:
+                    appSize.setText(entry.externalSizeStr);
+                    break;
+                default:
+                    appSize.setText(entry.sizeStr);
+                    break;
+            }
+        } else if (entry.size == ApplicationsState.SIZE_INVALID) {
+            appSize.setText(ma.mInvalidSizeStr);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 948ddb0..b522077 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -148,7 +148,7 @@
     private ApplicationsAdapter mApplicationsAdapter;
     
     // Size resource used for packages whose size computation failed for some reason
-    private CharSequence mInvalidSizeStr;
+    CharSequence mInvalidSizeStr;
     private CharSequence mComputingSizeStr;
     
     // layout inflater object used to inflate views
@@ -205,36 +205,6 @@
         }
     };
 
-    // View Holder used when displaying views
-    static class AppViewHolder {
-        ApplicationsState.AppEntry entry;
-        TextView appName;
-        ImageView appIcon;
-        TextView appSize;
-        TextView disabled;
-        CheckBox checkBox;
-        
-        void updateSizeText(ManageApplications ma, int whichSize) {
-            if (DEBUG) Log.i(TAG, "updateSizeText of " + entry.label + " " + entry
-                    + ": " + entry.sizeStr);
-            if (entry.sizeStr != null) {
-                switch (whichSize) {
-                    case SIZE_INTERNAL:
-                        appSize.setText(entry.internalSizeStr);
-                        break;
-                    case SIZE_EXTERNAL:
-                        appSize.setText(entry.externalSizeStr);
-                        break;
-                    default:
-                        appSize.setText(entry.sizeStr);
-                        break;
-                }
-            } else if (entry.size == ApplicationsState.SIZE_INVALID) {
-                appSize.setText(ma.mInvalidSizeStr);
-            }
-        }
-    }
-    
     /*
      * Custom adapter implementation for the ListView
      * This adapter maintains a map for each displayed application and its properties
@@ -477,28 +447,8 @@
         public View getView(int position, View convertView, ViewGroup parent) {
             // A ViewHolder keeps references to children views to avoid unnecessary 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.manage_applications_item, null);
-
-                // Creates a ViewHolder and store references to the two children views
-                // we want to bind data to.
-                holder = new AppViewHolder();
-                holder.appName = (TextView) convertView.findViewById(R.id.app_name);
-                holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
-                holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
-                holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled);
-                holder.checkBox = (CheckBox) convertView.findViewById(R.id.app_on_sdcard);
-                convertView.setTag(holder);
-            } else {
-                // Get the ViewHolder back to get fast access to the TextView
-                // and the ImageView.
-                holder = (AppViewHolder) convertView.getTag();
-            }
+            AppViewHolder holder = AppViewHolder.createOrRecycle(mInflater, convertView);
+            convertView = holder.rootView;
 
             // Bind the data efficiently with the holder
             ApplicationsState.AppEntry entry = mEntries.get(position);