Switch to recyclerview in ManageApplications

- Replace ListView with RecyclerView in layout
- Replace ApplicationAdapter's superclass to be RecyclerView.Adapter
- Change adapter interfaces (where necessary) to work with RecyclerView
- Replace fast scroll with Recycler's mechanism (all in xml)
- Removed section indexer (text bubble when fast scroll) because
  recyclerview doesn't support it.

Bug: 64804294
Test: robotests
Change-Id: I55b221836ce6abdeddf4568c8a8a5632cbddbd3b
This commit is contained in:
Fan Zhang
2017-10-12 11:09:24 -07:00
parent 24ff765731
commit 78369d91bc
17 changed files with 534 additions and 448 deletions

27
res/drawable/line.xml Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<padding
android:top="10dp"
android:left="10dp"
android:right="10dp"
android:bottom="10dp"/>
</shape>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/line" />
<item
android:drawable="@drawable/line" />
</selector>

32
res/drawable/thumb.xml Normal file
View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="8dp"
android:topRightRadius="8dp"
android:bottomLeftRadius="8dp"
android:bottomRightRadius="8dp"/>
<padding
android:paddingLeft="8dp"
android:paddingRight="8dp" />
<solid android:color="?android:attr/colorControlNormal" />
</shape>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/thumb"/>
<item
android:drawable="@drawable/thumb"/>
</selector>

View File

@@ -14,44 +14,53 @@
limitations under the License. limitations under the License.
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent" xmlns:settings="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"> android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"> android:layout_weight="1">
<LinearLayout android:id="@+id/list_container" <LinearLayout
android:layout_width="match_parent" android:id="@+id/list_container"
android:layout_height="match_parent" android:layout_width="match_parent"
android:orientation="vertical" android:layout_height="match_parent"
android:visibility="gone"> android:orientation="vertical"
android:visibility="gone">
<FrameLayout <FrameLayout
android:id="@+id/pinned_header" android:id="@+id/pinned_header"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<FrameLayout android:layout_width="match_parent" <FrameLayout
android:layout_height="0px" android:layout_width="match_parent"
android:layout_weight="1"> android:layout_height="0px"
android:layout_weight="1">
<ListView android:id="@android:id/list" <android.support.v7.widget.RecyclerView
android:drawSelectorOnTop="false" android:id="@+id/apps_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" settings:fastScrollEnabled="true"
android:scrollbarStyle="@integer/preference_scrollbar_style" /> settings:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
settings:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
settings:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable"/>
<TextView android:id="@android:id/empty" <TextView
android:layout_width="match_parent" android:id="@android:id/empty"
android:layout_height="match_parent" android:layout_width="match_parent"
android:gravity="center" android:layout_height="match_parent"
android:text="@string/no_applications" android:gravity="center"
android:textAppearance="?android:attr/textAppearanceLarge" /> android:text="@string/no_applications"
android:textAppearance="?android:attr/textAppearanceLarge"
android:visibility="invisible" />
</FrameLayout> </FrameLayout>

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2017 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.applications.manageapplications;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.support.annotation.StringRes;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
public class ApplicationViewHolder extends RecyclerView.ViewHolder {
private final TextView mAppName;
private final ImageView mAppIcon;
@VisibleForTesting
final TextView mSummary;
@VisibleForTesting
final TextView mDisabled;
ApplicationViewHolder(View itemView) {
super(itemView);
mAppName = itemView.findViewById(android.R.id.title);
mAppIcon = itemView.findViewById(android.R.id.icon);
mSummary = itemView.findViewById(R.id.widget_text1);
mDisabled = itemView.findViewById(R.id.widget_text2);
}
static View newView(LayoutInflater inflater, ViewGroup parent) {
final View root = LayoutInflater.from(parent.getContext())
.inflate(R.layout.preference_app, parent, false);
inflater.inflate(R.layout.widget_text_views,
root.findViewById(android.R.id.widget_frame));
return root;
}
void setSummary(CharSequence summary) {
mSummary.setText(summary);
}
void setSummary(@StringRes int summary) {
mSummary.setText(summary);
}
void setEnabled(boolean isEnabled) {
itemView.setEnabled(isEnabled);
}
void setTitle(CharSequence title) {
if (title == null) {
return;
}
mAppName.setText(title);
}
void setIcon(Drawable icon) {
if (icon == null) {
return;
}
mAppIcon.setImageDrawable(icon);
}
void updateDisableView(ApplicationInfo info) {
if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
mDisabled.setVisibility(View.VISIBLE);
mDisabled.setText(R.string.not_installed);
} else if (!info.enabled || info.enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
mDisabled.setVisibility(View.VISIBLE);
mDisabled.setText(R.string.disabled);
} else {
mDisabled.setVisibility(View.GONE);
}
}
void updateSizeText(AppEntry entry, CharSequence invalidSizeStr, int whichSize) {
if (ManageApplications.DEBUG) {
Log.d(ManageApplications.TAG, "updateSizeText of "
+ entry.label + " " + entry + ": " + entry.sizeStr);
}
if (entry.sizeStr != null) {
switch (whichSize) {
case ManageApplications.SIZE_INTERNAL:
setSummary(entry.internalSizeStr);
break;
case ManageApplications.SIZE_EXTERNAL:
setSummary(entry.externalSizeStr);
break;
default:
setSummary(entry.sizeStr);
break;
}
} else if (entry.size == ApplicationsState.SIZE_INVALID) {
setSummary(invalidSizeStr);
}
}
}

View File

