Merge branch 'master' into honeycomb-release

* master:
  Fix a null pointer exception when disabledSystemIMEs is null.
  Add minTickWidth for the bar chart
  Add a scroll view to proxy settings so that it isn't obscured by the keyboard.
  fix the TooManyDeletes to show up again
  Refactor memory measurement
This commit is contained in:
The Android Automerger
2010-12-20 07:38:53 -08:00
11 changed files with 678 additions and 504 deletions

View File

@@ -1073,15 +1073,6 @@
android:label="@string/header_add_an_account" android:label="@string/header_add_an_account"
android:theme="@android:style/Theme.Holo.DialogWhenLarge"/> android:theme="@android:style/Theme.Holo.DialogWhenLarge"/>
<activity android:name="com.android.settings.accounts.SyncActivityTooManyDeletes"
android:theme="@android:style/Theme.Holo.Dialog"
android:label="@string/sync_too_many_deletes">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<receiver android:name=".widget.SettingsAppWidgetProvider" <receiver android:name=".widget.SettingsAppWidgetProvider"
android:label="@string/gadget_title" android:exported="false"> android:label="@string/gadget_title" android:exported="false">
<intent-filter> <intent-filter>

View File

@@ -16,11 +16,15 @@
<com.android.settings.deviceinfo.PercentageBarChart <com.android.settings.deviceinfo.PercentageBarChart
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight" android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical" android:gravity="center_vertical"
android:id="@+id/percentage_bar_chart" android:id="@+id/percentage_bar_chart"
android:paddingRight="?android:attr/scrollbarSize" android:paddingRight="?android:attr/scrollbarSize"
android:textAppearance="?android:attr/textAppearanceMedium"> android:paddingTop="6dip"
android:paddingBottom="6dip"
settings:minTickWidth="6dip"
settings:emptyColor="@color/memory_avail">
</com.android.settings.deviceinfo.PercentageBarChart> </com.android.settings.deviceinfo.PercentageBarChart>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
/* //device/apps/Browser/res/layout/proxy.xml /*
** **
** Copyright 2006, The Android Open Source Project ** Copyright 2006, The Android Open Source Project
** **
@@ -17,7 +17,11 @@
** limitations under the License. ** limitations under the License.
*/ */
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="6dip" android:paddingLeft="6dip"
android:paddingRight="6dip" android:paddingRight="6dip"
@@ -91,5 +95,5 @@
android:text="@string/proxy_defaultView_text" /> android:text="@string/proxy_defaultView_text" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView>

View File

@@ -42,4 +42,11 @@
<!-- Radius of the shadow. --> <!-- Radius of the shadow. -->
<attr name="android:shadowRadius" /> <attr name="android:shadowRadius" />
</declare-styleable> </declare-styleable>
<declare-styleable name="PercentageBarChart">
<!-- Background color -->
<attr name="emptyColor" format="color" />
<!-- Minimum tick width for each slice in the bar chart. -->
<attr name="minTickWidth" format="dimension" />
</declare-styleable>
</resources> </resources>

View File

@@ -3052,8 +3052,6 @@ found in the list of installed applications.</string>
<!-- Title of the feed synchronization activity. --> <!-- Title of the feed synchronization activity. -->
<string name="app_label">Account and Sync Settings</string> <string name="app_label">Account and Sync Settings</string>
<!-- Error message when the sync tried to delete too many things -->
<string name="sync_too_many_deletes">Delete limit exceeded</string>
<!-- Data synchronization settings screen, setting option name --> <!-- Data synchronization settings screen, setting option name -->
<string name="settings_backup">Back up settings</string> <string name="settings_backup">Back up settings</string>
<!-- Data synchronization settings screen, setting option summary text when check box is selected --> <!-- Data synchronization settings screen, setting option summary text when check box is selected -->
@@ -3070,14 +3068,6 @@ found in the list of installed applications.</string>
<string name="sync_calendar">Calendar</string> <string name="sync_calendar">Calendar</string>
<!-- Data synchronization settings screen, checkbox setting option name --> <!-- Data synchronization settings screen, checkbox setting option name -->
<string name="sync_contacts">Contacts</string> <string name="sync_contacts">Contacts</string>
<!-- Dialog message for when there are too many deletes that would take place and we want user confirmation -->
<string name="sync_too_many_deletes_desc">There are <xliff:g id="number_of_deleted_items">%1$d</xliff:g> deleted items for <xliff:g id="type_of_sync">%2$s</xliff:g>, account <xliff:g id="account_name">%3$s</xliff:g>. What would you like to do?</string>
<!-- Dialog action for when there are too many deletes that would take place and we want user confirmation, and the user wants to delete the items -->
<string name="sync_really_delete">Delete the items.</string>
<!-- Dialog action for when there are too many deletes that would take place and we want user confirmation, and the user wants to undo the deletions -->
<string name="sync_undo_deletes">Undo the deletes.</string>
<!-- Dialog action for when there are too many deletes that would take place and we want user confirmation, and the user wants to do nothing for now -->
<string name="sync_do_nothing">Do nothing for now.</string>
<!-- Message introducing the user to Google Sync. --> <!-- Message introducing the user to Google Sync. -->
<string name="sync_plug"><font fgcolor="#ffffffff">Welcome to Google sync!</font> <string name="sync_plug"><font fgcolor="#ffffffff">Welcome to Google sync!</font>
\nA Google approach to synchronizing data to allow access to your contacts, appointments, and more from wherever you are. \nA Google approach to synchronizing data to allow access to your contacts, appointments, and more from wherever you are.

