/*
* Copyright (C) 2015 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;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.cyanogenmod.ProtectedAppsReceiver;
import cyanogenmod.providers.CMSettings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
public class ProtectedAppsActivity extends Activity {
private static final int REQ_ENTER_PATTERN = 1;
private static final int REQ_RESET_PATTERN = 2;
private static final String NEEDS_UNLOCK = "needs_unlock";
private static final String TARGET_INTENT = "target_intent";
private ListView mListView;
private static final int MENU_RESET = 0;
private static final int MENU_RESET_LOCK = 1;
private PackageManager mPackageManager;
private AppsAdapter mAppsAdapter;
private ArrayList<ComponentName> mProtect;
private boolean mWaitUserAuth = false;
private boolean mUserIsAuth = false;
private Intent mTargetIntent;
private int mOrientation;
private HashSet<ComponentName> mProtectedApps = new HashSet<ComponentName>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Handle incoming target activity
Intent incomingIntent = getIntent();
if (incomingIntent.hasExtra("com.android.settings.PROTECTED_APP_TARGET_INTENT")) {
mTargetIntent =
incomingIntent.getParcelableExtra(
"com.android.settings.PROTECTED_APP_TARGET_INTENT");
}
setTitle(R.string.protected_apps);
setContentView(R.layout.hidden_apps_list);
mPackageManager = getPackageManager();
mAppsAdapter = new AppsAdapter(this, R.layout.hidden_apps_list_item);
mAppsAdapter.setNotifyOnChange(true);
mListView = (ListView) findViewById(R.id.protected_apps_list);
mListView.setAdapter(mAppsAdapter);
mProtect = new ArrayList<ComponentName>();
if (savedInstanceState != null) {
mUserIsAuth = savedInstanceState.getBoolean(NEEDS_UNLOCK);
mTargetIntent = savedInstanceState.getParcelable(TARGET_INTENT);
} else {
if (!mUserIsAuth) {
// Require unlock
mWaitUserAuth = true;
Intent lockPattern = new Intent(this, LockPatternActivity.class);
startActivityForResult(lockPattern, REQ_ENTER_PATTERN);
} else {
//LAUNCH
if (mTargetIntent != null) {
launchTargetActivityInfoAndFinish();
}
}
}
mOrientation = getResources().getConfiguration().orientation;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(NEEDS_UNLOCK, mUserIsAuth);
outState.putParcelable(TARGET_INTENT, mTargetIntent);
}
@Override
protected void onResume() {
super.onResume();
AsyncTask<Void, Void, List<AppEntry>> refreshAppsTask =
new AsyncTask<Void, Void, List<AppEntry>>() {
@Override
protected void onPostExecute(List<AppEntry> apps) {
mAppsAdapter.clear();
mAppsAdapter.addAll(apps);
}
@Override
protected List<AppEntry> doInBackground(Void... params) {
return refreshApps();
}
};
refreshAppsTask.execute(null, null, null);
getActionBar().setDisplayHomeAsUpEnabled(true);
// Update Protected Apps list
updateProtectedComponentsList();
}
private void updateProtectedComponentsList() {
String protectedComponents = CMSettings.Secure.getString(getContentResolver(),
CMSettings.Secure.PROTECTED_COMPONENTS);
protectedComponents = protectedComponents == null ? "" : protectedComponents;
String [] flattened = protectedComponents.split("\\|");
mProtectedApps = new HashSet<ComponentName>(flattened.length);
for (String flat : flattened) {
ComponentName cmp = ComponentName.unflattenFromString(flat);
if (cmp != null) {
mProtectedApps.add(cmp);
}
}
}
@Override
public void onPause() {
super.onPause();
// Close this app to prevent unauthorized access when
// 1) not waiting for authorization and
// 2) there is no portrait/landscape mode switching
if (!mWaitUserAuth && (mOrientation == getResources().getConfiguration().orientation)) {
finish();
}
}
private boolean getProtectedStateFromComponentName(ComponentName componentName) {
return mProtectedApps.contains(componentName);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQ_ENTER_PATTERN:
mWaitUserAuth = false;
switch (resultCode) {
case RESULT_OK:
//Nothing to do, proceed!
mUserIsAuth = true;
if (mTargetIntent != null) {
launchTargetActivityInfoAndFinish();
}
break;
case RESULT_CANCELED:
// user failed to define a pattern, do not lock the folder
finish();
break;
}
break;
case REQ_RESET_PATTERN:
mWaitUserAuth = false;
mUserIsAuth = false;
}
}
private void launchTargetActivityInfoAndFinish() {
Intent launchIntent = mTargetIntent;
startActivity(launchIntent);
finish();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, MENU_RESET, 0, R.string.menu_hidden_apps_delete)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
menu.add(0, MENU_RESET_LOCK, 0, R.string.menu_hidden_apps_reset_lock)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
return true;
}
private void reset() {
ArrayList<ComponentName> componentsList = new ArrayList<ComponentName>();
// Check to see if any components that have been protected that aren't present in
// the ListView. This can happen if there are components which have been protected
// but do not respond to the queryIntentActivities for Launcher Category
ContentResolver resolver = getContentResolver();
String hiddenComponents = CMSettings.Secure.getString(resolver,
CMSettings.Secure.PROTECTED_COMPONENTS);
if (hiddenComponents != null && !hiddenComponents.equals("")) {
for (String flattened : hiddenComponents.split("\\|")) {
ComponentName cmp = ComponentName.unflattenFromString(flattened);
if (!componentsList.contains(cmp)) {
componentsList.add(cmp);
}
}
}
AppProtectList list = new AppProtectList(componentsList,
PackageManager.COMPONENT_VISIBLE_STATUS);
StoreComponentProtectedStatus task = new StoreComponentProtectedStatus(this);
task.execute(list);
}
private void resetLock() {
mWaitUserAuth = true;
Intent lockPattern = new Intent(LockPatternActivity.RECREATE_PATTERN, null,
this, LockPatternActivity.class);
startActivityForResult(lockPattern, REQ_RESET_PATTERN);
}
private List<AppEntry> refreshApps() {
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent, 0);
Collections.sort(apps, new ResolveInfo.DisplayNameComparator(mPackageManager));
List<AppEntry> appEntries = new ArrayList<AppEntry>(apps.size());
for (ResolveInfo info : apps) {
appEntries.add(new AppEntry(info));
}
return appEntries;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_RESET:
reset();
return true;
case MENU_RESET_LOCK:
resetLock();
return true;
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private final class AppEntry {
public final ComponentName componentName;
public final String title;
public AppEntry(ResolveInfo info) {
ActivityInfo aInfo = info.activityInfo;
componentName = new ComponentName(aInfo.packageName, aInfo.name);
title = info.loadLabel(mPackageManager).toString();
}
}
private final class AppProtectList {
public final ArrayList<ComponentName> componentNames;
public final boolean state;
public AppProtectList(ArrayList<ComponentName> componentNames, boolean state) {
this.componentNames = new ArrayList<ComponentName>();
for (ComponentName cn : componentNames) {
this.componentNames.add(cn.clone());
}
this.state = state;
}
}
public class StoreComponentProtectedStatus extends AsyncTask<AppProtectList, Void, Void> {
private ProgressDialog mDialog;
private Context mContext;
public StoreComponentProtectedStatus(Context context) {
mContext = context;
mDialog = new ProgressDialog(mContext);
}
@Override
protected void onPreExecute() {
mDialog.setMessage(getResources().getString(R.string.saving_protected_components));
mDialog.setCancelable(false);
mDialog.setCanceledOnTouchOutside(false);
mDialog.show();
}
@Override
protected void onPostExecute(Void aVoid) {
if (mDialog.isShowing()) {
mDialog.dismiss();
}
mAppsAdapter.notifyDataSetChanged();
}
@Override
protected Void doInBackground(final AppProtectList... args) {
for (AppProtectList appList : args) {
ProtectedAppsReceiver.updateProtectedAppComponentsAndNotify(mContext,
appList.componentNames, appList.state);
}
updateProtectedComponentsList();
return null;
}
}
/**
* App view holder used to reuse the views inside the list.
*/
private static class AppViewHolder {
public final View container;
public final TextView title;
public final ImageView icon;
public final View launch;
public final CheckBox checkBox;
public AppViewHolder(View parentView) {
container = parentView.findViewById(R.id.app_item);
icon = (ImageView) parentView.findViewById(R.id.icon);
title = (TextView) parentView.findViewById(R.id.title);
launch = parentView.findViewById(R.id.launch_app);
checkBox = (CheckBox) parentView.findViewById(R.id.checkbox);
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
public class AppsAdapter extends ArrayAdapter<AppEntry> {
private final LayoutInflater mInflator;
private ConcurrentHashMap<String, Drawable> mIcons;
private Drawable mDefaultImg;
private List<AppEntry> mApps;
public AppsAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
mApps = new ArrayList<AppEntry>();
mInflator = LayoutInflater.from(context);
// set the default icon till the actual app icon is loaded in async task
mDefaultImg = context.getResources().getDrawable(android.R.mipmap.sym_def_app_icon);
mIcons = new ConcurrentHashMap<String, Drawable>();
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
AppViewHolder viewHolder;
if (convertView == null) {
convertView = mInflator.inflate(R.layout.hidden_apps_list_item, parent, false);
viewHolder = new AppViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (AppViewHolder) convertView.getTag();
}
AppEntry app = getItem(position);
viewHolder.title.setText(app.title);
Drawable icon = mIcons.get(app.componentName.getPackageName());
viewHolder.icon.setImageDrawable(icon != null ? icon : mDefaultImg);
boolean state = getProtectedStateFromComponentName(app.componentName);
viewHolder.checkBox.setChecked(state);
if (state) {
viewHolder.launch.setVisibility(View.VISIBLE);
viewHolder.launch.setTag(app);
viewHolder.launch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ComponentName cName = ((AppEntry)v.getTag()).componentName;
Intent intent = new Intent();
intent.setClassName(cName.getPackageName(), cName.getClassName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
} else {
viewHolder.launch.setVisibility(View.GONE);
}
viewHolder.container.setTag(position);
viewHolder.container.setOnClickListener(mAppClickListener);
return convertView;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
// If we have new items, we have to load their icons
// If items were deleted, remove them from our mApps
List<AppEntry> newApps = new ArrayList<AppEntry>(getCount());
List<AppEntry> oldApps = new ArrayList<AppEntry>(getCount());
for (int i = 0; i < getCount(); i++) {
AppEntry app = getItem(i);
if (mApps.contains(app)) {
oldApps.add(app);
} else {
newApps.add(app);
}
}
if (newApps.size() > 0) {
new LoadIconsTask().execute(newApps.toArray(new AppEntry[] {}));
newApps.addAll(oldApps);
mApps = newApps;
} else {
mApps = oldApps;
}
}
/**
* An asynchronous task to load the icons of the installed applications.
*/
private class LoadIconsTask extends AsyncTask<AppEntry, Void, Void> {
@Override
protected Void doInBackground(AppEntry... apps) {
for (AppEntry app : apps) {
try {
String packageName = app.componentName.getPackageName();
if (mIcons.containsKey(packageName)) {
continue;
}
Drawable icon = mPackageManager.getApplicationIcon(packageName);
mIcons.put(packageName, icon);
publishProgress();
} catch (PackageManager.NameNotFoundException e) {
// ignored; app will show up with default image
}
}
return null;
}
@Override
protected void onProgressUpdate(Void... progress) {
notifyDataSetChanged();
}
}
}
private View.OnClickListener mAppClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = (Integer) v.getTag();
ComponentName cn = mAppsAdapter.getItem(position).componentName;
ArrayList<ComponentName> componentsList = new ArrayList<ComponentName>();
componentsList.add(cn);
boolean state = getProtectedStateFromComponentName(cn);
AppProtectList list = new AppProtectList(componentsList, state);
StoreComponentProtectedStatus task =
new StoreComponentProtectedStatus(ProtectedAppsActivity.this);
task.execute(list);
}
};
}