@@ -35,12 +35,14 @@ public interface FileViewHolderController {
/** /**
* Initializes the view within an AppViewHolder. * Initializes the view within an AppViewHolder.
*
* @param holder The holder to use to initialize. * @param holder The holder to use to initialize.
*/ */
void setupView(AppViewHolder holder); void setupView(ApplicationViewHolder holder);
/** /**
* Handles the behavior when the view is clicked. * Handles the behavior when the view is clicked.
*
* @param fragment Fragment where the click originated. * @param fragment Fragment where the click originated.
*/ */
void onClick(Fragment fragment); void onClick(Fragment fragment);

View File

@@ -40,16 +40,14 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo; import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.icu.text.AlphabeticIndex;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.Handler;
import android.os.LocaleList;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.preference.PreferenceFrameLayout; import android.preference.PreferenceFrameLayout;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.Log; import android.util.Log;
@@ -59,19 +57,11 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener; import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.SectionIndexer;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R; import com.android.settings.R;
@@ -86,7 +76,6 @@ import com.android.settings.Settings.StorageUseActivity;
import com.android.settings.Settings.UsageAccessSettingsActivity; import com.android.settings.Settings.UsageAccessSettingsActivity;
import com.android.settings.Settings.WriteSettingsActivity; import com.android.settings.Settings.WriteSettingsActivity;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.AppStateAppOpsBridge.PermissionState; import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
import com.android.settings.applications.AppStateBaseBridge; import com.android.settings.applications.AppStateBaseBridge;
@@ -122,13 +111,13 @@ import com.android.settingslib.applications.ApplicationsState.AppFilter;
import com.android.settingslib.applications.ApplicationsState.CompoundFilter; import com.android.settingslib.applications.ApplicationsState.CompoundFilter;
import com.android.settingslib.applications.ApplicationsState.VolumeFilter; import com.android.settingslib.applications.ApplicationsState.VolumeFilter;
import com.android.settingslib.applications.StorageStatsSource; import com.android.settingslib.applications.StorageStatsSource;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.wrapper.PackageManagerWrapper; import com.android.settingslib.wrapper.PackageManagerWrapper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Locale;
import java.util.Set; import java.util.Set;
/** /**
@@ -138,7 +127,7 @@ import java.util.Set;
* intent. * intent.
*/ */
public class ManageApplications extends InstrumentedPreferenceFragment public class ManageApplications extends InstrumentedPreferenceFragment
implements OnItemClickListener, OnItemSelectedListener { implements View.OnClickListener, OnItemSelectedListener {
static final String TAG = "ManageApplications"; static final String TAG = "ManageApplications";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -185,23 +174,17 @@ public class ManageApplications extends InstrumentedPreferenceFragment
private ApplicationsState mApplicationsState; private ApplicationsState mApplicationsState;
public int mListType; public int mListType;
public AppFilterItem mFilter; private AppFilterItem mFilter;
private ApplicationsAdapter mApplications;
public ApplicationsAdapter mApplications;
private View mLoadingContainer; private View mLoadingContainer;
private View mListContainer; private View mListContainer;
private RecyclerView mRecyclerView;
// ListView used to display list
private ListView mListView;
// Size resource used for packages whose size computation failed for some reason // Size resource used for packages whose size computation failed for some reason
CharSequence mInvalidSizeStr; CharSequence mInvalidSizeStr;
// layout inflater object used to inflate views
private LayoutInflater mInflater;
private String mCurrentPkgName; private String mCurrentPkgName;
private int mCurrentUid; private int mCurrentUid;
@@ -234,6 +217,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment
private int mStorageType; private int mStorageType;
private boolean mIsWorkOnly; private boolean mIsWorkOnly;
private int mWorkUserId; private int mWorkUserId;
private View mEmptyView;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@@ -304,25 +288,14 @@ public class ManageApplications extends InstrumentedPreferenceFragment
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
// initialize the inflater
mInflater = inflater;
mRootView = inflater.inflate(R.layout.manage_applications_apps, null); mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
mLoadingContainer = mRootView.findViewById(R.id.loading_container); mLoadingContainer = mRootView.findViewById(R.id.loading_container);
mListContainer = mRootView.findViewById(R.id.list_container); mListContainer = mRootView.findViewById(R.id.list_container);
if (mListContainer != null) { if (mListContainer != null) {
// Create adapter and list view here // Create adapter and list view here
View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty); mEmptyView = mListContainer.findViewById(android.R.id.empty);
ListView lv = mListContainer.findViewById(android.R.id.list); mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter,
if (emptyView != null) { savedInstanceState);
lv.setEmptyView(emptyView);
}
lv.setOnItemClickListener(this);
lv.setSaveEnabled(true);
lv.setItemsCanFocus(true);
lv.setTextFilterEnabled(true);
mListView = lv;
mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter);
if (savedInstanceState != null) { if (savedInstanceState != null) {
mApplications.mHasReceivedLoadEntries = mApplications.mHasReceivedLoadEntries =
savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false); savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false);
@@ -347,11 +320,10 @@ public class ManageApplications extends InstrumentedPreferenceFragment
mVolumeUuid, mVolumeUuid,
UserHandle.of(userId))); UserHandle.of(userId)));
} }
mListView.setAdapter(mApplications); mRecyclerView = mListContainer.findViewById(R.id.apps_list);
mListView.setRecyclerListener(mApplications); mRecyclerView.setLayoutManager(new LinearLayoutManager(
mListView.setFastScrollEnabled(isFastScrollEnabled()); getContext(), RecyclerView.VERTICAL, false /* reverseLayout */));
mRecyclerView.setAdapter(mApplications);
Utils.prepareCustomPreferencesList(container, mRootView, mListView, false);
} }
// We have to do this now because PreferenceFrameLayout looks at it // We have to do this now because PreferenceFrameLayout looks at it
@@ -492,6 +464,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem); outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
outState.putBoolean(EXTRA_HAS_ENTRIES, mApplications.mHasReceivedLoadEntries); outState.putBoolean(EXTRA_HAS_ENTRIES, mApplications.mHasReceivedLoadEntries);
outState.putBoolean(EXTRA_HAS_BRIDGE, mApplications.mHasReceivedBridgeCallback); outState.putBoolean(EXTRA_HAS_BRIDGE, mApplications.mHasReceivedBridgeCallback);
if (mApplications != null) {
mApplications.onSaveInstanceState(outState);
}
} }
@Override @Override
@@ -633,7 +608,6 @@ public class ManageApplications extends InstrumentedPreferenceFragment
case R.id.sort_order_alpha: case R.id.sort_order_alpha:
case R.id.sort_order_size: case R.id.sort_order_size:
mSortOrder = menuId; mSortOrder = menuId;
mListView.setFastScrollEnabled(isFastScrollEnabled());
if (mApplications != null) { if (mApplications != null) {
mApplications.rebuild(mSortOrder); mApplications.rebuild(mSortOrder);
} }
@@ -667,10 +641,11 @@ public class ManageApplications extends InstrumentedPreferenceFragment
} }
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onClick(View view) {
if (mApplications == null) { if (mApplications == null) {
return; return;
} }
final int position = mRecyclerView.getChildAdapterPosition(view);
if (mApplications.getApplicationCount() > position) { if (mApplications.getApplicationCount() > position) {
ApplicationsState.AppEntry entry = mApplications.getAppEntry(position); ApplicationsState.AppEntry entry = mApplications.getAppEntry(position);
@@ -757,7 +732,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE); mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE);
notifyDataSetChanged(); notifyDataSetChanged();
if (mFilterOptions.size() == 1) { if (mFilterOptions.size() == 1) {
if (DEBUG) Log.d(TAG, "Auto selecting filter " + filter); if (DEBUG) {
Log.d(TAG, "Auto selecting filter " + filter);
}
mManageApplications.mFilterSpinner.setSelection(0); mManageApplications.mFilterSpinner.setSelection(0);
mManageApplications.onItemSelected(null, null, 0, 0); mManageApplications.onItemSelected(null, null, 0, 0);
} }
@@ -777,7 +754,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
notifyDataSetChanged(); notifyDataSetChanged();
if (mManageApplications.mFilter == filter) { if (mManageApplications.mFilter == filter) {
if (mFilterOptions.size() > 0) { if (mFilterOptions.size() > 0) {
if (DEBUG) Log.d(TAG, "Auto selecting filter " + mFilterOptions.get(0)); if (DEBUG) {
Log.d(TAG, "Auto selecting filter " + mFilterOptions.get(0));
}
mManageApplications.mFilterSpinner.setSelection(0); mManageApplications.mFilterSpinner.setSelection(0);
mManageApplications.onItemSelected(null, null, 0, 0); mManageApplications.onItemSelected(null, null, 0, 0);
} }
@@ -795,38 +774,25 @@ public class ManageApplications extends InstrumentedPreferenceFragment
} }
} }
/* static class ApplicationsAdapter extends RecyclerView.Adapter<ApplicationViewHolder>
* Custom adapter implementation for the ListView implements ApplicationsState.Callbacks, AppStateBaseBridge.Callback {
* This adapter maintains a map for each displayed application and its properties
* An index value on each AppInfo object indicates the correct position or index
* in the list. If the list gets updated dynamically when the user is viewing the list of
* applications, we need to return the correct index of position. This is done by mapping
* the getId methods via the package name into the internal maps and indices.
* The order of applications in the list is mirrored in mAppLocalList
*/
static class ApplicationsAdapter extends BaseAdapter implements Filterable,
ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
AbsListView.RecyclerListener, SectionIndexer {
private static final SectionInfo[] EMPTY_SECTIONS = new SectionInfo[0]; private static final String STATE_LAST_SCROLL_INDEX = "state_last_scroll_index";
private static final int VIEW_TYPE_APP = 0;
private static final int VIEW_TYPE_EXTRA_VIEW = 1;
private final ApplicationsState mState; private final ApplicationsState mState;
private final ApplicationsState.Session mSession; private final ApplicationsState.Session mSession;
private final ManageApplications mManageApplications; private final ManageApplications mManageApplications;
private final Context mContext; private final Context mContext;
private final ArrayList<View> mActive = new ArrayList<>();
private final AppStateBaseBridge mExtraInfoBridge; private final AppStateBaseBridge mExtraInfoBridge;
private final Handler mBgHandler;
private final Handler mFgHandler;
private final LoadingViewController mLoadingViewController; private final LoadingViewController mLoadingViewController;
private AppFilterItem mAppFilter; private AppFilterItem mAppFilter;
private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
private ArrayList<ApplicationsState.AppEntry> mEntries; private ArrayList<ApplicationsState.AppEntry> mEntries;
private boolean mResumed; private boolean mResumed;
private int mLastSortMode = -1; private int mLastSortMode = -1;
private int mWhichSize = SIZE_TOTAL; private int mWhichSize = SIZE_TOTAL;
CharSequence mCurFilterPrefix;
private AppFilter mCompositeFilter; private AppFilter mCompositeFilter;
private boolean mHasReceivedLoadEntries; private boolean mHasReceivedLoadEntries;
private boolean mHasReceivedBridgeCallback; private boolean mHasReceivedBridgeCallback;
@@ -836,39 +802,11 @@ public class ManageApplications extends InstrumentedPreferenceFragment
// fragment is paused. We need this special handling because app entries are added gradually // fragment is paused. We need this special handling because app entries are added gradually
// when we rebuild the list after the user made some changes, like uninstalling an app. // when we rebuild the list after the user made some changes, like uninstalling an app.
private int mLastIndex = -1; private int mLastIndex = -1;
private int mLastTop;
private AlphabeticIndex.ImmutableIndex<Locale> mIndex;
private SectionInfo[] mSections = EMPTY_SECTIONS;
private int[] mPositionToSectionIndex;
private Filter mFilter = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
ArrayList<ApplicationsState.AppEntry> entries
= applyPrefixFilter(constraint, mBaseEntries);
FilterResults fr = new FilterResults();
fr.values = entries;
fr.count = entries.size();
return fr;
}
@Override
@SuppressWarnings("unchecked")
protected void publishResults(CharSequence constraint, FilterResults results) {
mCurFilterPrefix = constraint;
mEntries = (ArrayList<ApplicationsState.AppEntry>) results.values;
rebuildSections();
notifyDataSetChanged();
}
};
public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications, public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications,
AppFilterItem appFilter) { AppFilterItem appFilter, Bundle savedInstanceState) {
setHasStableIds(true);
mState = state; mState = state;
mFgHandler = new Handler();
mBgHandler = new Handler(mState.getBackgroundLooper());
mSession = state.newSession(this); mSession = state.newSession(this);
mManageApplications = manageApplications; mManageApplications = manageApplications;
mLoadingViewController = new LoadingViewController( mLoadingViewController = new LoadingViewController(
@@ -893,6 +831,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
} else { } else {
mExtraInfoBridge = null; mExtraInfoBridge = null;
} }
if (savedInstanceState != null) {
mLastIndex = savedInstanceState.getInt(STATE_LAST_SCROLL_INDEX);
}
} }
public void setCompositeFilter(AppFilter compositeFilter) { public void setCompositeFilter(AppFilter compositeFilter) {
@@ -907,9 +848,11 @@ public class ManageApplications extends InstrumentedPreferenceFragment
public void setExtraViewController(FileViewHolderController extraViewController) { public void setExtraViewController(FileViewHolderController extraViewController) {
mExtraViewController = extraViewController; mExtraViewController = extraViewController;
mBgHandler.post(() -> { // Start to query extra view's stats on background, and once done post result to main
// thread.
ThreadUtils.postOnBackgroundThread(() -> {
mExtraViewController.queryStats(); mExtraViewController.queryStats();
mFgHandler.post(() -> { ThreadUtils.postOnMainThread(() -> {
onExtraViewCompleted(); onExtraViewCompleted();
}); });
}); });
@@ -938,11 +881,13 @@ public class ManageApplications extends InstrumentedPreferenceFragment
mExtraInfoBridge.pause(); mExtraInfoBridge.pause();
} }
} }
}
public void onSaveInstanceState(Bundle outState) {
// Record the current scroll position before pausing. // Record the current scroll position before pausing.
mLastIndex = mManageApplications.mListView.getFirstVisiblePosition(); final LinearLayoutManager layoutManager =
View v = mManageApplications.mListView.getChildAt(0); (LinearLayoutManager) mManageApplications.mRecyclerView.getLayoutManager();
mLastTop = outState.putInt(STATE_LAST_SCROLL_INDEX, layoutManager.findFirstVisibleItemPosition());
(v == null) ? 0 : (v.getTop() - mManageApplications.mListView.getPaddingTop());
} }
public void release() { public void release() {
@@ -960,6 +905,21 @@ public class ManageApplications extends InstrumentedPreferenceFragment
rebuild(); rebuild();
} }
@Override
public ApplicationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = ApplicationViewHolder.newView(
LayoutInflater.from(parent.getContext()), parent);
return new ApplicationViewHolder(view);
}
@Override
public int getItemViewType(int position) {
boolean isLastItem = (getItemCount() - 1) == position;
return hasExtraView() && isLastItem
? VIEW_TYPE_EXTRA_VIEW
: VIEW_TYPE_APP;
}
public void rebuild() { public void rebuild() {
if (!mHasReceivedLoadEntries if (!mHasReceivedLoadEntries
|| (mExtraInfoBridge != null && !mHasReceivedBridgeCallback)) { || (mExtraInfoBridge != null && !mHasReceivedBridgeCallback)) {
@@ -1008,11 +968,11 @@ public class ManageApplications extends InstrumentedPreferenceFragment
filterObj = new CompoundFilter(filterObj, ApplicationsState.FILTER_NOT_HIDE); filterObj = new CompoundFilter(filterObj, ApplicationsState.FILTER_NOT_HIDE);
AppFilter finalFilterObj = filterObj; AppFilter finalFilterObj = filterObj;
mBgHandler.post(() -> { ThreadUtils.postOnBackgroundThread(() -> {
final ArrayList<AppEntry> entries = mSession.rebuild(finalFilterObj, final ArrayList<AppEntry> entries = mSession.rebuild(finalFilterObj,
comparatorObj, false); comparatorObj, false);
if (entries != null) { if (entries != null) {
mFgHandler.post(() -> onRebuildComplete(entries)); ThreadUtils.postOnMainThread(() -> onRebuildComplete(entries));
} }
}); });
} }
@@ -1031,8 +991,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment
ArrayList<ApplicationsState.AppEntry> entries) { ArrayList<ApplicationsState.AppEntry> entries) {
int size = entries.size(); int size = entries.size();
// returnList will not have more entries than entries // returnList will not have more entries than entries
ArrayList<ApplicationsState.AppEntry> returnEntries = new ArrayList<ApplicationsState.AppEntry> returnEntries = new ArrayList<>(size);
ArrayList<ApplicationsState.AppEntry>(size);
// assume appinfo of same package but different users are grouped together // assume appinfo of same package but different users are grouped together
PackageItemInfo lastInfo = null; PackageItemInfo lastInfo = null;
@@ -1055,21 +1014,19 @@ public class ManageApplications extends InstrumentedPreferenceFragment
filterType == FILTER_APPS_POWER_WHITELIST_ALL) { filterType == FILTER_APPS_POWER_WHITELIST_ALL) {
entries = removeDuplicateIgnoringUser(entries); entries = removeDuplicateIgnoringUser(entries);
} }
mBaseEntries = entries; mEntries = entries;
if (mBaseEntries != null) {
mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
rebuildSections();
} else {
mEntries = null;
mSections = EMPTY_SECTIONS;
mPositionToSectionIndex = null;
}
notifyDataSetChanged(); notifyDataSetChanged();
if (getItemCount() == 0) {
mManageApplications.mRecyclerView.setVisibility(View.GONE);
mManageApplications.mEmptyView.setVisibility(View.VISIBLE);
} else {
mManageApplications.mEmptyView.setVisibility(View.GONE);
mManageApplications.mRecyclerView.setVisibility(View.VISIBLE);
}
// Restore the last scroll position if the number of entries added so far is bigger than // Restore the last scroll position if the number of entries added so far is bigger than
// it. // it.
if (mLastIndex != -1 && getCount() > mLastIndex) { if (mLastIndex != -1 && getItemCount() > mLastIndex) {
mManageApplications.mListView.setSelectionFromTop(mLastIndex, mLastTop); mManageApplications.mRecyclerView.getLayoutManager().scrollToPosition(mLastIndex);
mLastIndex = -1; mLastIndex = -1;
} }
@@ -1086,45 +1043,6 @@ public class ManageApplications extends InstrumentedPreferenceFragment
mManageApplications.setHasInstant(mState.haveInstantApps()); mManageApplications.setHasInstant(mState.haveInstantApps());
} }
private void rebuildSections() {
if (mEntries != null && mManageApplications.mListView.isFastScrollEnabled()) {
// Rebuild sections
if (mIndex == null) {
LocaleList locales = mContext.getResources().getConfiguration().getLocales();
if (locales.size() == 0) {
locales = new LocaleList(Locale.ENGLISH);
}
AlphabeticIndex<Locale> index = new AlphabeticIndex(locales.get(0));
int localeCount = locales.size();
for (int i = 1; i < localeCount; i++) {
index.addLabels(locales.get(i));
}
// Ensure we always have some base English locale buckets
index.addLabels(Locale.ENGLISH);
mIndex = index.buildImmutableIndex();
}
ArrayList<SectionInfo> sections = new ArrayList<>();
int lastSecId = -1;
int totalEntries = mEntries.size();
mPositionToSectionIndex = new int[totalEntries];
for (int pos = 0; pos < totalEntries; pos++) {
String label = mEntries.get(pos).label;
int secId = mIndex.getBucketIndex(TextUtils.isEmpty(label) ? "" : label);
if (secId != lastSecId) {
lastSecId = secId;
sections.add(new SectionInfo(mIndex.getBucket(secId).getLabel(), pos));
}
mPositionToSectionIndex[pos] = sections.size() - 1;
}
mSections = sections.toArray(EMPTY_SECTIONS);
} else {
mSections = EMPTY_SECTIONS;
mPositionToSectionIndex = null;
}
}
@VisibleForTesting @VisibleForTesting
void updateLoading() { void updateLoading() {
final boolean appLoaded = mHasReceivedLoadEntries && mSession.getAllApps().size() != 0; final boolean appLoaded = mHasReceivedLoadEntries && mSession.getAllApps().size() != 0;
@@ -1135,26 +1053,6 @@ public class ManageApplications extends InstrumentedPreferenceFragment
} }
} }
ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix,
ArrayList<ApplicationsState.AppEntry> origEntries) {
if (prefix == null || prefix.length() == 0) {
return origEntries;
} else {
String prefixStr = ApplicationsState.normalize(prefix.toString());
final String spacePrefixStr = " " + prefixStr;
ArrayList<ApplicationsState.AppEntry> newEntries
= new ArrayList<ApplicationsState.AppEntry>();
for (int i = 0; i < origEntries.size(); i++) {
ApplicationsState.AppEntry entry = origEntries.get(i);
String nlabel = entry.getNormalizedLabel();
if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) {
newEntries.add(entry);
}
}
return newEntries;
}
}
@Override @Override
public void onExtraInfoUpdated() { public void onExtraInfoUpdated() {
mHasReceivedBridgeCallback = true; mHasReceivedBridgeCallback = true;
@@ -1186,29 +1084,27 @@ public class ManageApplications extends InstrumentedPreferenceFragment
@Override @Override
public void onPackageSizeChanged(String packageName) { public void onPackageSizeChanged(String packageName) {
for (int i = 0; i < mActive.size(); i++) { if (mEntries == null) {
AppViewHolder holder = (AppViewHolder) mActive.get(i).getTag(); return;
if (holder == null || holder.entry == null) { }
final int size = mEntries.size();
for (int i = 0; i < size; i++) {
final AppEntry entry = mEntries.get(i);
final ApplicationInfo info = entry.info;
if (info == null && !TextUtils.equals(packageName, info.packageName)) {
continue; continue;
} }
ApplicationInfo info = holder.entry.info; if (TextUtils.equals(mManageApplications.mCurrentPkgName, info.packageName)) {
if (info == null) { // We got the size information for the last app the
continue; // user viewed, and are sorting by size... they may
} // have cleared data, so we immediately want to resort
if (holder.entry.info.packageName.equals(packageName)) { // the list with the new size to reflect it to the user.
synchronized (holder.entry) { rebuild();
updateSummary(holder);
}
if (holder.entry.info.packageName.equals(mManageApplications.mCurrentPkgName)
&& mLastSortMode == R.id.sort_order_size) {
// We got the size information for the last app the
// user viewed, and are sorting by size... they may
// have cleared data, so we immediately want to resort
// the list with the new size to reflect it to the user.
rebuild();
}
return; return;
} else {
notifyItemChanged(i);
} }
} }
} }
@@ -1227,46 +1123,30 @@ public class ManageApplications extends InstrumentedPreferenceFragment
} }
public void onExtraViewCompleted() { public void onExtraViewCompleted() {
int size = mActive.size(); if (!hasExtraView()) {
// If we have no elements, don't do anything.
if (size < 1) {
return; return;
} }
AppViewHolder holder = (AppViewHolder) mActive.get(size - 1).getTag(); // Update last item - this is assumed to be the extra view.
notifyItemChanged(getItemCount() - 1);
// HACK: The extra view has no AppEntry -- and should be the only element without one.
// Thus, if the last active element has no AppEntry, it is the extra view.
if (holder == null || holder.entry != null) {
return;
}
mExtraViewController.setupView(holder);
} }
public int getCount() { @Override
public int getItemCount() {
if (mEntries == null) { if (mEntries == null) {
return 0; return 0;
} }
int extraViewAddition = return mEntries.size() + (hasExtraView() ? 1 : 0);
(mExtraViewController != null && mExtraViewController.shouldShow()) ? 1 : 0;
return mEntries.size() + extraViewAddition;
} }
public int getApplicationCount() { public int getApplicationCount() {
return mEntries != null ? mEntries.size() : 0; return mEntries != null ? mEntries.size() : 0;
} }
public Object getItem(int position) { public AppEntry getAppEntry(int position) {
if (position == mEntries.size()) {
return mExtraViewController;
}
return mEntries.get(position);
}
public ApplicationsState.AppEntry getAppEntry(int position) {
return mEntries.get(position); return mEntries.get(position);
} }
@Override
public long getItemId(int position) { public long getItemId(int position) {
if (position == mEntries.size()) { if (position == mEntries.size()) {
return -1; return -1;
@@ -1274,142 +1154,76 @@ public class ManageApplications extends InstrumentedPreferenceFragment
return mEntries.get(position).id; return mEntries.get(position).id;
} }
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) { public boolean isEnabled(int position) {
if (position == mEntries.size() && mExtraViewController != null && if (getItemViewType(position) == VIEW_TYPE_EXTRA_VIEW
mExtraViewController.shouldShow()) { || mManageApplications.mListType != LIST_TYPE_HIGH_POWER) {
return true;
}
if (mManageApplications.mListType != LIST_TYPE_HIGH_POWER) {
return true; return true;
} }
ApplicationsState.AppEntry entry = mEntries.get(position); ApplicationsState.AppEntry entry = mEntries.get(position);
return !PowerWhitelistBackend.getInstance().isSysWhitelisted(entry.info.packageName); return !PowerWhitelistBackend.getInstance().isSysWhitelisted(entry.info.packageName);
} }
public View getView(int position, View convertView, ViewGroup parent) { @Override
// A ViewHolder keeps references to children views to avoid unnecessary calls public void onBindViewHolder(ApplicationViewHolder holder, int position) {
// to findViewById() on each row.
AppViewHolder holder = AppViewHolder.createOrRecycle(mManageApplications.mInflater,
convertView);
convertView = holder.rootView;
// Handle the extra view if it is the last entry.
if (mEntries != null && mExtraViewController != null && position == mEntries.size()) { if (mEntries != null && mExtraViewController != null && position == mEntries.size()) {
// set up view for extra view controller
mExtraViewController.setupView(holder); mExtraViewController.setupView(holder);
convertView.setEnabled(true);
} else { } else {
// Bind the data efficiently with the holder // Bind the data efficiently with the holder
ApplicationsState.AppEntry entry = mEntries.get(position); ApplicationsState.AppEntry entry = mEntries.get(position);
synchronized (entry) { synchronized (entry) {
holder.entry = entry; holder.setTitle(entry.label);
if (entry.label != null) {
holder.appName.setText(entry.label);
}
mState.ensureIcon(entry); mState.ensureIcon(entry);
if (entry.icon != null) { holder.setIcon(entry.icon);
holder.appIcon.setImageDrawable(entry.icon); updateSummary(holder, entry);
} holder.updateDisableView(entry.info);
updateSummary(holder);
updateDisableView(holder.disabled, entry.info);
} }
convertView.setEnabled(isEnabled(position)); holder.setEnabled(isEnabled(position));
} }
holder.itemView.setOnClickListener(mManageApplications);
mActive.remove(convertView);
mActive.add(convertView);
return convertView;
} }
@VisibleForTesting private void updateSummary(ApplicationViewHolder holder, AppEntry entry) {
void updateDisableView(TextView view, ApplicationInfo info) {
if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
view.setVisibility(View.VISIBLE);
view.setText(R.string.not_installed);
} else if (!info.enabled || info.enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
view.setVisibility(View.VISIBLE);
view.setText(R.string.disabled);
} else {
view.setVisibility(View.GONE);
}
}
private void updateSummary(AppViewHolder holder) {
switch (mManageApplications.mListType) { switch (mManageApplications.mListType) {
case LIST_TYPE_NOTIFICATION: case LIST_TYPE_NOTIFICATION:
if (holder.entry.extraInfo != null) { if (entry.extraInfo != null) {
holder.summary.setText(InstalledAppDetails.getNotificationSummary( holder.setSummary(InstalledAppDetails.getNotificationSummary(
(AppRow) holder.entry.extraInfo, mContext)); (AppRow) entry.extraInfo, mContext));
} else { } else {
holder.summary.setText(null); holder.setSummary(null);
} }
break; break;
case LIST_TYPE_USAGE_ACCESS: case LIST_TYPE_USAGE_ACCESS:
if (holder.entry.extraInfo != null) { if (entry.extraInfo != null) {
holder.summary.setText((new UsageState((PermissionState) holder.entry holder.setSummary(
.extraInfo)).isPermissible() (new UsageState((PermissionState) entry.extraInfo)).isPermissible()
? R.string.app_permission_summary_allowed ? R.string.app_permission_summary_allowed
: R.string.app_permission_summary_not_allowed); : R.string.app_permission_summary_not_allowed);
} else { } else {
holder.summary.setText(null); holder.setSummary(null);
} }
break; break;
case LIST_TYPE_HIGH_POWER: case LIST_TYPE_HIGH_POWER:
holder.summary.setText(HighPowerDetail.getSummary(mContext, holder.entry)); holder.setSummary(HighPowerDetail.getSummary(mContext, entry));
break; break;
case LIST_TYPE_OVERLAY: case LIST_TYPE_OVERLAY:
holder.summary.setText(DrawOverlayDetails.getSummary(mContext, holder.entry)); holder.setSummary(DrawOverlayDetails.getSummary(mContext, entry));
break; break;
case LIST_TYPE_WRITE_SETTINGS: case LIST_TYPE_WRITE_SETTINGS:
holder.summary.setText(WriteSettingsDetails.getSummary(mContext, holder.setSummary(WriteSettingsDetails.getSummary(mContext, entry));
holder.entry));
break; break;
case LIST_TYPE_MANAGE_SOURCES: case LIST_TYPE_MANAGE_SOURCES:
holder.summary.setText(ExternalSourcesDetails.getPreferenceSummary(mContext, holder.setSummary(ExternalSourcesDetails.getPreferenceSummary(mContext, entry));
holder.entry));
break; break;
default: default:
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize); holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize);
break; break;
} }
} }
@Override private boolean hasExtraView() {
public Filter getFilter() { return mExtraViewController != null
return mFilter; && mExtraViewController.shouldShow();
}
@Override
public void onMovedToScrapHeap(View view) {
mActive.remove(view);
}
@Override
public Object[] getSections() {
return mSections;
}
@Override
public int getPositionForSection(int sectionIndex) {
return mSections[sectionIndex].position;
}
@Override
public int getSectionForPosition(int position) {
return mPositionToSectionIndex[position];
} }
} }
@@ -1439,21 +1253,6 @@ public class ManageApplications extends InstrumentedPreferenceFragment
} }
} }
private static class SectionInfo {
final String label;
final int position;
public SectionInfo(String label, int position) {
this.label = label;
this.position = position;
}
@Override
public String toString() {
return label;
}
}
public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
= new SummaryLoader.SummaryProviderFactory() { = new SummaryLoader.SummaryProviderFactory() {
@Override @Override

View File

@@ -72,11 +72,11 @@ public class MusicViewHolderController implements FileViewHolderController {
} }
@Override @Override
public void setupView(AppViewHolder holder) { public void setupView(ApplicationViewHolder holder) {
holder.appIcon.setImageDrawable( holder.setIcon(
new InsetDrawable(mContext.getDrawable(R.drawable.ic_headset_24dp), INSET_SIZE)); new InsetDrawable(mContext.getDrawable(R.drawable.ic_headset_24dp), INSET_SIZE));
holder.appName.setText(mContext.getText(R.string.audio_files_title)); holder.setTitle(mContext.getText(R.string.audio_files_title));
holder.summary.setText(Formatter.formatFileSize(mContext, mMusicSize)); holder.setSummary(Formatter.formatFileSize(mContext, mMusicSize));
} }
@Override @Override

View File

@@ -71,11 +71,11 @@ public class PhotosViewHolderController implements FileViewHolderController {
} }
@Override @Override
public void setupView(AppViewHolder holder) { public void setupView(ApplicationViewHolder holder) {
holder.appIcon.setImageDrawable( holder.setIcon(
new InsetDrawable(mContext.getDrawable(R.drawable.ic_photo_library), INSET_SIZE)); new InsetDrawable(mContext.getDrawable(R.drawable.ic_photo_library), INSET_SIZE));
holder.appName.setText(mContext.getText(R.string.storage_detail_images)); holder.setTitle(mContext.getText(R.string.storage_detail_images));
holder.summary.setText(Formatter.formatFileSize(mContext, mFilesSize)); holder.setSummary(Formatter.formatFileSize(mContext, mFilesSize));
} }
@Override @Override

