Blacklist : Improve logic

- List only those apps which are visible to AuroraStore
 - Use ArrayList<String> instead of Set<String>
This commit is contained in:
Mr. Dragon
2019-03-17 17:30:29 +05:30
parent 925b592521
commit fafc3ef9e0
12 changed files with 243 additions and 187 deletions

View File

@@ -56,7 +56,7 @@ public class Constants {
public static final String PREFERENCE_DEVICE_TO_PRETEND_TO_BE = "PREFERENCE_DEVICE_TO_PRETEND_TO_BE";
public static final String PREFERENCE_DEVICE_TO_PRETEND_TO_BE_INDEX = "PREFERENCE_DEVICE_TO_PRETEND_TO_BE_INDEX";
public static final String PREFERENCE_REQUESTED_LOCATION_INDEX = "PREFERENCE_REQUESTED_LOCATION_INDEX";
public static final String PREFERENCE_BLACKLIST_APPS_SET = "PREFERENCE_BLACKLIST_APPS_SET";
public static final String PREFERENCE_BLACKLIST_APPS_LIST = "PREFERENCE_BLACKLIST_APPS_LIST";
public static final String PREFERENCE_FILTER_GOOGLE = "PREFERENCE_FILTER_GOOGLE";
public static final String PREFERENCE_FILTER_F_DROID = "PREFERENCE_FILTER_F_DROID";
public static final String PREFERENCE_FILTER_SEARCH = "PREFERENCE_FILTER_SEARCH";

View File

@@ -114,11 +114,11 @@ public class AppMenuAdapter extends RecyclerView.Adapter<AppMenuAdapter.ViewHold
});
break;
case R.id.action_blacklist:
BlacklistManager blacklistManager = new BlacklistManager(context);
if (blacklistManager.isBackListed(app.getPackageName())) {
final BlacklistManager blacklistManager = new BlacklistManager(context);
if (blacklistManager.contains(app.getPackageName())) {
holder.menu_title.setText(R.string.action_whitelist);
view.setOnClickListener(v -> {
blacklistManager.removeFromBlacklist(app.getPackageName());
blacklistManager.remove(app.getPackageName());
menuSheet.dismissAllowingStateLoss();
Toast.makeText(context, context.getString(R.string.toast_apk_whitelisted),
Toast.LENGTH_SHORT).show();
@@ -126,7 +126,7 @@ public class AppMenuAdapter extends RecyclerView.Adapter<AppMenuAdapter.ViewHold
} else {
holder.menu_title.setText(R.string.action_blacklist);
view.setOnClickListener(v -> {
blacklistManager.addToBlacklist(app.getPackageName());
blacklistManager.add(app.getPackageName());
menuSheet.dismissAllowingStateLoss();
Toast.makeText(context, context.getString(R.string.toast_apk_blacklisted),
Toast.LENGTH_SHORT).show();

View File

@@ -21,8 +21,6 @@
package com.aurora.store.adapter;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -32,15 +30,14 @@ import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.aurora.store.GlideApp;
import com.aurora.store.R;
import com.aurora.store.model.Packages;
import com.aurora.store.model.App;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import butterknife.BindView;
import butterknife.ButterKnife;
@@ -48,29 +45,13 @@ import butterknife.ButterKnife;
public class BlacklistAdapter extends SelectableAdapter<BlacklistAdapter.ViewHolder> {
private List<Packages> packagesList;
private List<App> appList = new ArrayList<>();
private ItemClickListener itemClickListener;
public BlacklistAdapter(Context context, List<ResolveInfo> resolveInfos, ItemClickListener itemClickListener) {
public BlacklistAdapter(Context context, List<App> appList, ItemClickListener itemClickListener) {
super(context);
this.itemClickListener = itemClickListener;
PackageManager mPackageManager = context.getPackageManager();
Set<String> mBlackListSet = new HashSet<>();
mBlackListSet.add("com.aurora.store");
mBlackListSet.add("com.google.android.gms");
mBlackListSet.add("Services Framework Proxy");
mBlackListSet.add("com.android.vending");
mBlackListSet.add("");
packagesList = new ArrayList<>();
for (int i = 0; i < resolveInfos.size(); i++) {
ResolveInfo mResolveInfo = resolveInfos.get(i);
String mPackageName = mResolveInfo.activityInfo.packageName;
if (!mBlackListSet.contains(mPackageName)) {
packagesList.add(new Packages(mResolveInfo, mPackageName, mPackageManager));
}
}
this.appList = appList;
}
@NotNull
@@ -82,27 +63,36 @@ public class BlacklistAdapter extends SelectableAdapter<BlacklistAdapter.ViewHol
@Override
public void onBindViewHolder(@NotNull ViewHolder viewHolder, int position) {
viewHolder.label.setText(packagesList.get(position).getLabel());
viewHolder.icon.setImageDrawable(packagesList.get(position).getIcon());
viewHolder.checkBox.setChecked(isSelected(packagesList.get(position).getPackageName()));
final App app = appList.get(position);
viewHolder.label.setText(app.getDisplayName());
viewHolder.packageName.setText(app.getPackageName());
viewHolder.checkBox.setChecked(isSelected(app.getPackageName()));
GlideApp
.with(context)
.load(app.getIconInfo().getUrl())
.into(viewHolder.icon);
}
@Override
public int getItemCount() {
return packagesList.size();
return appList.size();
}
@Override
public void toggleSelection(int position) {
String packageName = packagesList.get(position).getPackageName();
String packageName = appList.get(position).getPackageName();
if (mSelections.contains(packageName)) {
mSelections.remove(packageName);
mBlacklistManager.remove(packageName);
} else {
mSelections.add(packageName);
}
notifyItemChanged(position);
}
public int getSelectedCount() {
return mSelections.size();
}
public interface ItemClickListener {
void onItemClicked(int position);
@@ -112,6 +102,8 @@ public class BlacklistAdapter extends SelectableAdapter<BlacklistAdapter.ViewHol
@BindView(R.id.label)
TextView label;
@BindView(R.id.packageName)
TextView packageName;
@BindView(R.id.icon)
ImageView icon;
@BindView(R.id.check)

View File

@@ -26,21 +26,19 @@ import androidx.recyclerview.widget.RecyclerView;
import com.aurora.store.manager.BlacklistManager;
import java.util.HashSet;
import java.util.Set;
import java.util.ArrayList;
abstract class SelectableAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
protected Set<String> mSelections;
protected ArrayList<String> mSelections;
protected Context context;
private BlacklistManager mBlacklistManager;
protected BlacklistManager mBlacklistManager;
SelectableAdapter(Context context) {
this.context = context;
mBlacklistManager = new BlacklistManager(context);
Set<String> blacklistedApps = mBlacklistManager.getBlacklistedApps();
mSelections = new HashSet<>();
ArrayList<String> blacklistedApps = mBlacklistManager.get();
mSelections = new ArrayList<>();
if (blacklistedApps != null && !blacklistedApps.isEmpty()) {
mSelections.addAll(blacklistedApps);
}
@@ -54,14 +52,11 @@ abstract class SelectableAdapter<VH extends RecyclerView.ViewHolder> extends Rec
}
public void addSelectionsToBlackList() {
mBlacklistManager.addSelectionsToBlackList(mSelections);
mBlacklistManager.addAll(mSelections);
}
public void removeSelectionsToBlackList() {
Set<String> blacklistedApps = mBlacklistManager.getBlacklistedApps();
if (blacklistedApps != null && !blacklistedApps.isEmpty()) {
mSelections.removeAll(blacklistedApps);
}
mBlacklistManager.removeSelectionsFromBlackList(mSelections);
public void removeSelectionsFromBlackList() {
mBlacklistManager.removeAll(mSelections);
mSelections = new ArrayList<>();
}
}

View File

@@ -41,7 +41,6 @@ import java.net.UnknownHostException;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
@@ -128,7 +127,7 @@ public abstract class BaseFragment extends Fragment {
mDisposable.add(Flowable.fromCallable(() ->
new PlayStoreApiAuthenticator(context).refreshToken())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.computation())
.subscribe((success) -> {
if (success) {
Log.i("Token Refreshed");
@@ -141,7 +140,6 @@ public abstract class BaseFragment extends Fragment {
Log.e("Token Refresh Login failed %s", err.getMessage());
eventListenerImpl.onLoginFailed();
}));
mDisposable.dispose();
}
public interface EventListenerImpl {

View File

@@ -21,37 +21,50 @@
package com.aurora.store.fragment;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ViewSwitcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.aurora.store.manager.BlacklistManager;
import com.aurora.store.ErrorType;
import com.aurora.store.R;
import com.aurora.store.adapter.BlacklistAdapter;
import com.aurora.store.model.App;
import com.aurora.store.task.InstalledApps;
import com.aurora.store.utility.Log;
import com.aurora.store.utility.ViewUtil;
import com.aurora.store.view.CustomSwipeToRefresh;
import com.aurora.store.view.ErrorView;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
public class BlacklistFragment extends Fragment implements BlacklistAdapter.ItemClickListener {
public class BlacklistFragment extends BaseFragment implements BlacklistAdapter.ItemClickListener, BaseFragment.EventListenerImpl {
@BindView(R.id.recycler_view)
@BindView(R.id.view_switcher)
ViewSwitcher mViewSwitcher;
@BindView(R.id.content_view)
LinearLayout layoutContent;
@BindView(R.id.err_view)
LinearLayout layoutError;
@BindView(R.id.swipe_layout)
CustomSwipeToRefresh customSwipeToRefresh;
@BindView(R.id.recycler)
RecyclerView mRecyclerView;
@BindView(R.id.btn_clear_all)
Button btnClearAll;
@@ -59,7 +72,6 @@ public class BlacklistFragment extends Fragment implements BlacklistAdapter.Item
TextView txtBlacklist;
private Context context;
private BlacklistManager mBlacklistManager;
private BlacklistAdapter mAdapter;
@Override
@@ -79,29 +91,75 @@ public class BlacklistFragment extends Fragment implements BlacklistAdapter.Item
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mBlacklistManager = new BlacklistManager(context);
setupRecycler();
setErrorView(ErrorType.UNKNOWN);
loadAllApps();
setupClearAll();
}
@Override
public void onResume() {
super.onResume();
}
private void updateBlackListedApps() {
mAdapter.addSelectionsToBlackList();
}
private void clearBlackListedApps() {
if (mAdapter != null) {
mAdapter.removeSelectionsToBlackList();
mAdapter.removeSelectionsFromBlackList();
mAdapter.notifyDataSetChanged();
txtBlacklist.setText(getString(R.string.list_blacklist_none));
}
}
private void setupRecycler() {
List<ResolveInfo> mInstalledPackages = getInstalledApps();
mAdapter = new BlacklistAdapter(context, mInstalledPackages, this);
private void loadAllApps() {
InstalledApps mTaskHelper = new InstalledApps(context);
mDisposable.add(Observable.fromCallable(() -> mTaskHelper.getInstalledApps(false))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(subscription -> customSwipeToRefresh.setRefreshing(true))
.subscribe((appList) -> {
if (appList.isEmpty()) {
setErrorView(ErrorType.NO_APPS);
switchViews(true);
} else {
switchViews(false);
setupRecycler(appList);
}
}, err -> {
Log.e(err.getMessage());
processException(err);
}));
}
private void setupRecycler(List<App> appList) {
customSwipeToRefresh.setRefreshing(false);
mAdapter = new BlacklistAdapter(context, appList, this);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new LinearLayoutManager(context, RecyclerView.VERTICAL, false));
mRecyclerView.setAdapter(mAdapter);
updateCount();
}
private void setErrorView(ErrorType errorType) {
layoutError.removeAllViews();
layoutError.addView(new ErrorView(context, errorType, retry()));
}
private View.OnClickListener retry() {
return v -> {
loadAllApps();
((Button) v).setText(getString(R.string.action_retry_ing));
((Button) v).setEnabled(false);
};
}
private void switchViews(boolean showError) {
if (mViewSwitcher.getCurrentView() == layoutContent && showError)
mViewSwitcher.showNext();
else if (mViewSwitcher.getCurrentView() == layoutError && !showError)
mViewSwitcher.showPrevious();
}
private void setupClearAll() {
@@ -110,24 +168,37 @@ public class BlacklistFragment extends Fragment implements BlacklistAdapter.Item
});
}
private void updateCount() {
int count = mAdapter.getSelectedCount();
String txtCount = new StringBuilder()
.append(getResources().getString(R.string.list_blacklist))
.append(" : ")
.append(count).toString();
txtBlacklist.setText(count > 0 ? txtCount : getString(R.string.list_blacklist_none));
ViewUtil.setVisibility(btnClearAll, count > 0, true);
}
@Override
public void onItemClicked(int position) {
mAdapter.toggleSelection(position);
updateBlackListedApps();
int count = mBlacklistManager.getBlacklistedApps().size();
String txtCount = new StringBuilder()
.append(getResources().getString(R.string.list_blacklist))
.append(" : ")
.append(mBlacklistManager.getBlacklistedApps().size()).toString();
txtBlacklist.setText(count > 0 ? txtCount : getString(R.string.list_blacklist_none));
updateCount();
}
private List<ResolveInfo> getInstalledApps() {
PackageManager packageManager = context.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> installedApps = packageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA);
Collections.sort(installedApps, new ResolveInfo.DisplayNameComparator(packageManager));
return installedApps;
@Override
public void onLoggedIn() {
loadAllApps();
}
@Override
public void onLoginFailed() {
setErrorView(ErrorType.UNKNOWN);
switchViews(true);
}
@Override
public void onNetworkFailed() {
setErrorView(ErrorType.NO_NETWORK);
switchViews(true);
}
}

View File

@@ -25,50 +25,59 @@ import android.content.Context;
import com.aurora.store.Constants;
import com.aurora.store.utility.PrefUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class BlacklistManager {
private Context context;
private ArrayList<String> blackList;
public BlacklistManager(Context context) {
this.context = context;
blackList = PrefUtil.getListString(context, Constants.PREFERENCE_BLACKLIST_APPS_LIST);
}
public Set<String> getBlacklistedApps() {
return PrefUtil.getStringSet(context, Constants.PREFERENCE_BLACKLIST_APPS_SET);
public boolean add(String s) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add(s);
boolean result = addAll(arrayList);
save();
return result;
}
public boolean isBackListed(String packageName){
Set<String> hiddenApps = PrefUtil.getStringSet(context, Constants.PREFERENCE_BLACKLIST_APPS_SET);
return hiddenApps.contains(packageName);
public boolean addAll(ArrayList<String> arrayList) {
boolean result = blackList.addAll(arrayList);
Set<String> mAppSet = new HashSet<>(blackList);
blackList.clear();
blackList.addAll(mAppSet);
save();
return result;
}
public void addToBlacklist(String packageName) {
Set<String> hiddenApps = PrefUtil.getStringSet(context, Constants.PREFERENCE_BLACKLIST_APPS_SET);
hiddenApps.add(packageName);
PrefUtil.putStringSet(context, Constants.PREFERENCE_BLACKLIST_APPS_SET, hiddenApps);
public ArrayList<String> get() {
return blackList;
}
public void removeFromBlacklist(String packageName) {
Set<String> hiddenApps = PrefUtil.getStringSet(context, Constants.PREFERENCE_BLACKLIST_APPS_SET);
if (hiddenApps != null && !hiddenApps.isEmpty()) {
hiddenApps.remove(packageName);
}
PrefUtil.putStringSet(context, Constants.PREFERENCE_BLACKLIST_APPS_SET, hiddenApps);
public boolean contains(String packageName) {
return blackList.contains(packageName);
}
public void addSelectionsToBlackList(Set<String> set) {
PrefUtil.putStringSet(context, Constants.PREFERENCE_BLACKLIST_APPS_SET, set);
public boolean remove(String packageName) {
boolean result = blackList.remove(packageName);
save();
return result;
}
public void removeSelectionsFromBlackList(Set<String> set) {
Set<String> hiddenApps = PrefUtil.getStringSet(context, Constants.PREFERENCE_BLACKLIST_APPS_SET);
if (hiddenApps != null && !hiddenApps.isEmpty()) {
set.removeAll(hiddenApps);
}
PrefUtil.putStringSet(context, Constants.PREFERENCE_BLACKLIST_APPS_SET, set);
public boolean removeAll(ArrayList<String> packageList) {
boolean result = blackList.removeAll(packageList);
save();
return result;
}
private void save() {
PrefUtil.putListString(context, Constants.PREFERENCE_BLACKLIST_APPS_LIST, blackList);
}
public Set<String> getGoogleApps() {

View File

@@ -1,49 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2019, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
package com.aurora.store.model;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
public class Packages {
private String packageName;
private CharSequence label;
private Drawable icon;
public Packages(ResolveInfo info, String packageName, PackageManager packageManager) {
this.packageName = packageName;
label = info.loadLabel(packageManager);
icon = info.loadIcon(packageManager);
}
public String getPackageName() {
return packageName;
}
public CharSequence getLabel() {
return label;
}
public Drawable getIcon() {
return icon;
}
}

View File

@@ -103,7 +103,7 @@ public class UpdatableApps extends AllApps {
private Map<String, App> filterBlacklistedApps(Map<String, App> apps) {
Set<String> packageNames = new HashSet<>(apps.keySet());
packageNames.removeAll(new BlacklistManager(context).getBlacklistedApps());
packageNames.removeAll(new BlacklistManager(context).get());
Map<String, App> result = new HashMap<>();
for (App app : apps.values()) {
if (packageNames.contains(app.getPackageName())) {

View File

@@ -133,6 +133,13 @@ public class ViewUtil {
hideWithAnimation(view);
}
public static void setVisibility(View view, boolean visibility, boolean noAnim) {
if (noAnim)
view.setVisibility(visibility ? View.VISIBLE : View.INVISIBLE);
else
setVisibility(view, visibility);
}
public static List<MenuEntry> parseMenu(Context context, @MenuRes int menuRes) {
List<MenuEntry> menuEntryList = new ArrayList<>();
PopupMenu p = new PopupMenu(context, null);

View File

@@ -23,47 +23,67 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/content_view"
<ViewSwitcher
android:id="@+id/view_switcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="wrap_content">
<RelativeLayout
<LinearLayout
android:id="@+id/content_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?actionBarSize">
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_blacklist"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginEnd="@dimen/margin_small"
android:layout_toStartOf="@id/btn_clear_all"
android:text="@string/list_app_blacklisted_empty"
android:textAppearance="@style/TextAppearance.Aurora.SubTitle" />
android:minHeight="?actionBarSize">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_clear_all"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:maxLines="1"
android:text="@string/action_clear_all" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_blacklist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginEnd="@dimen/margin_small"
android:layout_toStartOf="@id/btn_clear_all"
android:textAppearance="@style/TextAppearance.Aurora.SubTitle" />
</RelativeLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_clear_all"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:maxLines="1"
android:text="@string/action_clear_all"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
</RelativeLayout>
<com.aurora.store.view.CustomSwipeToRefresh
android:id="@+id/swipe_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
</com.aurora.store.view.CustomSwipeToRefresh>
</LinearLayout>
<LinearLayout
android:id="@+id/err_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical" />
</LinearLayout>
android:layout_height="match_parent"
android:orientation="vertical" />
</ViewSwitcher>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -38,7 +38,6 @@
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/check"
android:layout_toEndOf="@+id/icon"
android:ellipsize="end"
@@ -47,6 +46,20 @@
android:textColor="?android:attr/textColorPrimary"
android:textSize="18sp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/packageName"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/label"
android:layout_alignStart="@id/label"
android:layout_alignEnd="@id/label"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp" />
<CheckBox
android:id="@+id/check"
android:layout_width="wrap_content"