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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout android:id="@+id/list_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:id="@+id/list_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone">
<FrameLayout
android:id="@+id/pinned_header"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1">
<ListView android:id="@android:id/list"
android:drawSelectorOnTop="false"
<android.support.v7.widget.RecyclerView
android:id="@+id/apps_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbarStyle="@integer/preference_scrollbar_style" />
settings:fastScrollEnabled="true"
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/no_applications"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/no_applications"
android:textAppearance="?android:attr/textAppearanceLarge"
android:visibility="invisible" />
</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.
*
* @param holder The holder to use to initialize.
*/
void setupView(AppViewHolder holder);
void setupView(ApplicationViewHolder holder);
/**
* Handles the behavior when the view is clicked.
*
* @param fragment Fragment where the click originated.
*/
void onClick(Fragment fragment);

View File

@@ -40,16 +40,14 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.icu.text.AlphabeticIndex;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.LocaleList;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.PreferenceFrameLayout;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -59,19 +57,11 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.SectionIndexer;
import android.widget.Spinner;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
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.WriteSettingsActivity;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
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.VolumeFilter;
import com.android.settingslib.applications.StorageStatsSource;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.wrapper.PackageManagerWrapper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
import java.util.Set;
/**
@@ -138,7 +127,7 @@ import java.util.Set;
* intent.
*/
public class ManageApplications extends InstrumentedPreferenceFragment
implements OnItemClickListener, OnItemSelectedListener {
implements View.OnClickListener, OnItemSelectedListener {
static final String TAG = "ManageApplications";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -185,23 +174,17 @@ public class ManageApplications extends InstrumentedPreferenceFragment
private ApplicationsState mApplicationsState;
public int mListType;
public AppFilterItem mFilter;
public ApplicationsAdapter mApplications;
private AppFilterItem mFilter;
private ApplicationsAdapter mApplications;
private View mLoadingContainer;
private View mListContainer;
// ListView used to display list
private ListView mListView;
private RecyclerView mRecyclerView;
// Size resource used for packages whose size computation failed for some reason
CharSequence mInvalidSizeStr;
// layout inflater object used to inflate views
private LayoutInflater mInflater;
private String mCurrentPkgName;
private int mCurrentUid;
@@ -234,6 +217,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment
private int mStorageType;
private boolean mIsWorkOnly;
private int mWorkUserId;
private View mEmptyView;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -304,25 +288,14 @@ public class ManageApplications extends InstrumentedPreferenceFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// initialize the inflater
mInflater = inflater;
mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
mLoadingContainer = mRootView.findViewById(R.id.loading_container);
mListContainer = mRootView.findViewById(R.id.list_container);
if (mListContainer != null) {
// Create adapter and list view here
View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty);
ListView lv = mListContainer.findViewById(android.R.id.list);
if (emptyView != null) {
lv.setEmptyView(emptyView);
}
lv.setOnItemClickListener(this);
lv.setSaveEnabled(true);
lv.setItemsCanFocus(true);
lv.setTextFilterEnabled(true);
mListView = lv;
mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter);
mEmptyView = mListContainer.findViewById(android.R.id.empty);
mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter,
savedInstanceState);
if (savedInstanceState != null) {
mApplications.mHasReceivedLoadEntries =
savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false);
@@ -347,11 +320,10 @@ public class ManageApplications extends InstrumentedPreferenceFragment
mVolumeUuid,
UserHandle.of(userId)));
}
mListView.setAdapter(mApplications);
mListView.setRecyclerListener(mApplications);
mListView.setFastScrollEnabled(isFastScrollEnabled());
Utils.prepareCustomPreferencesList(container, mRootView, mListView, false);
mRecyclerView = mListContainer.findViewById(R.id.apps_list);
mRecyclerView.setLayoutManager(new LinearLayoutManager(
getContext(), RecyclerView.VERTICAL, false /* reverseLayout */));
mRecyclerView.setAdapter(mApplications);
}
// 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_HAS_ENTRIES, mApplications.mHasReceivedLoadEntries);
outState.putBoolean(EXTRA_HAS_BRIDGE, mApplications.mHasReceivedBridgeCallback);
if (mApplications != null) {
mApplications.onSaveInstanceState(outState);
}
}
@Override
@@ -633,7 +608,6 @@ public class ManageApplications extends InstrumentedPreferenceFragment
case R.id.sort_order_alpha:
case R.id.sort_order_size:
mSortOrder = menuId;
mListView.setFastScrollEnabled(isFastScrollEnabled());
if (mApplications != null) {
mApplications.rebuild(mSortOrder);
}
@@ -667,10 +641,11 @@ public class ManageApplications extends InstrumentedPreferenceFragment
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
public void onClick(View view) {
if (mApplications == null) {
return;
}
final int position = mRecyclerView.getChildAdapterPosition(view);
if (mApplications.getApplicationCount() > position) {
ApplicationsState.AppEntry entry = mApplications.getAppEntry(position);
@@ -757,7 +732,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE);
notifyDataSetChanged();
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.onItemSelected(null, null, 0, 0);
}
@@ -777,7 +754,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
notifyDataSetChanged();
if (mManageApplications.mFilter == filter) {
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.onItemSelected(null, null, 0, 0);
}
@@ -795,38 +774,25 @@ public class ManageApplications extends InstrumentedPreferenceFragment
}
}
/*
* Custom adapter implementation for the ListView
* 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 {
static class ApplicationsAdapter extends RecyclerView.Adapter<ApplicationViewHolder>
implements ApplicationsState.Callbacks, AppStateBaseBridge.Callback {
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.Session mSession;
private final ManageApplications mManageApplications;
private final Context mContext;
private final ArrayList<View> mActive = new ArrayList<>();
private final AppStateBaseBridge mExtraInfoBridge;
private final Handler mBgHandler;
private final Handler mFgHandler;
private final LoadingViewController mLoadingViewController;
private AppFilterItem mAppFilter;
private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
private ArrayList<ApplicationsState.AppEntry> mEntries;
private boolean mResumed;
private int mLastSortMode = -1;
private int mWhichSize = SIZE_TOTAL;
CharSequence mCurFilterPrefix;
private AppFilter mCompositeFilter;
private boolean mHasReceivedLoadEntries;
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
// when we rebuild the list after the user made some changes, like uninstalling an app.
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,
AppFilterItem appFilter) {
AppFilterItem appFilter, Bundle savedInstanceState) {
setHasStableIds(true);
mState = state;
mFgHandler = new Handler();
mBgHandler = new Handler(mState.getBackgroundLooper());
mSession = state.newSession(this);
mManageApplications = manageApplications;
mLoadingViewController = new LoadingViewController(
@@ -893,6 +831,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
} else {
mExtraInfoBridge = null;
}
if (savedInstanceState != null) {
mLastIndex = savedInstanceState.getInt(STATE_LAST_SCROLL_INDEX);
}
}
public void setCompositeFilter(AppFilter compositeFilter) {
@@ -907,9 +848,11 @@ public class ManageApplications extends InstrumentedPreferenceFragment
public void setExtraViewController(FileViewHolderController 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();
mFgHandler.post(() -> {
ThreadUtils.postOnMainThread(() -> {
onExtraViewCompleted();
});
});
@@ -938,11 +881,13 @@ public class ManageApplications extends InstrumentedPreferenceFragment
mExtraInfoBridge.pause();
}
}
}
public void onSaveInstanceState(Bundle outState) {
// Record the current scroll position before pausing.
mLastIndex = mManageApplications.mListView.getFirstVisiblePosition();
View v = mManageApplications.mListView.getChildAt(0);
mLastTop =
(v == null) ? 0 : (v.getTop() - mManageApplications.mListView.getPaddingTop());
final LinearLayoutManager layoutManager =
(LinearLayoutManager) mManageApplications.mRecyclerView.getLayoutManager();
outState.putInt(STATE_LAST_SCROLL_INDEX, layoutManager.findFirstVisibleItemPosition());
}
public void release() {
@@ -960,6 +905,21 @@ public class ManageApplications extends InstrumentedPreferenceFragment
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() {
if (!mHasReceivedLoadEntries
|| (mExtraInfoBridge != null && !mHasReceivedBridgeCallback)) {
@@ -1008,11 +968,11 @@ public class ManageApplications extends InstrumentedPreferenceFragment
filterObj = new CompoundFilter(filterObj, ApplicationsState.FILTER_NOT_HIDE);
AppFilter finalFilterObj = filterObj;
mBgHandler.post(() -> {
ThreadUtils.postOnBackgroundThread(() -> {
final ArrayList<AppEntry> entries = mSession.rebuild(finalFilterObj,
comparatorObj, false);
if (entries != null) {
mFgHandler.post(() -> onRebuildComplete(entries));
ThreadUtils.postOnMainThread(() -> onRebuildComplete(entries));
}
});
}
@@ -1031,8 +991,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment
ArrayList<ApplicationsState.AppEntry> entries) {
int size = entries.size();
// returnList will not have more entries than entries
ArrayList<ApplicationsState.AppEntry> returnEntries = new
ArrayList<ApplicationsState.AppEntry>(size);
ArrayList<ApplicationsState.AppEntry> returnEntries = new ArrayList<>(size);
// assume appinfo of same package but different users are grouped together
PackageItemInfo lastInfo = null;
@@ -1055,21 +1014,19 @@ public class ManageApplications extends InstrumentedPreferenceFragment
filterType == FILTER_APPS_POWER_WHITELIST_ALL) {
entries = removeDuplicateIgnoringUser(entries);
}
mBaseEntries = entries;
if (mBaseEntries != null) {
mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
rebuildSections();
} else {
mEntries = null;
mSections = EMPTY_SECTIONS;
mPositionToSectionIndex = null;
}
mEntries = entries;
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
// it.
if (mLastIndex != -1 && getCount() > mLastIndex) {
mManageApplications.mListView.setSelectionFromTop(mLastIndex, mLastTop);
if (mLastIndex != -1 && getItemCount() > mLastIndex) {
mManageApplications.mRecyclerView.getLayoutManager().scrollToPosition(mLastIndex);
mLastIndex = -1;
}
@@ -1086,45 +1043,6 @@ public class ManageApplications extends InstrumentedPreferenceFragment
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
void updateLoading() {
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
public void onExtraInfoUpdated() {
mHasReceivedBridgeCallback = true;
@@ -1186,29 +1084,27 @@ public class ManageApplications extends InstrumentedPreferenceFragment
@Override
public void onPackageSizeChanged(String packageName) {
for (int i = 0; i < mActive.size(); i++) {
AppViewHolder holder = (AppViewHolder) mActive.get(i).getTag();
if (holder == null || holder.entry == null) {
if (mEntries == null) {
return;
}
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;
}
ApplicationInfo info = holder.entry.info;
if (info == null) {
continue;
}
if (holder.entry.info.packageName.equals(packageName)) {
synchronized (holder.entry) {
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();
}
if (TextUtils.equals(mManageApplications.mCurrentPkgName, info.packageName)) {
// 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;
} else {
notifyItemChanged(i);
}
}
}
@@ -1227,46 +1123,30 @@ public class ManageApplications extends InstrumentedPreferenceFragment
}
public void onExtraViewCompleted() {
int size = mActive.size();
// If we have no elements, don't do anything.
if (size < 1) {
if (!hasExtraView()) {
return;
}
AppViewHolder holder = (AppViewHolder) mActive.get(size - 1).getTag();
// 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);
// Update last item - this is assumed to be the extra view.
notifyItemChanged(getItemCount() - 1);
}
public int getCount() {
@Override
public int getItemCount() {
if (mEntries == null) {
return 0;
}
int extraViewAddition =
(mExtraViewController != null && mExtraViewController.shouldShow()) ? 1 : 0;
return mEntries.size() + extraViewAddition;
return mEntries.size() + (hasExtraView() ? 1 : 0);
}
public int getApplicationCount() {
return mEntries != null ? mEntries.size() : 0;
}
public Object getItem(int position) {
if (position == mEntries.size()) {
return mExtraViewController;
}
return mEntries.get(position);
}
public ApplicationsState.AppEntry getAppEntry(int position) {
public AppEntry getAppEntry(int position) {
return mEntries.get(position);
}
@Override
public long getItemId(int position) {
if (position == mEntries.size()) {
return -1;
@@ -1274,142 +1154,76 @@ public class ManageApplications extends InstrumentedPreferenceFragment
return mEntries.get(position).id;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) {
if (position == mEntries.size() && mExtraViewController != null &&
mExtraViewController.shouldShow()) {
return true;
}
if (mManageApplications.mListType != LIST_TYPE_HIGH_POWER) {
if (getItemViewType(position) == VIEW_TYPE_EXTRA_VIEW
|| mManageApplications.mListType != LIST_TYPE_HIGH_POWER) {
return true;
}
ApplicationsState.AppEntry entry = mEntries.get(position);
return !PowerWhitelistBackend.getInstance().isSysWhitelisted(entry.info.packageName);
}
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unnecessary calls
// 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.
@Override
public void onBindViewHolder(ApplicationViewHolder holder, int position) {
if (mEntries != null && mExtraViewController != null && position == mEntries.size()) {
// set up view for extra view controller
mExtraViewController.setupView(holder);
convertView.setEnabled(true);
} else {
// Bind the data efficiently with the holder
ApplicationsState.AppEntry entry = mEntries.get(position);
synchronized (entry) {
holder.entry = entry;
if (entry.label != null) {
holder.appName.setText(entry.label);
}
holder.setTitle(entry.label);
mState.ensureIcon(entry);
if (entry.icon != null) {
holder.appIcon.setImageDrawable(entry.icon);
}
updateSummary(holder);
updateDisableView(holder.disabled, entry.info);
holder.setIcon(entry.icon);
updateSummary(holder, entry);
holder.updateDisableView(entry.info);
}
convertView.setEnabled(isEnabled(position));
holder.setEnabled(isEnabled(position));
}
mActive.remove(convertView);
mActive.add(convertView);
return convertView;
holder.itemView.setOnClickListener(mManageApplications);
}
@VisibleForTesting
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) {
private void updateSummary(ApplicationViewHolder holder, AppEntry entry) {
switch (mManageApplications.mListType) {
case LIST_TYPE_NOTIFICATION:
if (holder.entry.extraInfo != null) {
holder.summary.setText(InstalledAppDetails.getNotificationSummary(
(AppRow) holder.entry.extraInfo, mContext));
if (entry.extraInfo != null) {
holder.setSummary(InstalledAppDetails.getNotificationSummary(
(AppRow) entry.extraInfo, mContext));
} else {
holder.summary.setText(null);
holder.setSummary(null);
}
break;
case LIST_TYPE_USAGE_ACCESS:
if (holder.entry.extraInfo != null) {
holder.summary.setText((new UsageState((PermissionState) holder.entry
.extraInfo)).isPermissible()
? R.string.app_permission_summary_allowed
: R.string.app_permission_summary_not_allowed);
if (entry.extraInfo != null) {
holder.setSummary(
(new UsageState((PermissionState) entry.extraInfo)).isPermissible()
? R.string.app_permission_summary_allowed
: R.string.app_permission_summary_not_allowed);
} else {
holder.summary.setText(null);
holder.setSummary(null);
}
break;
case LIST_TYPE_HIGH_POWER:
holder.summary.setText(HighPowerDetail.getSummary(mContext, holder.entry));
holder.setSummary(HighPowerDetail.getSummary(mContext, entry));
break;
case LIST_TYPE_OVERLAY:
holder.summary.setText(DrawOverlayDetails.getSummary(mContext, holder.entry));
holder.setSummary(DrawOverlayDetails.getSummary(mContext, entry));
break;
case LIST_TYPE_WRITE_SETTINGS:
holder.summary.setText(WriteSettingsDetails.getSummary(mContext,
holder.entry));
holder.setSummary(WriteSettingsDetails.getSummary(mContext, entry));
break;
case LIST_TYPE_MANAGE_SOURCES:
holder.summary.setText(ExternalSourcesDetails.getPreferenceSummary(mContext,
holder.entry));
holder.setSummary(ExternalSourcesDetails.getPreferenceSummary(mContext, entry));
break;
default:
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize);
break;
}
}
@Override
public Filter getFilter() {
return mFilter;
}
@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];
private boolean hasExtraView() {
return mExtraViewController != null
&& mExtraViewController.shouldShow();
}
}
@@ -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
= new SummaryLoader.SummaryProviderFactory() {
@Override

View File

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

View File

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

View File

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

View File

@@ -14,9 +14,8 @@
* 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.View;
import android.view.ViewGroup;
@@ -57,24 +56,4 @@ public class AppViewHolder {
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;
import com.android.settings.R;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -30,6 +28,8 @@ import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import com.android.settings.R;
import java.util.ArrayList;
import java.util.Collections;
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.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Bundle;
import android.os.Looper;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
@@ -102,30 +99,6 @@ public class ManageApplicationsTest {
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
public void updateMenu_mainListType_showAppReset() {
setUpOptionMenus();
@@ -170,14 +143,12 @@ public class ManageApplicationsTest {
ReflectionHelpers.setField(fragment, "mLoadingContainer", mock(View.class));
ReflectionHelpers.setField(fragment, "mListContainer", mock(View.class));
when(fragment.getActivity()).thenReturn(mock(Activity.class));
final Handler handler = mock(Handler.class);
final ManageApplications.ApplicationsAdapter adapter =
spy(new ManageApplications.ApplicationsAdapter(mState, fragment,
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL)));
spy(new ManageApplications.ApplicationsAdapter(mState, fragment,
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle()));
final LoadingViewController loadingViewController =
mock(LoadingViewController.class);
ReflectionHelpers.setField(adapter, "mLoadingViewController", loadingViewController);
ReflectionHelpers.setField(adapter, "mFgHandler", handler);
// app loading completed
ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", true);
@@ -196,15 +167,12 @@ public class ManageApplicationsTest {
ReflectionHelpers.setField(fragment, "mLoadingContainer", mock(View.class));
ReflectionHelpers.setField(fragment, "mListContainer", mock(View.class));
when(fragment.getActivity()).thenReturn(mock(Activity.class));
final Handler handler = mock(Handler.class);
final ManageApplications.ApplicationsAdapter adapter =
spy(new ManageApplications.ApplicationsAdapter(mState, fragment,
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL)));
spy(new ManageApplications.ApplicationsAdapter(mState, fragment,
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle()));
final LoadingViewController loadingViewController =
mock(LoadingViewController.class);
ReflectionHelpers.setField(adapter, "mLoadingViewController", loadingViewController);
ReflectionHelpers.setField(adapter, "mFgHandler", handler);
// app loading not yet completed
ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", false);
@@ -218,6 +186,10 @@ public class ManageApplicationsTest {
public void onRebuildComplete_shouldHideLoadingView() {
final Context context = RuntimeEnvironment.application;
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);
when(loadingContainer.getContext()).thenReturn(context);
final View listContainer = mock(View.class);
@@ -226,14 +198,12 @@ public class ManageApplicationsTest {
ReflectionHelpers.setField(fragment, "mLoadingContainer", loadingContainer);
ReflectionHelpers.setField(fragment, "mListContainer", listContainer);
when(fragment.getActivity()).thenReturn(mock(Activity.class));
final Handler handler = mock(Handler.class);
final ManageApplications.ApplicationsAdapter adapter =
spy(new ManageApplications.ApplicationsAdapter(mState, fragment,
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL)));
spy(new ManageApplications.ApplicationsAdapter(mState, fragment,
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL), new Bundle()));
final LoadingViewController loadingViewController =
mock(LoadingViewController.class);
ReflectionHelpers.setField(adapter, "mLoadingViewController", loadingViewController);
ReflectionHelpers.setField(adapter, "mFgHandler", handler);
ReflectionHelpers.setField(adapter, "mAppFilter",
AppFilterRegistry.getInstance().get(FILTER_APPS_ALL));

View File

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

View File

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