View File

@@ -32,7 +32,6 @@ import android.widget.ArrayAdapter;
import android.widget.ListView; import android.widget.ListView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.applications.manageapplications.AppViewHolder;
import java.text.Collator; import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.settings.applications.manageapplications; package com.android.settings.development;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -57,24 +56,4 @@ public class AppViewHolder {
return (AppViewHolder)convertView.getTag(); return (AppViewHolder)convertView.getTag();
} }
} }
void updateSizeText(CharSequence invalidSizeStr, int whichSize) {
if (ManageApplications.DEBUG) Log.i(ManageApplications.TAG, "updateSizeText of "
+ entry.label + " " + entry + ": " + entry.sizeStr);
if (entry.sizeStr != null) {
switch (whichSize) {
case ManageApplications.SIZE_INTERNAL:
summary.setText(entry.internalSizeStr);
break;
case ManageApplications.SIZE_EXTERNAL:
summary.setText(entry.externalSizeStr);
break;
default:
summary.setText(entry.sizeStr);
break;
}
} else if (entry.size == ApplicationsState.SIZE_INVALID) {
summary.setText(invalidSizeStr);
}
}
} }

View File

@@ -16,8 +16,6 @@
package com.android.settings.display; package com.android.settings.display;
import com.android.settings.R;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@@ -30,6 +28,8 @@ import android.widget.ArrayAdapter;
import android.widget.GridView; import android.widget.GridView;
import android.widget.ImageView; import android.widget.ImageView;
import com.android.settings.R;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2017 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.applications.manageapplications;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.applications.ApplicationsState;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class ApplicationViewHolderTest {
private Context mContext;
private View mView;
private ApplicationViewHolder mHolder;
@Before
public void seUp() {
mContext = RuntimeEnvironment.application;
mView = ApplicationViewHolder.newView(LayoutInflater.from(mContext),
new FrameLayout(mContext));
mHolder = new ApplicationViewHolder(mView);
}
@Test
public void updateDisableView_appDisabledUntilUsed_shouldSetDisabled() {
final ApplicationInfo info = new ApplicationInfo();
info.flags = ApplicationInfo.FLAG_INSTALLED;
info.enabled = true;
info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
mHolder.updateDisableView(info);
assertThat(mHolder.mDisabled.getText()).isEqualTo(mContext.getText(R.string.disabled));
}
@Test
public void setSummaries() {
mHolder.setSummary("hello");
assertThat(mHolder.mSummary.getText()).isEqualTo("hello");
mHolder.setSummary(R.string.disabled);
assertThat(mHolder.mSummary.getText()).isEqualTo(mContext.getText(R.string.disabled));
}
@Test
public void updateSize() {
final String invalidStr = "invalid";
final ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class);
entry.internalSizeStr = "internal";
entry.externalSizeStr = "external";
entry.sizeStr = entry.internalSizeStr;
mHolder.updateSizeText(entry, invalidStr, ManageApplications.SIZE_INTERNAL);
assertThat(mHolder.mSummary.getText()).isEqualTo(entry.internalSizeStr);
mHolder.updateSizeText(entry, invalidStr, ManageApplications.SIZE_EXTERNAL);
assertThat(mHolder.mSummary.getText()).isEqualTo(entry.externalSizeStr);
entry.sizeStr = null;
entry.size = ApplicationsState.SIZE_INVALID;
mHolder.updateSizeText(entry, invalidStr, ManageApplications.SIZE_EXTERNAL);
assertThat(mHolder.mSummary.getText()).isEqualTo(invalidStr);
}
}

