From 508fedc4102790dcd7004e4962e2b540967b714b Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Wed, 28 Jul 2010 17:10:42 -0700 Subject: [PATCH] Improvements to manage apps / running services: - Running services now keeps a single data structure to make switching through the UI a lot faster. - Display text when there are no apps. - Fix deadlock. - Add new preference entry to view manage apps for storage use. - Etc. Change-Id: I0f5babf407ed7e84169f59584ddcb6cd0e9d67d9 --- AndroidManifest.xml | 14 +- ...pute_sizes.xml => manage_applications.xml} | 24 ++- res/values/strings.xml | 10 +- res/xml/application_settings.xml | 8 + .../applications/ApplicationsState.java | 2 +- .../applications/ManageApplications.java | 20 ++- .../applications/RunningProcessesView.java | 134 +++++--------- .../applications/RunningServiceDetails.java | 119 +++++-------- .../settings/applications/RunningState.java | 167 +++++++++++++++++- 9 files changed, 310 insertions(+), 188 deletions(-) rename res/layout/{compute_sizes.xml => manage_applications.xml} (59%) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 43374ebc03f..7b15e3f023f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -380,10 +380,10 @@ - + @@ -391,11 +391,11 @@ - - --> + - diff --git a/res/layout/compute_sizes.xml b/res/layout/manage_applications.xml similarity index 59% rename from res/layout/compute_sizes.xml rename to res/layout/manage_applications.xml index 502488f4ff5..c398f5e7e83 100755 --- a/res/layout/compute_sizes.xml +++ b/res/layout/manage_applications.xml @@ -4,9 +4,9 @@ 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. @@ -18,12 +18,20 @@ android:layout_width="match_parent" android:layout_height="match_parent" > - + + + + On SD card Disabled - Loading\u2026 + + No applications. Recomputing size\u2026 @@ -1750,12 +1751,19 @@ found in the list of installed applications. Change the preferred installation location for new applications. + + Storage use + + View storage used by applications + Running services View and control currently running services Restarting + + Nothing running. Started by application. diff --git a/res/xml/application_settings.xml b/res/xml/application_settings.xml index 6ac58d1acd6..27a04119785 100644 --- a/res/xml/application_settings.xml +++ b/res/xml/application_settings.xml @@ -59,6 +59,14 @@ android:targetClass="com.android.settings.RunningServices" /> + + + + = 0) { + //Log.i("foo", "Time for " + mItem.mDisplayLabel + // + ": " + (SystemClock.uptimeMillis()-mFirstRunTime)); uptimeView.setText(DateUtils.formatElapsedTime(builder, (SystemClock.uptimeMillis()-mFirstRunTime)/1000)); } else { @@ -220,6 +210,11 @@ public class RunningProcessesView extends FrameLayout return mItems.size(); } + @Override + public boolean isEmpty() { + return mState.hasData() && mItems.size() == 0; + } + public Object getItem(int position) { return mItems.get(position); } @@ -330,55 +325,6 @@ public class RunningProcessesView extends FrameLayout } } - HandlerThread mBackgroundThread; - final class BackgroundHandler extends Handler { - public BackgroundHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_CONTENTS: - Message cmd = mHandler.obtainMessage(MSG_REFRESH_UI); - cmd.arg1 = mState.update(getContext(), mAm) ? 1 : 0; - mHandler.sendMessage(cmd); - removeMessages(MSG_UPDATE_CONTENTS); - msg = obtainMessage(MSG_UPDATE_CONTENTS); - sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY); - break; - } - } - }; - - BackgroundHandler mBackgroundHandler; - - final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_TIMES: - Iterator it = mActiveItems.values().iterator(); - while (it.hasNext()) { - ActiveItem ai = it.next(); - if (ai.mRootView.getWindowToken() == null) { - // Clean out any dead views, just in case. - it.remove(); - continue; - } - ai.updateTime(getContext(), mBuilder); - } - removeMessages(MSG_UPDATE_TIMES); - msg = obtainMessage(MSG_UPDATE_TIMES); - sendMessageDelayed(msg, TIME_UPDATE_DELAY); - break; - case MSG_REFRESH_UI: - refreshUi(msg.arg1 != 0); - break; - } - } - }; - private boolean matchText(byte[] buffer, int index, String text) { int N = text.length(); if ((index+N) >= buffer.length) { @@ -506,10 +452,7 @@ public class RunningProcessesView extends FrameLayout public void doCreate(Bundle savedInstanceState, Object nonConfigurationInstace) { mAm = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); - mState = (RunningState)nonConfigurationInstace; - if (mState == null) { - mState = new RunningState(); - } + mState = RunningState.getInstance(getContext()); LayoutInflater inflater = (LayoutInflater)getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.running_processes_view, this); @@ -531,28 +474,49 @@ public class RunningProcessesView extends FrameLayout } public void doPause() { - mHandler.removeMessages(MSG_UPDATE_TIMES); - if (mBackgroundThread != null) { - mBackgroundThread.quit(); - mBackgroundThread = null; - mBackgroundHandler = null; - } + mState.pause(); } public void doResume() { - refreshUi(mState.update(getContext(), mAm)); - mBackgroundThread = new HandlerThread("RunningServices"); - mBackgroundThread.start(); - mBackgroundHandler = new BackgroundHandler(mBackgroundThread.getLooper()); - mHandler.removeMessages(MSG_UPDATE_TIMES); - Message msg = mHandler.obtainMessage(MSG_UPDATE_TIMES); - mHandler.sendMessageDelayed(msg, TIME_UPDATE_DELAY); - mBackgroundHandler.removeMessages(MSG_UPDATE_CONTENTS); - msg = mBackgroundHandler.obtainMessage(MSG_UPDATE_CONTENTS); - mBackgroundHandler.sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY); + mState.resume(this); + if (mState.hasData()) { + // If the state already has its data, then let's populate our + // list right now to avoid flicker. + refreshUi(true); + } } public Object doRetainNonConfigurationInstance() { - return mState; + return null; + } + + void updateTimes() { + Iterator it = mActiveItems.values().iterator(); + while (it.hasNext()) { + ActiveItem ai = it.next(); + if (ai.mRootView.getWindowToken() == null) { + // Clean out any dead views, just in case. + it.remove(); + continue; + } + ai.updateTime(getContext(), mBuilder); + } + } + + @Override + public void onRefreshUi(int what) { + switch (what) { + case REFRESH_TIME: + updateTimes(); + break; + case REFRESH_DATA: + refreshUi(false); + updateTimes(); + break; + case REFRESH_STRUCTURE: + refreshUi(true); + updateTimes(); + break; + } } } diff --git a/src/com/android/settings/applications/RunningServiceDetails.java b/src/com/android/settings/applications/RunningServiceDetails.java index a55e54f62db..d57cd4a9106 100644 --- a/src/com/android/settings/applications/RunningServiceDetails.java +++ b/src/com/android/settings/applications/RunningServiceDetails.java @@ -42,16 +42,13 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -public class RunningServiceDetails extends Activity { +public class RunningServiceDetails extends Activity + implements RunningState.OnRefreshUiListener { static final String TAG = "RunningServicesDetails"; static final String KEY_UID = "uid"; static final String KEY_PROCESS = "process"; - static final int MSG_UPDATE_TIMES = 1; - static final int MSG_UPDATE_CONTENTS = 2; - static final int MSG_REFRESH_UI = 3; - ActivityManager mAm; LayoutInflater mInflater; @@ -156,9 +153,7 @@ public class RunningServiceDetails extends Activity { // so no reason for the UI to stick around. finish(); } else { - if (mBackgroundHandler != null) { - mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS); - } + mState.updateNow(); } } else { // Heavy-weight process. We'll do a force-stop on it. @@ -170,52 +165,6 @@ public class RunningServiceDetails extends Activity { StringBuilder mBuilder = new StringBuilder(128); - HandlerThread mBackgroundThread; - final class BackgroundHandler extends Handler { - public BackgroundHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_CONTENTS: - Message cmd = mHandler.obtainMessage(MSG_REFRESH_UI); - cmd.arg1 = mState.update(RunningServiceDetails.this, mAm) ? 1 : 0; - mHandler.sendMessage(cmd); - removeMessages(MSG_UPDATE_CONTENTS); - msg = obtainMessage(MSG_UPDATE_CONTENTS); - sendMessageDelayed(msg, RunningProcessesView.CONTENTS_UPDATE_DELAY); - break; - } - } - }; - - BackgroundHandler mBackgroundHandler; - - final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_TIMES: - if (mSnippetActiveItem != null) { - mSnippetActiveItem.updateTime(RunningServiceDetails.this, mBuilder); - } - for (int i=0; i newItems = mState.getCurrentMergedItems(); @@ -447,10 +396,7 @@ public class RunningServiceDetails extends Activity { mAm = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mState = (RunningState)getLastNonConfigurationInstance(); - if (mState == null) { - mState = new RunningState(); - } + mState = RunningState.getInstance(this); setContentView(R.layout.running_service_details); @@ -464,36 +410,53 @@ public class RunningServiceDetails extends Activity { @Override protected void onPause() { super.onPause(); - mHandler.removeMessages(MSG_UPDATE_TIMES); - if (mBackgroundThread != null) { - mBackgroundThread.quit(); - mBackgroundThread = null; - mBackgroundHandler = null; - } + mState.pause(); } @Override protected void onResume() { super.onResume(); - refreshUi(mState.update(this, mAm)); - mBackgroundThread = new HandlerThread("RunningServices"); - mBackgroundThread.start(); - mBackgroundHandler = new BackgroundHandler(mBackgroundThread.getLooper()); - mHandler.removeMessages(MSG_UPDATE_TIMES); - Message msg = mHandler.obtainMessage(MSG_UPDATE_TIMES); - mHandler.sendMessageDelayed(msg, RunningProcessesView.TIME_UPDATE_DELAY); - mBackgroundHandler.removeMessages(MSG_UPDATE_CONTENTS); - msg = mBackgroundHandler.obtainMessage(MSG_UPDATE_CONTENTS); - mBackgroundHandler.sendMessageDelayed(msg, RunningProcessesView.CONTENTS_UPDATE_DELAY); - } + mState.resume(this); - @Override - public Object onRetainNonConfigurationInstance() { - return mState; + // We want to go away if the service being shown no longer exists, + // so we need to ensure we have done the initial data retrieval before + // showing our ui. + mState.waitForData(); + + // And since we know we have the data, let's show the UI right away + // to avoid flicker. + refreshUi(true); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } + + void updateTimes() { + if (mSnippetActiveItem != null) { + mSnippetActiveItem.updateTime(RunningServiceDetails.this, mBuilder); + } + for (int i=0; i mItems = new ArrayList(); ArrayList mMergedItems = new ArrayList(); @@ -95,6 +120,78 @@ public class RunningState { int mNumServiceProcesses; long mServiceProcessMemory; + // ----- BACKGROUND MONITORING THREAD ----- + + final HandlerThread mBackgroundThread; + final class BackgroundHandler extends Handler { + public BackgroundHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_CONTENTS: + synchronized (mLock) { + if (!mResumed) { + return; + } + } + Message cmd = mHandler.obtainMessage(MSG_REFRESH_UI); + cmd.arg1 = update(mApplicationContext, mAm) ? 1 : 0; + mHandler.sendMessage(cmd); + removeMessages(MSG_UPDATE_CONTENTS); + msg = obtainMessage(MSG_UPDATE_CONTENTS); + sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY); + break; + } + } + }; + + final BackgroundHandler mBackgroundHandler; + + final Handler mHandler = new Handler() { + int mNextUpdate = OnRefreshUiListener.REFRESH_TIME; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REFRESH_UI: + mNextUpdate = msg.arg1 != 0 + ? OnRefreshUiListener.REFRESH_STRUCTURE + : OnRefreshUiListener.REFRESH_DATA; + break; + case MSG_UPDATE_TIME: + synchronized (mLock) { + if (!mResumed) { + return; + } + } + removeMessages(MSG_UPDATE_TIME); + Message m = obtainMessage(MSG_UPDATE_TIME); + sendMessageDelayed(m, TIME_UPDATE_DELAY); + + if (mRefreshUiListener != null) { + //Log.i("foo", "Refresh UI: " + mNextUpdate + // + " @ " + SystemClock.uptimeMillis()); + mRefreshUiListener.onRefreshUi(mNextUpdate); + mNextUpdate = OnRefreshUiListener.REFRESH_TIME; + } + break; + } + } + }; + + // ----- DATA STRUCTURES ----- + + static interface OnRefreshUiListener { + public static final int REFRESH_TIME = 0; + public static final int REFRESH_DATA = 1; + public static final int REFRESH_STRUCTURE = 2; + + public void onRefreshUi(int what); + } + static class BaseItem { final boolean mIsProcess; @@ -428,7 +525,69 @@ public class RunningState { return label; } - boolean update(Context context, ActivityManager am) { + static RunningState getInstance(Context context) { + synchronized (sGlobalLock) { + if (sInstance == null) { + sInstance = new RunningState(context); + } + return sInstance; + } + } + + private RunningState(Context context) { + mApplicationContext = context.getApplicationContext(); + mAm = (ActivityManager)mApplicationContext.getSystemService(Context.ACTIVITY_SERVICE); + mPm = mApplicationContext.getPackageManager(); + mResumed = false; + mBackgroundThread = new HandlerThread("RunningState:Background"); + mBackgroundThread.start(); + mBackgroundHandler = new BackgroundHandler(mBackgroundThread.getLooper()); + } + + void resume(OnRefreshUiListener listener) { + synchronized (mLock) { + mResumed = true; + mRefreshUiListener = listener; + if (!mBackgroundHandler.hasMessages(MSG_UPDATE_CONTENTS)) { + mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS); + } + mHandler.sendEmptyMessage(MSG_UPDATE_TIME); + } + } + + void updateNow() { + synchronized (mLock) { + mBackgroundHandler.removeMessages(MSG_UPDATE_CONTENTS); + mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS); + } + } + + boolean hasData() { + synchronized (mLock) { + return mHaveData; + } + } + + void waitForData() { + synchronized (mLock) { + while (!mHaveData) { + try { + mLock.wait(0); + } catch (InterruptedException e) { + } + } + } + } + + void pause() { + synchronized (mLock) { + mResumed = false; + mRefreshUiListener = null; + mHandler.removeMessages(MSG_UPDATE_TIME); + } + } + + private boolean update(Context context, ActivityManager am) { final PackageManager pm = context.getPackageManager(); mSequence++; @@ -436,7 +595,7 @@ public class RunningState { boolean changed = false; List services - = am.getRunningServices(RunningProcessesView.MAX_SERVICES); + = am.getRunningServices(MAX_SERVICES); final int NS = services != null ? services.size() : 0; for (int i=0; i