Rework manage applications to be page-able.

Turn everything around so that we can have multiple list views
with their own adapters.  Switch to using a ViewPager for managing
the different lists.  Smile!

Change-Id: I9c102abb06cf67f313a8696507aa4597b38c7ab9
This commit is contained in:
Dianne Hackborn
2012-05-16 15:50:48 -07:00
parent 1cbacf10bd
commit 309c5dcee1
9 changed files with 762 additions and 548 deletions

View File

@@ -2,7 +2,7 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_JAVA_LIBRARIES := bouncycastle
LOCAL_STATIC_JAVA_LIBRARIES := guava
LOCAL_STATIC_JAVA_LIBRARIES := guava android-support-v4
LOCAL_MODULE_TAGS := optional

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
<!-- Copyright (C) 2012 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.
@@ -83,12 +83,6 @@
</view>
</LinearLayout>
<view class="com.android.settings.applications.RunningProcessesView"
android:id="@+id/running_processes"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<LinearLayout android:id="@+id/loading_container"
android:orientation="vertical"
android:layout_width="match_parent"

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 2012, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
<android.support.v4.view.PagerTabStrip
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top">
</android.support.v4.view.PagerTabStrip>
</android.support.v4.view.ViewPager>
</LinearLayout>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2012 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<view class="com.android.settings.applications.RunningProcessesView"
android:id="@+id/running_processes"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<LinearLayout android:id="@+id/loading_container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@*android:dimen/preference_fragment_padding_side"
android:layout_marginRight="@*android:dimen/preference_fragment_padding_side"
android:visibility="gone"
android:gravity="center">
<ProgressBar style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/settings_safetylegal_activity_loading"
android:paddingTop="4dip"
android:singleLine="true" />
</LinearLayout>
</FrameLayout>

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 2012, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Spinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="48sp"
android:minWidth="180dp"
android:layout_marginLeft="@*android:dimen/preference_fragment_padding_side"
android:layout_marginRight="@*android:dimen/preference_fragment_padding_side"
/>
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@*android:dimen/preference_fragment_padding_side"
android:layout_marginRight="@*android:dimen/preference_fragment_padding_side"
android:scaleType="fitXY"
android:src="?android:attr/listDivider" />
<FrameLayout
android:id="@+id/spinner_content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1" />
</LinearLayout>

View File

@@ -42,7 +42,7 @@ public class AppViewHolder {
}
}
void updateSizeText(ManageApplications ma, int whichSize) {
void updateSizeText(CharSequence invalidSizeStr, int whichSize) {
if (ManageApplications.DEBUG) Log.i(ManageApplications.TAG, "updateSizeText of " + entry.label + " " + entry
+ ": " + entry.sizeStr);
if (entry.sizeStr != null) {
@@ -58,7 +58,7 @@ public class AppViewHolder {
break;
}
} else if (entry.size == ApplicationsState.SIZE_INVALID) {
appSize.setText(ma.mInvalidSizeStr);
appSize.setText(invalidSizeStr);
}
}
}

View File