View File

@@ -31,19 +31,16 @@ import static org.mockito.Mockito.when;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.pm.ApplicationInfo; import android.os.Bundle;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settings.testutils.shadow.SettingsShadowResources;
@@ -102,30 +99,6 @@ public class ManageApplicationsTest {
ReflectionHelpers.setField(mFragment, "mLifecycle", new Lifecycle()); ReflectionHelpers.setField(mFragment, "mLifecycle", new Lifecycle());
} }
@Test
public void launchFragment() {
SettingsRobolectricTestRunner.startSettingsFragment(
mFragment, Settings.ManageApplicationsActivity.class);
}
@Test
public void updateDisableView_appDisabledUntilUsed_shouldSetDisabled() {
final TextView view = mock(TextView.class);
final ApplicationInfo info = new ApplicationInfo();
info.flags = ApplicationInfo.FLAG_INSTALLED;
info.enabled = true;
info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
ManageApplications fragment = mock(ManageApplications.class);
when(fragment.getActivity()).thenReturn(mock(Activity.class));
final ManageApplications.ApplicationsAdapter adapter =
new ManageApplications.ApplicationsAdapter(mState, fragment,
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL));
adapter.updateDisableView(view, info);
verify(view).setText(R.string.disabled);
}
@Test @Test
public void updateMenu_mainListType_showAppReset() { public void updateMenu_mainListType_showAppReset() {
setUpOptionMenus(); setUpOptionMenus();
@@ -170,14 +143,12 @@ public class ManageApplicationsTest {
ReflectionHelpers.setField(fragment, "mLoadingContainer", mock(View.class)); ReflectionHelpers.setField(fragment, "mLoadingContainer", mock(View.class));
ReflectionHelpers.setField(fragment, "mListContainer", mock(View.class)); ReflectionHelpers.setField(fragment, "mListContainer", mock(View.class));
when(fragment.getActivity()).thenReturn(mock(Activity.class)); when(fragment.getActivity()).thenReturn(mock(Activity.class));
final Handler handler = mock(Handler.class);
final ManageApplications.ApplicationsAdapter adapter = final ManageApplications.ApplicationsAdapter adapter =
spy(new ManageApplications.ApplicationsAdapter(mState, fragment, spy(new ManageApplications.ApplicationsAdapter(mState, fragment,
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL))); AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle()));
final LoadingViewController loadingViewController = final LoadingViewController loadingViewController =
mock(LoadingViewController.class); mock(LoadingViewController.class);
ReflectionHelpers.setField(adapter, "mLoadingViewController", loadingViewController); ReflectionHelpers.setField(adapter, "mLoadingViewController", loadingViewController);
ReflectionHelpers.setField(adapter, "mFgHandler", handler);
// app loading completed // app loading completed
ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", true); ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", true);
@@ -196,15 +167,12 @@ public class ManageApplicationsTest {
ReflectionHelpers.setField(fragment, "mLoadingContainer", mock(View.class)); ReflectionHelpers.setField(fragment, "mLoadingContainer", mock(View.class));
ReflectionHelpers.setField(fragment, "mListContainer", mock(View.class)); ReflectionHelpers.setField(fragment, "mListContainer", mock(View.class));
when(fragment.getActivity()).thenReturn(mock(Activity.class)); when(fragment.getActivity()).thenReturn(mock(Activity.class));
final Handler handler = mock(Handler.class);
final ManageApplications.ApplicationsAdapter adapter = final ManageApplications.ApplicationsAdapter adapter =
spy(new ManageApplications.ApplicationsAdapter(mState, fragment, spy(new ManageApplications.ApplicationsAdapter(mState, fragment,
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL))); AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle()));
final LoadingViewController loadingViewController = final LoadingViewController loadingViewController =
mock(LoadingViewController.class); mock(LoadingViewController.class);
ReflectionHelpers.setField(adapter, "mLoadingViewController", loadingViewController); ReflectionHelpers.setField(adapter, "mLoadingViewController", loadingViewController);
ReflectionHelpers.setField(adapter, "mFgHandler", handler);
// app loading not yet completed // app loading not yet completed
ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", false); ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", false);
@@ -218,6 +186,10 @@ public class ManageApplicationsTest {
public void onRebuildComplete_shouldHideLoadingView() { public void onRebuildComplete_shouldHideLoadingView() {
final Context context = RuntimeEnvironment.application; final Context context = RuntimeEnvironment.application;
final ManageApplications fragment = mock(ManageApplications.class); final ManageApplications fragment = mock(ManageApplications.class);
final RecyclerView recyclerView = mock(RecyclerView.class);
final View emptyView = mock(View.class);
ReflectionHelpers.setField(fragment, "mRecyclerView", recyclerView);
ReflectionHelpers.setField(fragment, "mEmptyView", emptyView);
final View loadingContainer = mock(View.class); final View loadingContainer = mock(View.class);
when(loadingContainer.getContext()).thenReturn(context); when(loadingContainer.getContext()).thenReturn(context);
final View listContainer = mock(View.class); final View listContainer = mock(View.class);
@@ -226,14 +198,12 @@ public class ManageApplicationsTest {
ReflectionHelpers.setField(fragment, "mLoadingContainer", loadingContainer); ReflectionHelpers.setField(fragment, "mLoadingContainer", loadingContainer);
ReflectionHelpers.setField(fragment, "mListContainer", listContainer); ReflectionHelpers.setField(fragment, "mListContainer", listContainer);
when(fragment.getActivity()).thenReturn(mock(Activity.class)); when(fragment.getActivity()).thenReturn(mock(Activity.class));
final Handler handler = mock(Handler.class);
final ManageApplications.ApplicationsAdapter adapter = final ManageApplications.ApplicationsAdapter adapter =
spy(new ManageApplications.ApplicationsAdapter(mState, fragment, spy(new ManageApplications.ApplicationsAdapter(mState, fragment,
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL))); AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle()));
final LoadingViewController loadingViewController = final LoadingViewController loadingViewController =
mock(LoadingViewController.class); mock(LoadingViewController.class);
ReflectionHelpers.setField(adapter, "mLoadingViewController", loadingViewController); ReflectionHelpers.setField(adapter, "mLoadingViewController", loadingViewController);
ReflectionHelpers.setField(adapter, "mFgHandler", handler);
ReflectionHelpers.setField(adapter, "mAppFilter", ReflectionHelpers.setField(adapter, "mAppFilter",
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL)); AppFilterRegistry.getInstance().get(FILTER_APPS_ALL));

View File

@@ -28,11 +28,12 @@ import android.os.UserHandle;
import android.os.storage.VolumeInfo; import android.os.storage.VolumeInfo;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.applications.StorageStatsSource; import com.android.settingslib.applications.StorageStatsSource;
import com.android.settingslib.deviceinfo.StorageVolumeProvider;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -51,14 +52,13 @@ public class MusicViewHolderControllerTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Fragment mFragment; private Fragment mFragment;
@Mock @Mock
private StorageVolumeProvider mSvp;
@Mock
private StorageStatsSource mSource; private StorageStatsSource mSource;
private Context mContext; private Context mContext;
private MusicViewHolderController mController; private MusicViewHolderController mController;
private VolumeInfo mVolume; private VolumeInfo mVolume;
private AppViewHolder mHolder; private View mView;
private ApplicationViewHolder mHolder;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@@ -69,25 +69,26 @@ public class MusicViewHolderControllerTest {
new UserHandle(0)); new UserHandle(0));
LayoutInflater inflater = LayoutInflater.from(mContext); LayoutInflater inflater = LayoutInflater.from(mContext);
mHolder = AppViewHolder.createOrRecycle(inflater, null); mView = ApplicationViewHolder.newView(inflater, new FrameLayout(mContext));
mHolder = new ApplicationViewHolder(mView);
} }
@Test @Test
public void storageShouldBeZeroBytesIfQueriedBeforeStorageQueryFinishes() { public void storageShouldBeZeroBytesIfQueriedBeforeStorageQueryFinishes() {
mController.setupView(mHolder); mController.setupView(mHolder);
assertThat(mHolder.summary.getText().toString()).isEqualTo("0.00 B"); assertThat(mHolder.mSummary.getText().toString()).isEqualTo("0.00 B");
} }
@Test @Test
public void storageShouldRepresentStorageStatsQuery() throws Exception { public void storageShouldRepresentStorageStatsQuery() throws Exception {
when(mSource.getExternalStorageStats(nullable(String.class), nullable(UserHandle.class))).thenReturn( when(mSource.getExternalStorageStats(nullable(String.class), nullable(UserHandle.class)))
new StorageStatsSource.ExternalStorageStats(1, 1, 0, 0, 0)); .thenReturn(new StorageStatsSource.ExternalStorageStats(1, 1, 0, 0, 0));
mController.queryStats(); mController.queryStats();
mController.setupView(mHolder); mController.setupView(mHolder);
assertThat(mHolder.summary.getText().toString()).isEqualTo("1.00 B"); assertThat(mHolder.mSummary.getText().toString()).isEqualTo("1.00 B");
} }
@Test @Test