View File

@@ -37,22 +37,19 @@
<PreferenceCategory android:title="@string/internal_memory"> <PreferenceCategory android:title="@string/internal_memory">
<com.android.settings.deviceinfo.UsageBarPreference <com.android.settings.deviceinfo.UsageBarPreference
android:key="memory_internal_chart" /> android:key="memory_internal_chart"/>
<Preference android:key="memory_internal_size" <Preference android:key="memory_internal_size"
android:title="@string/memory_size" android:title="@string/memory_size"
android:summary="00"/> android:summary="@string/memory_calculating_size"/>
<Preference android:key="memory_internal_media" <Preference android:key="memory_internal_media"
android:icon="@color/memory_media_usage"
android:title="@string/memory_media_usage" android:title="@string/memory_media_usage"
android:summary="@string/memory_calculating_size"/> android:summary="@string/memory_calculating_size"/>
<Preference android:key="memory_internal_apps" <Preference android:key="memory_internal_apps"
android:icon="@color/memory_apps_usage"
android:title="@string/memory_apps_usage" android:title="@string/memory_apps_usage"
android:summary="@string/memory_calculating_size"/> android:summary="@string/memory_calculating_size"/>
<Preference android:key="memory_internal_avail" <Preference android:key="memory_internal_avail"
android:icon="@color/memory_avail"
android:title="@string/memory_available" android:title="@string/memory_available"
android:summary="00"/> android:summary="@string/memory_calculating_size"/>
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -1,134 +0,0 @@
/*
* 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.
*/
package com.android.settings.accounts;
import com.android.settings.R;
import android.accounts.Account;
import android.app.Activity;
import android.content.ContentResolver;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
/**
* Presents multiple options for handling the case where a sync was aborted because there
* were too many pending deletes. One option is to force the delete, another is to rollback
* the deletes, the third is to do nothing.
*/
public class SyncActivityTooManyDeletes extends Activity
implements AdapterView.OnItemClickListener {
private long mNumDeletes;
private Account mAccount;
private String mAuthority;
private String mProvider;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
if (extras == null) {
finish();
return;
}
mNumDeletes = extras.getLong("numDeletes");
mAccount = (Account) extras.getParcelable("account");
mAuthority = extras.getString("authority");
mProvider = extras.getString("provider");
// the order of these must match up with the constants for position used in onItemClick
CharSequence[] options = new CharSequence[]{
getResources().getText(R.string.sync_really_delete),
getResources().getText(R.string.sync_undo_deletes),
getResources().getText(R.string.sync_do_nothing)
};
ListAdapter adapter = new ArrayAdapter<CharSequence>(this,
android.R.layout.simple_list_item_1,
android.R.id.text1,
options);
ListView listView = new ListView(this);
listView.setAdapter(adapter);
listView.setItemsCanFocus(true);
listView.setOnItemClickListener(this);
TextView textView = new TextView(this);
CharSequence tooManyDeletesDescFormat =
getResources().getText(R.string.sync_too_many_deletes_desc);
textView.setText(String.format(tooManyDeletesDescFormat.toString(),
mNumDeletes, mProvider, mAccount.name));
final LinearLayout ll = new LinearLayout(this);
ll.setOrientation(LinearLayout.VERTICAL);
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0);
ll.addView(textView, lp);
ll.addView(listView, lp);
// TODO: consider displaying the icon of the account type
// AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes();
// for (AuthenticatorDescription desc : descs) {
// if (desc.type.equals(mAccount.type)) {
// try {
// final Context authContext = createPackageContext(desc.packageName, 0);
// ImageView imageView = new ImageView(this);
// imageView.setImageDrawable(authContext.getResources().getDrawable(desc.iconId));
// ll.addView(imageView, lp);
// } catch (PackageManager.NameNotFoundException e) {
// }
// break;
// }
// }
setContentView(ll);
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// the constants for position correspond to the items options array in onCreate()
if (position == 0) startSyncReallyDelete();
else if (position == 1) startSyncUndoDeletes();
finish();
}
private void startSyncReallyDelete() {
Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS, true);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
ContentResolver.requestSync(mAccount, mAuthority, extras);
}
private void startSyncUndoDeletes() {
Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS, true);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
ContentResolver.requestSync(mAccount, mAuthority, extras);
}
}

View File