@@ -227,24 +227,21 @@ public class ApplicationsState {
PackageIntentReceiver mPackageIntentReceiver;
boolean mResumed;
Callbacks mCurCallbacks;
// Information about all applications. Synchronize on mAppEntries
// Information about all applications. Synchronize on mEntriesMap
// to protect access to these.
final ArrayList<Session> mSessions = new ArrayList<Session>();
final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>();
final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
final HashMap<String, AppEntry> mEntriesMap = new HashMap<String, AppEntry>();
final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>();
List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>();
long mCurId = 1;
String mCurComputingSizePkg;
boolean mSessionsChanged;
// Rebuilding of app list. Synchronized on mRebuildSync.
final Object mRebuildSync = new Object();
boolean mRebuildRequested;
boolean mRebuildAsync;
AppFilter mRebuildFilter;
Comparator<AppEntry> mRebuildComparator;
ArrayList<AppEntry> mRebuildResult;
// Temporary for dispatching session callbacks. Only touched by main thread.
final ArrayList<Session> mActiveSessions = new ArrayList<Session>();
/**
* Receives notifications when applications are added/removed.
@@ -262,6 +259,9 @@ public class ApplicationsState {
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(this, sdFilter);
}
void unregisterReceiver() {
mContext.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent) {
String actionStr = intent.getAction();
@@ -300,6 +300,21 @@ public class ApplicationsState {
}
}
void rebuildActiveSessions() {
synchronized (mEntriesMap) {
if (!mSessionsChanged) {
return;
}
mActiveSessions.clear();
for (int i=0; i<mSessions.size(); i++) {
Session s = mSessions.get(i);
if (s.mResumed) {
mActiveSessions.add(s);
}
}
}
}
class MainHandler extends Handler {
static final int MSG_REBUILD_COMPLETE = 1;
static final int MSG_PACKAGE_LIST_CHANGED = 2;
@@ -310,35 +325,39 @@ public class ApplicationsState {
@Override
public void handleMessage(Message msg) {
rebuildActiveSessions();
switch (msg.what) {
case MSG_REBUILD_COMPLETE: {
if (mCurCallbacks != null) {
mCurCallbacks.onRebuildComplete((ArrayList<AppEntry>)msg.obj);
Session s = (Session)msg.obj;
if (mActiveSessions.contains(s)) {
s.mCallbacks.onRebuildComplete(s.mLastAppList);
}
} break;
case MSG_PACKAGE_LIST_CHANGED: {
if (mCurCallbacks != null) {
mCurCallbacks.onPackageListChanged();
for (int i=0; i<mActiveSessions.size(); i++) {
mActiveSessions.get(i).mCallbacks.onPackageListChanged();
}
} break;
case MSG_PACKAGE_ICON_CHANGED: {
if (mCurCallbacks != null) {
mCurCallbacks.onPackageIconChanged();
for (int i=0; i<mActiveSessions.size(); i++) {
mActiveSessions.get(i).mCallbacks.onPackageIconChanged();
}
} break;
case MSG_PACKAGE_SIZE_CHANGED: {
if (mCurCallbacks != null) {
mCurCallbacks.onPackageSizeChanged((String)msg.obj);
for (int i=0; i<mActiveSessions.size(); i++) {
mActiveSessions.get(i).mCallbacks.onPackageSizeChanged(
(String)msg.obj);
}
} break;
case MSG_ALL_SIZES_COMPUTED: {
if (mCurCallbacks != null) {
mCurCallbacks.onAllSizesComputed();
for (int i=0; i<mActiveSessions.size(); i++) {
mActiveSessions.get(i).mCallbacks.onAllSizesComputed();
}
} break;
case MSG_RUNNING_STATE_CHANGED: {
if (mCurCallbacks != null) {
mCurCallbacks.onRunningStateChanged(msg.arg1 != 0);
for (int i=0; i<mActiveSessions.size(); i++) {
mActiveSessions.get(i).mCallbacks.onRunningStateChanged(
msg.arg1 != 0);
}
} break;
}
@@ -391,157 +410,226 @@ public class ApplicationsState {
}
}
void resume(Callbacks callbacks) {
if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
synchronized (mEntriesMap) {
mCurCallbacks = callbacks;
mResumed = true;
if (mPackageIntentReceiver == null) {
mPackageIntentReceiver = new PackageIntentReceiver();
mPackageIntentReceiver.registerReceiver();
}
mApplications = mPm.getInstalledApplications(
PackageManager.GET_UNINSTALLED_PACKAGES |
PackageManager.GET_DISABLED_COMPONENTS);
if (mApplications == null) {
mApplications = new ArrayList<ApplicationInfo>();
}
public class Session {
final Callbacks mCallbacks;
boolean mResumed;
if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
// If an interesting part of the configuration has changed, we
// should completely reload the app entries.
mEntriesMap.clear();
mAppEntries.clear();
} else {
for (int i=0; i<mAppEntries.size(); i++) {
mAppEntries.get(i).sizeStale = true;
}
}
// Rebuilding of app list. Synchronized on mRebuildSync.
final Object mRebuildSync = new Object();
boolean mRebuildRequested;
boolean mRebuildAsync;
AppFilter mRebuildFilter;
Comparator<AppEntry> mRebuildComparator;
ArrayList<AppEntry> mRebuildResult;
ArrayList<AppEntry> mLastAppList;
for (int i=0; i<mApplications.size(); i++) {
final ApplicationInfo info = mApplications.get(i);
// Need to trim out any applications that are disabled by
// something different than the user.
if (!info.enabled && info.enabledSetting
!= PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
mApplications.remove(i);
i--;
continue;
Session(Callbacks callbacks) {
mCallbacks = callbacks;
}
public void resume() {
if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
synchronized (mEntriesMap) {
if (!mResumed) {
mResumed = true;
mSessionsChanged = true;
doResumeIfNeededLocked();
}
final AppEntry entry = mEntriesMap.get(info.packageName);
if (entry != null) {
entry.info = info;
}
}
mCurComputingSizePkg = null;
if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
}
if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
}
}
void pause() {
if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
synchronized (mEntriesMap) {
mCurCallbacks = null;
mResumed = false;
if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
}
}
// Creates a new list of app entries with the given filter and comparator.
ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) {
synchronized (mRebuildSync) {
mRebuildRequested = true;
mRebuildAsync = false;
mRebuildFilter = filter;
mRebuildComparator = comparator;
mRebuildResult = null;
if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) {
mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_REBUILD_LIST);
}
// We will wait for .25s for the list to be built.
long waitend = SystemClock.uptimeMillis()+250;
while (mRebuildResult == null) {
long now = SystemClock.uptimeMillis();
if (now >= waitend) {
break;
}
try {
mRebuildSync.wait(waitend - now);
} catch (InterruptedException e) {
public void pause() {
if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
synchronized (mEntriesMap) {
if (mResumed) {
mResumed = false;
mSessionsChanged = true;
mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this);
doPauseIfNeededLocked();
}
if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
}
mRebuildAsync = true;
return mRebuildResult;
}
}
void handleRebuildList() {
AppFilter filter;
Comparator<AppEntry> comparator;
synchronized (mRebuildSync) {
if (!mRebuildRequested) {
return;
}
filter = mRebuildFilter;
comparator = mRebuildComparator;
mRebuildRequested = false;
mRebuildFilter = null;
mRebuildComparator = null;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
if (filter != null) {
filter.init();
}
List<ApplicationInfo> apps;
synchronized (mEntriesMap) {
apps = new ArrayList<ApplicationInfo>(mApplications);
}
ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>();
if (DEBUG) Log.i(TAG, "Rebuilding...");
for (int i=0; i<apps.size(); i++) {
ApplicationInfo info = apps.get(i);
if (filter == null || filter.filterApp(info)) {
// Creates a new list of app entries with the given filter and comparator.
ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) {
synchronized (mRebuildSync) {
synchronized (mEntriesMap) {
if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
AppEntry entry = getEntryLocked(info);
entry.ensureLabel(mContext);
if (DEBUG) Log.i(TAG, "Using " + info.packageName + ": " + entry);
filteredApps.add(entry);
if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
mRebuildingSessions.add(this);
mRebuildRequested = true;
mRebuildAsync = false;
mRebuildFilter = filter;
mRebuildComparator = comparator;
mRebuildResult = null;
if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) {
Message msg = mBackgroundHandler.obtainMessage(
BackgroundHandler.MSG_REBUILD_LIST);
mBackgroundHandler.sendMessage(msg);
}
}
// We will wait for .25s for the list to be built.
long waitend = SystemClock.uptimeMillis()+250;
while (mRebuildResult == null) {
long now = SystemClock.uptimeMillis();
if (now >= waitend) {
break;
}
try {
mRebuildSync.wait(waitend - now);
} catch (InterruptedException e) {
}
}
mRebuildAsync = true;
return mRebuildResult;
}
}
Collections.sort(filteredApps, comparator);
void handleRebuildList() {
AppFilter filter;
Comparator<AppEntry> comparator;
synchronized (mRebuildSync) {
if (!mRebuildRequested) {
return;
}
synchronized (mRebuildSync) {
if (!mRebuildRequested) {
if (!mRebuildAsync) {
mRebuildResult = filteredApps;
mRebuildSync.notifyAll();
} else {
if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE)) {
Message msg = mMainHandler.obtainMessage(
MainHandler.MSG_REBUILD_COMPLETE, filteredApps);
mMainHandler.sendMessage(msg);
filter = mRebuildFilter;
comparator = mRebuildComparator;
mRebuildRequested = false;
mRebuildFilter = null;
mRebuildComparator = null;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
if (filter != null) {
filter.init();
}
List<ApplicationInfo> apps;
synchronized (mEntriesMap) {
apps = new ArrayList<ApplicationInfo>(mApplications);
}
ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>();
if (DEBUG) Log.i(TAG, "Rebuilding...");
for (int i=0; i<apps.size(); i++) {
ApplicationInfo info = apps.get(i);
if (filter == null || filter.filterApp(info)) {
synchronized (mEntriesMap) {
if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
AppEntry entry = getEntryLocked(info);
entry.ensureLabel(mContext);
if (DEBUG) Log.i(TAG, "Using " + info.packageName + ": " + entry);
filteredApps.add(entry);
if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
}
}
}
Collections.sort(filteredApps, comparator);
synchronized (mRebuildSync) {
if (!mRebuildRequested) {
mLastAppList = filteredApps;
if (!mRebuildAsync) {
mRebuildResult = filteredApps;
mRebuildSync.notifyAll();
} else {
if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) {
Message msg = mMainHandler.obtainMessage(
MainHandler.MSG_REBUILD_COMPLETE, this);
mMainHandler.sendMessage(msg);
}
}
}
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
public void release() {
pause();
synchronized (mEntriesMap) {
mSessions.remove(this);
}
}
}
public Session newSession(Callbacks callbacks) {
Session s = new Session(callbacks);
synchronized (mEntriesMap) {
mSessions.add(s);
}
return s;
}
void doResumeIfNeededLocked() {
if (mResumed) {
return;
}
mResumed = true;
if (mPackageIntentReceiver == null) {
mPackageIntentReceiver = new PackageIntentReceiver();
mPackageIntentReceiver.registerReceiver();
}
mApplications = mPm.getInstalledApplications(
PackageManager.GET_UNINSTALLED_PACKAGES |
PackageManager.GET_DISABLED_COMPONENTS);
if (mApplications == null) {
mApplications = new ArrayList<ApplicationInfo>();
}
if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
// If an interesting part of the configuration has changed, we
// should completely reload the app entries.
mEntriesMap.clear();
mAppEntries.clear();
} else {
for (int i=0; i<mAppEntries.size(); i++) {
mAppEntries.get(i).sizeStale = true;
}
}
for (int i=0; i<mApplications.size(); i++) {
final ApplicationInfo info = mApplications.get(i);
// Need to trim out any applications that are disabled by
// something different than the user.
if (!info.enabled && info.enabledSetting
!= PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
mApplications.remove(i);
i--;
continue;
}
final AppEntry entry = mEntriesMap.get(info.packageName);
if (entry != null) {
entry.info = info;
}
}
mCurComputingSizePkg = null;
if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
}
}
void doPauseIfNeededLocked() {
if (!mResumed) {
return;
}
for (int i=0; i<mSessions.size(); i++) {
if (mSessions.get(i).mResumed) {
return;
}
}
mResumed = false;
if (mPackageIntentReceiver != null) {
mPackageIntentReceiver.unregisterReceiver();
mPackageIntentReceiver = null;
}
}
AppEntry getEntry(String packageName) {
@@ -772,7 +860,18 @@ public class ApplicationsState {
@Override
public void handleMessage(Message msg) {
// Always try rebuilding list first thing, if needed.
handleRebuildList();
ArrayList<Session> rebuildingSessions = null;
synchronized (mEntriesMap) {
if (mRebuildingSessions.size() > 0) {
rebuildingSessions = new ArrayList<Session>(mRebuildingSessions);
mRebuildingSessions.clear();
}
}
if (rebuildingSessions != null) {
for (int i=0; i<rebuildingSessions.size(); i++) {
rebuildingSessions.get(i).handleRebuildList();
}
}
switch (msg.what) {
case MSG_REBUILD_LIST: {

View File

@@ -98,6 +98,7 @@ public class InstalledAppDetails extends Fragment
private AppWidgetManager mAppWidgetManager;
private DevicePolicyManager mDpm;
private ApplicationsState mState;
private ApplicationsState.Session mSession;
private ApplicationsState.AppEntry mAppEntry;
private PackageInfo mPackageInfo;
private CanBeOnSdCardChecker mCanBeOnSdCardChecker;
@@ -348,6 +349,7 @@ public class InstalledAppDetails extends Fragment
super.onCreate(icicle);
mState = ApplicationsState.getInstance(getActivity().getApplication());
mSession = mState.newSession(this);
mPm = getActivity().getPackageManager();
IBinder b = ServiceManager.getService(Context.USB_SERVICE);
mUsbManager = IUsbManager.Stub.asInterface(b);
@@ -423,7 +425,7 @@ public class InstalledAppDetails extends Fragment
public void onResume() {
super.onResume();
mState.resume(this);
mSession.resume();
if (!refreshUi()) {
setIntentAndFinish(true, true);
}
@@ -432,7 +434,7 @@ public class InstalledAppDetails extends Fragment
@Override
public void onPause() {
super.onPause();
mState.pause();
mSession.pause();
}
@Override

File diff suppressed because it is too large Load Diff