View File

@@ -27,11 +27,12 @@ import android.content.Intent;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.storage.VolumeInfo; import android.os.storage.VolumeInfo;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.applications.StorageStatsSource; import com.android.settingslib.applications.StorageStatsSource;
import com.android.settingslib.deviceinfo.StorageVolumeProvider;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -48,14 +49,14 @@ import org.robolectric.annotation.Config;
public class PhotosViewHolderControllerTest { public class PhotosViewHolderControllerTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Fragment mFragment; private Fragment mFragment;
@Mock private StorageVolumeProvider mSvp;
@Mock private StorageStatsSource mSource; @Mock private StorageStatsSource mSource;
private Context mContext; private Context mContext;
private PhotosViewHolderController mController; private PhotosViewHolderController mController;
private VolumeInfo mVolume; private VolumeInfo mVolume;
private AppViewHolder mHolder; private View mView;
private ApplicationViewHolder mHolder;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@@ -66,15 +67,16 @@ public class PhotosViewHolderControllerTest {
new PhotosViewHolderController( new PhotosViewHolderController(
mContext, mSource, mVolume.fsUuid, new UserHandle(0)); mContext, mSource, mVolume.fsUuid, new UserHandle(0));
LayoutInflater inflater = LayoutInflater.from(mContext); final LayoutInflater inflater = LayoutInflater.from(mContext);
mHolder = AppViewHolder.createOrRecycle(inflater, null); mView = ApplicationViewHolder.newView(inflater, new FrameLayout(mContext));
mHolder = new ApplicationViewHolder(mView);
} }
@Test @Test
public void storageShouldBeZeroBytesIfQueriedBeforeStorageQueryFinishes() { public void storageShouldBeZeroBytesIfQueriedBeforeStorageQueryFinishes() {
mController.setupView(mHolder); mController.setupView(mHolder);
assertThat(mHolder.summary.getText().toString()).isEqualTo("0.00 B"); assertThat(mHolder.mSummary.getText().toString()).isEqualTo("0.00 B");
} }
@Test @Test
@@ -85,7 +87,7 @@ public class PhotosViewHolderControllerTest {
mController.queryStats(); mController.queryStats();
mController.setupView(mHolder); mController.setupView(mHolder);
assertThat(mHolder.summary.getText().toString()).isEqualTo("11.00 B"); assertThat(mHolder.mSummary.getText().toString()).isEqualTo("11.00 B");
} }
@Test @Test