@@ -16,38 +16,32 @@
package com.android.settings.deviceinfo; package com.android.settings.deviceinfo;
import com.android.internal.app.IMediaContainerService;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment; import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.deviceinfo.MemoryMeasurement.MeasurementReceiver;
import android.app.Activity;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnCancelListener;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.PackageManager;
import android.content.pm.PackageStats;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.graphics.drawable.shapes.RoundRectShape;
import android.hardware.UsbManager; import android.hardware.UsbManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.os.StatFs;
import android.os.storage.IMountService; import android.os.storage.IMountService;
import android.os.storage.StorageEventListener; import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager; import android.os.storage.StorageManager;
@@ -58,11 +52,10 @@ import android.text.format.Formatter;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class Memory extends SettingsPreferenceFragment implements OnCancelListener { public class Memory extends SettingsPreferenceFragment implements OnCancelListener,
MeasurementReceiver {
private static final String TAG = "Memory"; private static final String TAG = "Memory";
private static final boolean localLOGV = false; private static final boolean localLOGV = false;
@@ -110,13 +103,6 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
private int mInternalAppsColor; private int mInternalAppsColor;
private int mInternalUsedColor; private int mInternalUsedColor;
// Internal memory fields
private long mInternalTotalSize;
private long mInternalUsedSize;
private long mInternalMediaSize;
private long mInternalAppsSize;
private boolean mMeasured = false;
boolean mSdMountToggleAdded = true; boolean mSdMountToggleAdded = true;
// Access using getMountService() // Access using getMountService()
@@ -125,241 +111,46 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
private StorageManager mStorageManager = null; private StorageManager mStorageManager = null;
// Updates the memory usage bar graph. // Updates the memory usage bar graph.
private static final int MSG_UI_UPDATE_APPROXIMATE = 1; private static final int MSG_UI_UPDATE_INTERNAL_APPROXIMATE = 1;
// Updates the memory usage bar graph. // Updates the memory usage bar graph.
private static final int MSG_UI_UPDATE_EXACT = 2; private static final int MSG_UI_UPDATE_INTERNAL_EXACT = 2;
// Updates the memory usage stats for external.
private static final int MSG_UI_UPDATE_EXTERNAL_APPROXIMATE = 3;
private Handler mUpdateHandler = new Handler() { private Handler mUpdateHandler = new Handler() {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
switch (msg.what) { switch (msg.what) {
case MSG_UI_UPDATE_APPROXIMATE: case MSG_UI_UPDATE_INTERNAL_APPROXIMATE: {
updateUiApproximate(); Bundle bundle = msg.getData();
break; final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
case MSG_UI_UPDATE_EXACT: final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
updateUiExact(); updateUiApproximate(totalSize, availSize);
mMeasured = true;
break; break;
} }
} case MSG_UI_UPDATE_INTERNAL_EXACT: {
}; Bundle bundle = msg.getData();
final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer"; final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
final long mediaUsed = bundle.getLong(MemoryMeasurement.MEDIA_USED);
private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( final long appsUsed = bundle.getLong(MemoryMeasurement.APPS_USED);
DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService"); updateUiExact(totalSize, availSize, mediaUsed, appsUsed);
class MemoryMeasurementHandler extends Handler {
public static final int MSG_MEASURE_ALL = 1;
public static final int MSG_CONNECTED = 2;
public static final int MSG_DISCONNECTED = 3;
private List<String> mPendingApps = new ArrayList<String>();
private volatile boolean mBound = false;
private long mAppsSize = 0;
final private ServiceConnection mDefContainerConn = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
mBound = true;
IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
mMeasurementHandler.sendMessage(mMeasurementHandler.obtainMessage(
MemoryMeasurementHandler.MSG_CONNECTED, imcs));
}
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};
MemoryMeasurementHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_MEASURE_ALL: {
updateExternalStorage();
updateApproximateInternalStorage();
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
getActivity().bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE);
mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_APPROXIMATE);
break; break;
} }
case MSG_CONNECTED: { case MSG_UI_UPDATE_EXTERNAL_APPROXIMATE: {
IMediaContainerService imcs = (IMediaContainerService) msg.obj; Bundle bundle = msg.getData();
updateExactInternalStorage(imcs); final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
} final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
} updateExternalStorage(totalSize, availSize);
} break;
public void cleanUp() {
if (mBound) {
getActivity().unbindService(mDefContainerConn);
}
}
public void queuePackageMeasurementLocked(String packageName) {
mPendingApps.add(packageName);
}
public void requestQueuedMeasurementsLocked() {
final Activity activity = getActivity();
if (activity == null) {
return;
}
final PackageManager pm = activity.getPackageManager();
if (pm == null) {
return;
}
final int N = mPendingApps.size();
for (int i = 0; i < N; i++) {
pm.getPackageSizeInfo(mPendingApps.get(i), mStatsObserver);
}
}
final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
if (succeeded) {
mAppsSize += stats.codeSize + stats.dataSize;
}
synchronized (mPendingApps) {
mPendingApps.remove(stats.packageName);
if (mPendingApps.size() == 0) {
mInternalAppsSize = mAppsSize;
mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_EXACT);
} }
} }
} }
}; };
private void updateApproximateInternalStorage() { private MemoryMeasurement mMeasurement;
final File dataPath = Environment.getDataDirectory();
final StatFs stat = new StatFs(dataPath.getPath());
final long blockSize = stat.getBlockSize();
final long totalBlocks = stat.getBlockCount();
final long availableBlocks = stat.getAvailableBlocks();
final long totalSize = totalBlocks * blockSize;
final long availSize = availableBlocks * blockSize;
mInternalSize.setSummary(formatSize(totalSize));
mInternalAvail.setSummary(formatSize(availSize));
mInternalTotalSize = totalSize;
mInternalUsedSize = totalSize - availSize;
}
private void updateExactInternalStorage(IMediaContainerService imcs) {
long mediaSize;
try {
// TODO get these directories from somewhere
mediaSize = imcs.calculateDirectorySize("/data/media");
} catch (Exception e) {
Log.i(TAG, "Could not read memory from default container service");
return;
}
mInternalMediaSize = mediaSize;
// We have to get installd to measure the package sizes.
PackageManager pm = getPackageManager();
List<ApplicationInfo> apps = pm
.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
| PackageManager.GET_DISABLED_COMPONENTS);
if (apps != null) {
synchronized (mPendingApps) {
for (int i = 0; i < apps.size(); i++) {
final ApplicationInfo info = apps.get(i);
queuePackageMeasurementLocked(info.packageName);
}
requestQueuedMeasurementsLocked();
}
}
}
private void updateExternalStorage() {
if (Environment.isExternalStorageEmulated()) {
return;
}
String status = Environment.getExternalStorageState();
String readOnly = "";
if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
status = Environment.MEDIA_MOUNTED;
readOnly = mRes.getString(R.string.read_only);
}
if (status.equals(Environment.MEDIA_MOUNTED)) {
if (!Environment.isExternalStorageRemovable()) {
// This device has built-in storage that is not removable.
// There is no reason for the user to unmount it.
if (mSdMountToggleAdded) {
mSdMountPreferenceGroup.removePreference(mSdMountToggle);
mSdMountToggleAdded = false;
}
}
try {
File path = Environment.getExternalStorageDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long totalBlocks = stat.getBlockCount();
long availableBlocks = stat.getAvailableBlocks();
mSdSize.setSummary(formatSize(totalBlocks * blockSize));
mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly);
mSdMountToggle.setEnabled(true);
mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
} catch (IllegalArgumentException e) {
// this can occur if the SD card is removed, but we haven't
// received the
// ACTION_MEDIA_REMOVED Intent yet.
status = Environment.MEDIA_REMOVED;
}
} else {
mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
if (!Environment.isExternalStorageRemovable()) {
if (status.equals(Environment.MEDIA_UNMOUNTED)) {
if (!mSdMountToggleAdded) {
mSdMountPreferenceGroup.addPreference(mSdMountToggle);
mSdMountToggleAdded = true;
}
}
}
if (status.equals(Environment.MEDIA_UNMOUNTED) ||
status.equals(Environment.MEDIA_NOFS) ||
status.equals(Environment.MEDIA_UNMOUNTABLE) ) {
mSdMountToggle.setEnabled(true);
mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
} else {
mSdMountToggle.setEnabled(false);
mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
}
}
}
}
private MemoryMeasurementHandler mMeasurementHandler;
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
@@ -394,12 +185,27 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
mInternalAppsColor = mRes.getColor(R.color.memory_apps_usage); mInternalAppsColor = mRes.getColor(R.color.memory_apps_usage);
mInternalUsedColor = mRes.getColor(R.color.memory_used); mInternalUsedColor = mRes.getColor(R.color.memory_used);
float[] radius = new float[] {
5f, 5f, 5f, 5f, 5f, 5f, 5f, 5f
};
RoundRectShape shape1 = new RoundRectShape(radius, null, null);
ShapeDrawable mediaShape = new ShapeDrawable(shape1);
mediaShape.setIntrinsicWidth(32);
mediaShape.setIntrinsicHeight(32);
mediaShape.getPaint().setColor(mInternalMediaColor);
mInternalMediaUsage.setIcon(mediaShape);
ShapeDrawable appsShape = new ShapeDrawable(shape1);
appsShape.setIntrinsicWidth(32);
appsShape.setIntrinsicHeight(32);
appsShape.getPaint().setColor(mInternalAppsColor);
mInternalAppsUsage.setIcon(appsShape);
mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART); mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART);
// Start the thread that will measure the disk usage. mMeasurement = MemoryMeasurement.getInstance(getActivity());
final HandlerThread t = new HandlerThread("MeasurementHandler"); mMeasurement.setReceiver(this);
t.start();
mMeasurementHandler = new MemoryMeasurementHandler(t.getLooper());
} }
@Override @Override
@@ -411,9 +217,10 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
intentFilter.addDataScheme("file"); intentFilter.addDataScheme("file");
getActivity().registerReceiver(mReceiver, intentFilter); getActivity().registerReceiver(mReceiver, intentFilter);
if (!mMeasured) { if (!Environment.isExternalStorageEmulated()) {
mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL); mMeasurement.measureExternal();
} }
mMeasurement.measureInternal();
} }
StorageEventListener mStorageListener = new StorageEventListener() { StorageEventListener mStorageListener = new StorageEventListener() {
@@ -422,7 +229,9 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
Log.i(TAG, "Received storage state changed notification that " + Log.i(TAG, "Received storage state changed notification that " +
path + " changed state from " + oldState + path + " changed state from " + oldState +
" to " + newState); " to " + newState);
mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL); if (!Environment.isExternalStorageEmulated()) {
mMeasurement.measureExternal();
}
} }
}; };
@@ -430,7 +239,7 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
getActivity().unregisterReceiver(mReceiver); getActivity().unregisterReceiver(mReceiver);
mMeasurementHandler.removeMessages(MemoryMeasurementHandler.MSG_MEASURE_ALL); mMeasurement.cleanUp();
} }
@Override @Override
@@ -438,7 +247,6 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
if (mStorageManager != null && mStorageListener != null) { if (mStorageManager != null && mStorageListener != null) {
mStorageManager.unregisterListener(mStorageListener); mStorageManager.unregisterListener(mStorageListener);
} }
mMeasurementHandler.cleanUp();
super.onDestroy(); super.onDestroy();
} }
@@ -477,7 +285,12 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL); mMeasurement.invalidate();
if (!Environment.isExternalStorageEmulated()) {
mMeasurement.measureExternal();
}
mMeasurement.measureInternal();
} }
}; };
@@ -572,35 +385,95 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
} }
} }
private void updateUiExact() { private void updateUiExact(long totalSize, long availSize, long mediaSize, long appsSize) {
final float totalSize = mInternalTotalSize; mInternalSize.setSummary(formatSize(totalSize));
mInternalAvail.setSummary(formatSize(availSize));
final long mediaSize = mInternalMediaSize; mInternalMediaUsage.setSummary(formatSize(mediaSize));
final long appsSize = mInternalAppsSize; mInternalAppsUsage.setSummary(formatSize(appsSize));
mInternalUsageChart.clear(); mInternalUsageChart.clear();
mInternalUsageChart.addEntry(mediaSize / totalSize, mInternalMediaColor); mInternalUsageChart.addEntry(mediaSize / (float) totalSize, mInternalMediaColor);
mInternalUsageChart.addEntry(appsSize / totalSize, mInternalAppsColor); mInternalUsageChart.addEntry(appsSize / (float) totalSize, mInternalAppsColor);
final long usedSize = totalSize - availSize;
// There are other things that can take up storage, but we didn't // There are other things that can take up storage, but we didn't
// measure it. // measure it.
final long remaining = mInternalUsedSize - (mediaSize + appsSize); final long remaining = usedSize - (mediaSize + appsSize);
if (remaining > 0) { if (remaining > 0) {
mInternalUsageChart.addEntry(remaining / totalSize, mInternalUsedColor); mInternalUsageChart.addEntry(remaining / (float) totalSize, mInternalUsedColor);
} }
mInternalUsageChart.commit(); mInternalUsageChart.commit();
mInternalMediaUsage.setSummary(formatSize(mediaSize));
mInternalAppsUsage.setSummary(formatSize(appsSize));
} }
private void updateUiApproximate() { private void updateUiApproximate(long totalSize, long availSize) {
mInternalSize.setSummary(formatSize(totalSize));
mInternalAvail.setSummary(formatSize(availSize));
final long usedSize = totalSize - availSize;
mInternalUsageChart.clear(); mInternalUsageChart.clear();
mInternalUsageChart.addEntry(mInternalUsedSize / (float) mInternalTotalSize, getResources() mInternalUsageChart.addEntry(usedSize / (float) totalSize, mInternalUsedColor);
.getColor(R.color.memory_used));
mInternalUsageChart.commit(); mInternalUsageChart.commit();
} }
private void updateExternalStorage(long totalSize, long availSize) {
String status = Environment.getExternalStorageState();
String readOnly = "";
if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
status = Environment.MEDIA_MOUNTED;
readOnly = mRes.getString(R.string.read_only);
}
if (status.equals(Environment.MEDIA_MOUNTED)) {
if (!Environment.isExternalStorageRemovable()) {
// This device has built-in storage that is not removable.
// There is no reason for the user to unmount it.
if (mSdMountToggleAdded) {
mSdMountPreferenceGroup.removePreference(mSdMountToggle);
mSdMountToggleAdded = false;
}
}
try {
mSdSize.setSummary(formatSize(totalSize));
mSdAvail.setSummary(formatSize(availSize) + readOnly);
mSdMountToggle.setEnabled(true);
mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
} catch (IllegalArgumentException e) {
// this can occur if the SD card is removed, but we haven't
// received the
// ACTION_MEDIA_REMOVED Intent yet.
status = Environment.MEDIA_REMOVED;
}
} else {
mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
if (!Environment.isExternalStorageRemovable()) {
if (status.equals(Environment.MEDIA_UNMOUNTED)) {
if (!mSdMountToggleAdded) {
mSdMountPreferenceGroup.addPreference(mSdMountToggle);
mSdMountToggleAdded = true;
}
}
}
if (status.equals(Environment.MEDIA_UNMOUNTED) || status.equals(Environment.MEDIA_NOFS)
|| status.equals(Environment.MEDIA_UNMOUNTABLE)) {
mSdMountToggle.setEnabled(true);
mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
} else {
mSdMountToggle.setEnabled(false);
mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
}
}
}
private String formatSize(long size) { private String formatSize(long size) {
return Formatter.formatFileSize(getActivity(), size); return Formatter.formatFileSize(getActivity(), size);
} }
@@ -609,4 +482,25 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
// TODO: Is this really required? // TODO: Is this really required?
// finish(); // finish();
} }
@Override
public void updateApproximateExternal(Bundle bundle) {
final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_EXTERNAL_APPROXIMATE);
message.setData(bundle);
mUpdateHandler.sendMessage(message);
}
@Override
public void updateApproximateInternal(Bundle bundle) {
final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_APPROXIMATE);
message.setData(bundle);
mUpdateHandler.sendMessage(message);
}
@Override
public void updateExactInternal(Bundle bundle) {
final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_EXACT);
message.setData(bundle);
mUpdateHandler.sendMessage(message);
}
} }

