New Running Services UI.
This introduces a simplified (thanks, dsandler!) UI for Running Services, collapsing the groups of apps and processes into single lines. Tapping on a line moves to a new activity showing details on that group, where the stop functionality is now available. This UI is now also integrated into Manage Applications, as the Running tab. You no longer get a really confusing, misleading, scary list of every package that appears to be laying around for some reason. The code was also re-organized, to put everything related to Manage Applications and Running Services under its own package. There is still some clean-up -- some performance improvements (such as not re-computing the world when we switch to the details view), and if this looks good then eradicating the old running services UI. Change-Id: I3fc059c18060600742cab5b455d11ff74bf45ae3
This commit is contained in:
390
src/com/android/settings/applications/RunningServiceDetails.java
Normal file
390
src/com/android/settings/applications/RunningServiceDetails.java
Normal file
@@ -0,0 +1,390 @@
|
||||
package com.android.settings.applications;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RunningServiceDetails extends Activity {
|
||||
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;
|
||||
|
||||
RunningState mState;
|
||||
|
||||
int mUid;
|
||||
String mProcessName;
|
||||
|
||||
RunningState.MergedItem mMergedItem;
|
||||
|
||||
ViewGroup mAllDetails;
|
||||
ViewGroup mSnippet;
|
||||
RunningProcessesView.ActiveItem mSnippetActiveItem;
|
||||
RunningProcessesView.ViewHolder mSnippetViewHolder;
|
||||
|
||||
TextView mServicesHeader;
|
||||
TextView mProcessesHeader;
|
||||
final ArrayList<ActiveDetail> mActiveDetails = new ArrayList<ActiveDetail>();
|
||||
|
||||
class ActiveDetail implements View.OnClickListener {
|
||||
View mRootView;
|
||||
RunningProcessesView.ActiveItem mActiveItem;
|
||||
RunningProcessesView.ViewHolder mViewHolder;
|
||||
PendingIntent mManageIntent;
|
||||
|
||||
public void onClick(View v) {
|
||||
if (mManageIntent != null) {
|
||||
try {
|
||||
startIntentSender(mManageIntent.getIntentSender(), null,
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET,
|
||||
Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET, 0);
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
Log.w(TAG, e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, e);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
} else {
|
||||
RunningState.ServiceItem si = (RunningState.ServiceItem)mActiveItem.mItem;
|
||||
stopService(new Intent().setComponent(si.mRunningService.service));
|
||||
if (mMergedItem == null || mMergedItem.mServices.size() <= 1) {
|
||||
// If there was only one service, we are finishing it,
|
||||
// so no reason for the UI to stick around.
|
||||
finish();
|
||||
} else {
|
||||
if (mBackgroundHandler != null) {
|
||||
mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<mActiveDetails.size(); i++) {
|
||||
mActiveDetails.get(i).mActiveItem.updateTime(
|
||||
RunningServiceDetails.this, mBuilder);
|
||||
}
|
||||
removeMessages(MSG_UPDATE_TIMES);
|
||||
msg = obtainMessage(MSG_UPDATE_TIMES);
|
||||
sendMessageDelayed(msg, RunningProcessesView.TIME_UPDATE_DELAY);
|
||||
break;
|
||||
case MSG_REFRESH_UI:
|
||||
refreshUi(msg.arg1 != 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
boolean findMergedItem() {
|
||||
RunningState.MergedItem item = null;
|
||||
ArrayList<RunningState.MergedItem> newItems = mState.getCurrentMergedItems();
|
||||
if (newItems != null) {
|
||||
for (int i=0; i<newItems.size(); i++) {
|
||||
RunningState.MergedItem mi = newItems.get(i);
|
||||
if (mi.mProcess.mUid == mUid
|
||||
&& mi.mProcess.mProcessName.equals(mProcessName)) {
|
||||
item = mi;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mMergedItem != item) {
|
||||
mMergedItem = item;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void addDetailViews() {
|
||||
for (int i=mActiveDetails.size()-1; i>=0; i--) {
|
||||
mAllDetails.removeView(mActiveDetails.get(i).mRootView);
|
||||
}
|
||||
mActiveDetails.clear();
|
||||
|
||||
if (mServicesHeader != null) {
|
||||
mAllDetails.removeView(mServicesHeader);
|
||||
mServicesHeader = null;
|
||||
}
|
||||
|
||||
if (mProcessesHeader != null) {
|
||||
mAllDetails.removeView(mProcessesHeader);
|
||||
mProcessesHeader = null;
|
||||
}
|
||||
|
||||
if (mMergedItem != null) {
|
||||
for (int i=0; i<mMergedItem.mServices.size(); i++) {
|
||||
if (i == 0) {
|
||||
mServicesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
|
||||
mAllDetails, false);
|
||||
mServicesHeader.setText(R.string.runningservicedetails_services_title);
|
||||
mAllDetails.addView(mServicesHeader);
|
||||
}
|
||||
RunningState.ServiceItem si = mMergedItem.mServices.get(i);
|
||||
ActiveDetail detail = new ActiveDetail();
|
||||
View root = mInflater.inflate(R.layout.running_service_details_service,
|
||||
mAllDetails, false);
|
||||
mAllDetails.addView(root);
|
||||
detail.mRootView = root;
|
||||
detail.mViewHolder = new RunningProcessesView.ViewHolder(root);
|
||||
detail.mActiveItem = detail.mViewHolder.bind(mState, si, mBuilder);
|
||||
|
||||
if (si.mRunningService.clientLabel != 0) {
|
||||
detail.mManageIntent = mAm.getRunningServiceControlPanel(
|
||||
si.mRunningService.service);
|
||||
}
|
||||
|
||||
TextView description = (TextView)root.findViewById(R.id.comp_description);
|
||||
if (si.mServiceInfo.descriptionRes != 0) {
|
||||
description.setText(getPackageManager().getText(
|
||||
si.mServiceInfo.packageName, si.mServiceInfo.descriptionRes,
|
||||
si.mServiceInfo.applicationInfo));
|
||||
} else {
|
||||
if (detail.mManageIntent != null) {
|
||||
try {
|
||||
Resources clientr = getPackageManager().getResourcesForApplication(
|
||||
si.mRunningService.clientPackage);
|
||||
String label = clientr.getString(si.mRunningService.clientLabel);
|
||||
description.setText(getString(R.string.service_manage_description,
|
||||
label));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
}
|
||||
} else {
|
||||
description.setText(getText(R.string.service_stop_description));
|
||||
}
|
||||
}
|
||||
|
||||
View button = root.findViewById(R.id.right_button);
|
||||
button.setOnClickListener(detail);
|
||||
((TextView)button).setText(getText(detail.mManageIntent != null
|
||||
? R.string.service_manage : R.string.service_stop));
|
||||
root.findViewById(R.id.left_button).setVisibility(View.INVISIBLE);
|
||||
|
||||
mActiveDetails.add(detail);
|
||||
}
|
||||
|
||||
boolean didProcess = false;
|
||||
for (int i=-1; i<mMergedItem.mOtherProcesses.size(); i++) {
|
||||
RunningState.ProcessItem pi = i < 0 ? mMergedItem.mProcess
|
||||
: mMergedItem.mOtherProcesses.get(i);
|
||||
if (pi.mPid <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!didProcess) {
|
||||
mProcessesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
|
||||
mAllDetails, false);
|
||||
mProcessesHeader.setText(R.string.runningservicedetails_processes_title);
|
||||
mAllDetails.addView(mProcessesHeader);
|
||||
didProcess = true;
|
||||
}
|
||||
ActiveDetail detail = new ActiveDetail();
|
||||
View root = mInflater.inflate(R.layout.running_service_details_process,
|
||||
mAllDetails, false);
|
||||
mAllDetails.addView(root);
|
||||
detail.mRootView = root;
|
||||
detail.mViewHolder = new RunningProcessesView.ViewHolder(root);
|
||||
detail.mActiveItem = detail.mViewHolder.bind(mState, pi, mBuilder);
|
||||
|
||||
TextView description = (TextView)root.findViewById(R.id.comp_description);
|
||||
if (i < 0) {
|
||||
description.setText(R.string.main_running_process_description);
|
||||
} else {
|
||||
int textid = 0;
|
||||
CharSequence label = null;
|
||||
ActivityManager.RunningAppProcessInfo rpi = pi.mRunningProcessInfo;
|
||||
final ComponentName comp = rpi.importanceReasonComponent;
|
||||
//Log.i(TAG, "Secondary proc: code=" + rpi.importanceReasonCode
|
||||
// + " pid=" + rpi.importanceReasonPid + " comp=" + comp);
|
||||
switch (rpi.importanceReasonCode) {
|
||||
case ActivityManager.RunningAppProcessInfo.REASON_PROVIDER_IN_USE:
|
||||
textid = R.string.process_provider_in_use_description;
|
||||
List<ProviderInfo> providers = null;
|
||||
if (comp != null) {
|
||||
providers = getPackageManager()
|
||||
.queryContentProviders(comp.getPackageName(),
|
||||
rpi.uid, 0);
|
||||
}
|
||||
if (providers != null) {
|
||||
for (int j=0; j<providers.size(); j++) {
|
||||
ProviderInfo prov = providers.get(i);
|
||||
if (comp.getClassName().equals(prov.name)) {
|
||||
label = RunningState.makeLabel(getPackageManager(),
|
||||
prov.name, prov);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ActivityManager.RunningAppProcessInfo.REASON_SERVICE_IN_USE:
|
||||
textid = R.string.process_service_in_use_description;
|
||||
if (rpi.importanceReasonComponent != null) {
|
||||
try {
|
||||
ServiceInfo serv = getPackageManager().getServiceInfo(
|
||||
rpi.importanceReasonComponent, 0);
|
||||
label = RunningState.makeLabel(getPackageManager(),
|
||||
serv.name, serv);
|
||||
} catch (NameNotFoundException e) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (textid != 0 && label != null) {
|
||||
description.setText(getString(textid, label));
|
||||
}
|
||||
}
|
||||
|
||||
mActiveDetails.add(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void refreshUi(boolean dataChanged) {
|
||||
if (findMergedItem()) {
|
||||
dataChanged = true;
|
||||
}
|
||||
if (dataChanged) {
|
||||
if (mMergedItem != null) {
|
||||
mSnippetActiveItem = mSnippetViewHolder.bind(mState,
|
||||
mMergedItem, mBuilder);
|
||||
} else if (mSnippetActiveItem != null) {
|
||||
// Clear whatever is currently being shown.
|
||||
mSnippetActiveItem.mHolder.size.setText("");
|
||||
mSnippetActiveItem.mHolder.uptime.setText("");
|
||||
mSnippetActiveItem.mHolder.description.setText(R.string.no_services);
|
||||
} else {
|
||||
// No merged item, never had one. Nothing to do.
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
addDetailViews();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mUid = getIntent().getIntExtra(KEY_UID, 0);
|
||||
mProcessName = getIntent().getStringExtra(KEY_PROCESS);
|
||||
|
||||
mAm = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
|
||||
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
mState = (RunningState)getLastNonConfigurationInstance();
|
||||
if (mState == null) {
|
||||
mState = new RunningState();
|
||||
}
|
||||
|
||||
setContentView(R.layout.running_service_details);
|
||||
|
||||
mAllDetails = (ViewGroup)findViewById(R.id.all_details);
|
||||
mSnippet = (ViewGroup)findViewById(R.id.snippet);
|
||||
mSnippet.setBackgroundResource(com.android.internal.R.drawable.title_bar_medium);
|
||||
mSnippet.setPadding(0, mSnippet.getPaddingTop(), 0, mSnippet.getPaddingBottom());
|
||||
mSnippetViewHolder = new RunningProcessesView.ViewHolder(mSnippet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
mHandler.removeMessages(MSG_UPDATE_TIMES);
|
||||
if (mBackgroundThread != null) {
|
||||
mBackgroundThread.quit();
|
||||
mBackgroundThread = null;
|
||||
mBackgroundHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user