View File

@@ -0,0 +1,403 @@
package com.android.settings.deviceinfo;
import com.android.internal.app.IMediaContainerService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.PackageManager;
import android.content.pm.PackageStats;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.StatFs;
import android.util.Log;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* Measure the memory for various systems.
*
* TODO: This class should ideally have less knowledge about what the context
* it's measuring is. In the future, reduce the amount of stuff it needs to
* know about by just keeping an array of measurement types of the following
* properties:
*
* Filesystem stats (using StatFs)
* Directory measurements (using DefaultContainerService.measureDir)
* Applicaiton measurements (using PackageManager)
*
* Then the calling application would just specify the type and an argument.
* This class would keep track of it while the calling application would
* decide on how to use it.
*/
public class MemoryMeasurement {
private static final String TAG = "MemoryMeasurement";
public static final String TOTAL_SIZE = "total_size";
public static final String AVAIL_SIZE = "avail_size";
public static final String APPS_USED = "apps_used";
public static final String MEDIA_USED = "media_used";
private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
private final MeasurementHandler mHandler;
private static volatile MemoryMeasurement sInstance;
private volatile WeakReference<MeasurementReceiver> mReceiver;
// Internal memory fields
private long mInternalTotalSize;
private long mInternalAvailSize;
private long mInternalMediaSize;
private long mInternalAppsSize;
// External memory fields
private long mExternalTotalSize;
private long mExternalAvailSize;
private MemoryMeasurement(Context context) {
// Start the thread that will measure the disk usage.
final HandlerThread t = new HandlerThread("MemoryMeasurement");
t.start();
mHandler = new MeasurementHandler(context, t.getLooper());
}
/**
* Get the singleton of the MemoryMeasurement class. The application
* context is used to avoid leaking activities.
*/
public static MemoryMeasurement getInstance(Context context) {
if (sInstance == null) {
synchronized (MemoryMeasurement.class) {
if (sInstance == null) {
sInstance = new MemoryMeasurement(context.getApplicationContext());
}
}
}
return sInstance;
}
public void setReceiver(MeasurementReceiver receiver) {
mReceiver = new WeakReference<MeasurementReceiver>(receiver);
}
public void measureExternal() {
if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE_EXTERNAL)) {
mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE_EXTERNAL);
}
}
public void measureInternal() {
if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE_INTERNAL)) {
mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE_INTERNAL);
}
}
public void cleanUp() {
mReceiver = null;
mHandler.cleanUp();
}
private void sendInternalApproximateUpdate() {
MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
if (receiver == null) {
return;
}
Bundle bundle = new Bundle();
bundle.putLong(TOTAL_SIZE, mInternalTotalSize);
bundle.putLong(AVAIL_SIZE, mInternalAvailSize);
receiver.updateApproximateInternal(bundle);
}
private void sendInternalExactUpdate() {
MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
if (receiver == null) {
return;
}
Bundle bundle = new Bundle();
bundle.putLong(TOTAL_SIZE, mInternalTotalSize);
bundle.putLong(AVAIL_SIZE, mInternalAvailSize);
bundle.putLong(APPS_USED, mInternalAppsSize);
bundle.putLong(MEDIA_USED, mInternalMediaSize);
receiver.updateExactInternal(bundle);
}
private void sendExternalApproximateUpdate() {
MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
if (receiver == null) {
return;
}
Bundle bundle = new Bundle();
bundle.putLong(TOTAL_SIZE, mExternalTotalSize);
bundle.putLong(AVAIL_SIZE, mExternalAvailSize);
receiver.updateApproximateExternal(bundle);
}
public interface MeasurementReceiver {
public void updateApproximateInternal(Bundle bundle);
public void updateExactInternal(Bundle bundle);
public void updateApproximateExternal(Bundle bundle);
}
private class MeasurementHandler extends Handler {
public static final int MSG_MEASURE_INTERNAL = 1;
public static final int MSG_MEASURE_EXTERNAL = 2;
public static final int MSG_CONNECTED = 3;
public static final int MSG_DISCONNECT = 4;
public static final int MSG_COMPLETED = 5;
public static final int MSG_INVALIDATE = 6;
private List<String> mPendingApps = new ArrayList<String>();
private Object mLock = new Object();
private IMediaContainerService mDefaultContainer;
private volatile boolean mBound = false;
private volatile boolean mMeasured = false;
private long mAppsSize = 0;
private final WeakReference<Context> mContext;
final private ServiceConnection mDefContainerConn = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
final IMediaContainerService imcs = IMediaContainerService.Stub
.asInterface(service);
mDefaultContainer = imcs;
mBound = true;
sendMessage(obtainMessage(MSG_CONNECTED, imcs));
}
public void onServiceDisconnected(ComponentName name) {
mBound = false;
removeMessages(MSG_CONNECTED);
}
};
public MeasurementHandler(Context context, Looper looper) {
super(looper);
mContext = new WeakReference<Context>(context);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_MEASURE_EXTERNAL: {
if (mMeasured) {
sendExternalApproximateUpdate();
break;
}
measureApproximateExternalStorage();
break;
}
case MSG_MEASURE_INTERNAL: {
if (mMeasured) {
sendInternalExactUpdate();
break;
}
final Context context = (mContext != null) ? mContext.get() : null;
if (context == null) {
return;
}
measureApproximateInternalStorage();
synchronized (mLock) {
if (mBound) {
removeMessages(MSG_DISCONNECT);
sendMessage(obtainMessage(MSG_CONNECTED, mDefaultContainer));
} else {
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
context.bindService(service, mDefContainerConn,
Context.BIND_AUTO_CREATE);
}
}
break;
}
case MSG_CONNECTED: {
IMediaContainerService imcs = (IMediaContainerService) msg.obj;
measureExactInternalStorage(imcs);
}
case MSG_DISCONNECT: {
synchronized (mLock) {
if (mBound) {
final Context context = (mContext != null) ? mContext.get() : null;
if (context == null) {
return;
}
mBound = false;
context.unbindService(mDefContainerConn);
}
}
}
case MSG_COMPLETED: {
mMeasured = true;
sendInternalExactUpdate();
break;
}
case MSG_INVALIDATE: {
mMeasured = false;
break;
}
}
}
public void cleanUp() {
removeMessages(MSG_MEASURE_INTERNAL);
removeMessages(MSG_MEASURE_EXTERNAL);
sendEmptyMessage(MSG_DISCONNECT);
}
public void queuePackageMeasurementLocked(String packageName) {
mPendingApps.add(packageName);
}
/**
* Request measurement of each package.
*
* @param pm PackageManager instance to query
*/
public void requestQueuedMeasurementsLocked(PackageManager pm) {
final int N = mPendingApps.size();
for (int i = 0; i < N; i++) {
pm.getPackageSizeInfo(mPendingApps.get(i), mStatsObserver);
}
}
final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
if (succeeded) {
mAppsSize += stats.codeSize + stats.dataSize;
}
synchronized (mPendingApps) {
mPendingApps.remove(stats.packageName);
if (mPendingApps.size() == 0) {
mInternalAppsSize = mAppsSize;
onInternalMeasurementComplete();
}
}
}
};
private void onInternalMeasurementComplete() {
sendEmptyMessage(MSG_COMPLETED);
}
private void measureApproximateInternalStorage() {
final File dataPath = Environment.getDataDirectory();
final StatFs stat = new StatFs(dataPath.getPath());
final long blockSize = stat.getBlockSize();
final long totalBlocks = stat.getBlockCount();
final long availableBlocks = stat.getAvailableBlocks();
final long totalSize = totalBlocks * blockSize;
final long availSize = availableBlocks * blockSize;
mInternalTotalSize = totalSize;
mInternalAvailSize = availSize;
sendInternalApproximateUpdate();
}
private void measureExactInternalStorage(IMediaContainerService imcs) {
Context context = mContext != null ? mContext.get() : null;
if (context == null) {
return;
}
// We have to get installd to measure the package sizes.
PackageManager pm = context.getPackageManager();
if (pm == null) {
return;
}
long mediaSize;
try {
// TODO get these directories from somewhere
mediaSize = imcs.calculateDirectorySize("/data/media");
} catch (Exception e) {
Log.i(TAG, "Could not read memory from default container service");
return;
}
mInternalMediaSize = mediaSize;
final List<ApplicationInfo> apps = pm
.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
| PackageManager.GET_DISABLED_COMPONENTS);
if (apps != null) {
synchronized (mPendingApps) {
for (int i = 0; i < apps.size(); i++) {
final ApplicationInfo info = apps.get(i);
queuePackageMeasurementLocked(info.packageName);
}
requestQueuedMeasurementsLocked(pm);
}
}
// Sending of the message back to the MeasurementReceiver is
// completed in the PackageObserver
}
public void measureApproximateExternalStorage() {
File path = Environment.getExternalStorageDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long totalBlocks = stat.getBlockCount();
long availableBlocks = stat.getAvailableBlocks();
mExternalTotalSize = totalBlocks * blockSize;
mExternalAvailSize = availableBlocks * blockSize;
sendExternalApproximateUpdate();
}
}
public void invalidate() {
mHandler.sendEmptyMessage(MeasurementHandler.MSG_INVALIDATE);
}
}

View File

@@ -16,8 +16,12 @@
package com.android.settings.deviceinfo; package com.android.settings.deviceinfo;
import com.android.settings.R;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
@@ -28,10 +32,12 @@ import java.util.Collection;
* *
*/ */
public class PercentageBarChart extends View { public class PercentageBarChart extends View {
private final Paint mBackgroundPaint = new Paint(); private final Paint mEmptyPaint = new Paint();
private Collection<Entry> mEntries; private Collection<Entry> mEntries;
private int mMinTickWidth = 1;
public static class Entry { public static class Entry {
public final float percentage; public final float percentage;
public final Paint paint; public final Paint paint;
@@ -45,20 +51,27 @@ public class PercentageBarChart extends View {
public PercentageBarChart(Context context, AttributeSet attrs) { public PercentageBarChart(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
mBackgroundPaint.setARGB(255, 64, 64, 64); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PercentageBarChart);
mBackgroundPaint.setStyle(Paint.Style.FILL); mMinTickWidth = a.getDimensionPixelSize(R.styleable.PercentageBarChart_minTickWidth, 1);
int emptyColor = a.getColor(R.styleable.PercentageBarChart_emptyColor, Color.BLACK);
a.recycle();
mEmptyPaint.setColor(emptyColor);
mEmptyPaint.setStyle(Paint.Style.FILL);
} }
@Override @Override
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
final int width = getWidth(); final int left = getPaddingLeft();
final int height = getHeight(); final int right = getWidth() - getPaddingRight();
final int top = getPaddingTop();
final int bottom = getHeight() - getPaddingBottom();
canvas.drawPaint(mBackgroundPaint); final int width = right - left;
int lastX = 0; int lastX = left;
if (mEntries != null) { if (mEntries != null) {
for (final Entry e : mEntries) { for (final Entry e : mEntries) {
@@ -66,18 +79,20 @@ public class PercentageBarChart extends View {
if (e.percentage == 0f) { if (e.percentage == 0f) {
entryWidth = 0; entryWidth = 0;
} else { } else {
entryWidth = Math.max(1, (int) (width * e.percentage)); entryWidth = Math.max(mMinTickWidth, (int) (width * e.percentage));
} }
final int nextX = lastX + entryWidth; final int nextX = lastX + entryWidth;
if (nextX >= width) { if (nextX >= right) {
break; break;
} }
canvas.drawRect(lastX, 0, nextX, height, e.paint); canvas.drawRect(lastX, top, nextX, bottom, e.paint);
lastX = nextX; lastX = nextX;
} }
} }
canvas.drawRect(lastX, top, lastX + width, bottom, mEmptyPaint);
} }
/** /**
@@ -85,7 +100,7 @@ public class PercentageBarChart extends View {
* calling {@link #invalidate()}. * calling {@link #invalidate()}.
*/ */
public void setBackgroundColor(int color) { public void setBackgroundColor(int color) {
mBackgroundPaint.setColor(color); mEmptyPaint.setColor(color);
} }
/** /**

View File

@@ -138,6 +138,9 @@ public class InputMethodAndSubtypeUtil {
HashSet<String> set = new HashSet<String>(); HashSet<String> set = new HashSet<String>();
String disabledIMEsStr = Settings.Secure.getString( String disabledIMEsStr = Settings.Secure.getString(
resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS); resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
if (TextUtils.isEmpty(disabledIMEsStr)) {
return set;
}
sStringInputMethodSplitter.setString(disabledIMEsStr); sStringInputMethodSplitter.setString(disabledIMEsStr);
while(sStringInputMethodSplitter.hasNext()) { while(sStringInputMethodSplitter.hasNext()) {
set.add(sStringInputMethodSplitter.next()); set.add(sStringInputMethodSplitter.next());