diff options
author | Vineet Patil <vpatil@cyngn.com> | 2014-11-12 17:14:27 -0800 |
---|---|---|
committer | Gerrit Code Review <gerrit@cyanogenmod.org> | 2015-11-24 12:44:15 -0800 |
commit | 5f350518a2aec87c057152191eddddad21083d1c (patch) | |
tree | be00269646217027b249e0ab56a20832d60b793b /src | |
parent | 99d6fd01a334b4057e7b56ce3ece09c3e6124bf9 (diff) | |
download | packages_apps_Settings-5f350518a2aec87c057152191eddddad21083d1c.zip packages_apps_Settings-5f350518a2aec87c057152191eddddad21083d1c.tar.gz packages_apps_Settings-5f350518a2aec87c057152191eddddad21083d1c.tar.bz2 |
Re-Implementation of Protected App Settings
Protected App [2/3] Protected Apps Settings -> Apps: - Added Receiver which can send in a call to PackageManager to toggle a components protected status. - Add Protected Apps activity (available from Apps fragment) - Reads from ApplicationInfo state - Requires Pattern Lock to view/modify protected apps - Updates Settings Secure DB with protected components - Support resetting protected apps pattern lock
Change-Id: If07a7b69ac963ffae855621881e1944fc8754782
Protected Apps:
- App Info for protected Apps doesn't allow Uninstall or Clear data
- App Info menu item for launching into Protected Apps when looking at protected components
- Prevent process dialog from getting dismissed on touch or back key (can cause odd behavior if cancelled mid-protect)
Conflicts:
src/com/android/settings/applications/InstalledAppDetails.java
Change-Id: I64104d7ff3fbf9d8c393ebf262d4de0b28abbc5c
Reset Pattern: - If user cancels while creating new pattern, old pattern is restored
Change-Id: I55955b1ffadca2ba712c40c7d443c4fc4b0f528c
Clean up protected apps code (1/2)
- Work with actual ComponentNames instead of converting them between
String and ComponentName all the time
- Name protection state parameter in methods 'state' instead of
'protect', as a value of true actually means it's not protected. Also
consistently use the respective constants for its values.
- Some misc. cleanup
Change-Id: I2855978c8aef3cfa14249e3398039c7cdd145ede
Settings: Create a security fallback on protected apps.
-- Allow a user to bypass pattern lock on protected apps
by inputting their primary account information for the
device.
Conflicts:
res/values/cm_dimens.xml
res/values/cm_strings.xml
Change-Id: I39e5a89a8699cfd2ffaba8aea2daa4f477f2cc9b
settings: reset protected apps view instead of finish the activity
Finish the app after resettings the account, will lead to the protected app selection without a
valid pattern. Instead this should reset the pattern view to create a new pattern prior to go to
protected apps.
Change-Id: Ida41a29f4f8787940f803a23014a68a2f8beb969
Signed-off-by: Jorge Ruesga <jorge@ruesga.com>
Settings: Don't show protected apps options in restricted profiles.
Change-Id: I38c2e8fd3508d360f0e23703ce6c939f02f3714e
JIRA: 4668
Issue: https://jira.cyanogenmod.org/browse/CYAN-4668
Follow normal lock screen convention - allow user to retry when creating pattern lock
Change-Id: I6ad39b07b8de3de03146322b37b60e3f846093d1
Fix: Even when the list is entirely unchecked there may still be components which are still protected and need to cleared when Reset is triggered.
https://jira.cyanogenmod.org/browse/BACON-679
https://jira.cyanogenmod.org/browse/CYAN-4835
Change-Id: Ifee3e8b87be39769aedfed0f9a5bda366c67ee45
ProtectedApps pattern lock for landscape mode.
Change-Id: I9ef70a0e363d4d17510188d24f4742f458fba38e
Launch Protected Apps
Change-Id: I2dc2e1e05c1979118d5324c3c05adfcc6f7ee22a
Protected Apps: update protected apps screens UI
Change-Id: I2dd922956f8ffd9ed153c3d1aa1f9161a127e4c6
Remove unnecessary drawables for launching Protected Apps
Change-Id: I58f471ef9d64c7ced79befbfc30b94d75a0085a2
ProtectedAppsReceiver: fix NPE when components are null
* To reproduce: create a folder at Trebuchet, lock it and leave the screen
Change-Id: I49a2e76fdaa3e375b0ea5aa2bb05eaa92528dd19
Protected Apps: Show state by component instead of by app
Change-Id: Idbe1d69b376fc3f42980404d9448152f606e7f8e
(cherry picked from commit 56e11c8640cb9e34d07eb11fb1b67f3283b2f6f6)
Protected Apps: Increase hit target of the launch app button
Change-Id: I38355aca37079d43975d287ee9d81c3a500c3575
(cherry picked from commit 47e8e46f53cdf4f536845d57cc383d3c5f0ca893)
Protected Apps: Monitor unlock status
Monitor the Activities unlock status so that we can rotate
the screen in this activity without having to unlock again.
Change-Id: I8feab5cb4d55c4df0d0d1475ab6646c046f01925
Protected Apps: add some side padding in app list view
Change-Id: I2549fd8f8b662e2e2c542e67cbc20e6a366fba42
Conflicts:
AndroidManifest.xml
res/values/cm_dimens.xml
res/values/cm_strings.xml
res/values/strings.xml
src/com/android/settings/applications/ApplicationsState.java
src/com/android/settings/applications/InstalledAppDetails.java
src/com/android/settings/applications/ManageApplications.java
Conflicts:
res/values/cm_dimens.xml
Conflicts:
res/values/cm_strings.xml
.
Change-Id: I065ac7965a8c7253ad67a20806201fe8fc6cec84
Diffstat (limited to 'src')
9 files changed, 1425 insertions, 1 deletions
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 5d3980f..34a27fa 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -721,6 +721,11 @@ public final class Utils { .getUsers().size() > 1; } + public static boolean isRestrictedProfile(Context context) { + UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); + return um.getUserInfo(um.getUserHandle()).isRestricted(); + } + private static int getScreenType(Context context) { if (sDeviceType == -1) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java index ff618c2..9528ec1 100644 --- a/src/com/android/settings/applications/AppInfoBase.java +++ b/src/com/android/settings/applications/AppInfoBase.java @@ -123,7 +123,8 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName, PackageManager.GET_DISABLED_COMPONENTS | PackageManager.GET_UNINSTALLED_PACKAGES | - PackageManager.GET_SIGNATURES); + PackageManager.GET_SIGNATURES | + PackageManager.GET_ACTIVITIES); } catch (NameNotFoundException e) { Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e); } diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index 8b6fe53..b5e24e5 100755 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.Loader; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -63,6 +64,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; +import com.android.settings.cyanogenmod.ProtectedAppsReceiver; import com.android.internal.logging.MetricsLogger; import com.android.internal.os.BatterySipper; @@ -106,10 +108,12 @@ public class InstalledAppDetails extends AppInfoBase // Menu identifiers public static final int UNINSTALL_ALL_USERS_MENU = 1; public static final int UNINSTALL_UPDATES = 2; + public static final int OPEN_PROTECTED_APPS = 3; // Result code identifiers public static final int REQUEST_UNINSTALL = 0; private static final int SUB_INFO_FRAGMENT = 1; + public static final int REQUEST_TOGGLE_PROTECTION = 3; private static final int LOADER_CHART_DATA = 2; @@ -236,6 +240,12 @@ public class InstalledAppDetails extends AppInfoBase enabled = false; } + // This is a protected app component. + // You cannot a uninstall a protected component + if (mPackageInfo.applicationInfo.protect) { + enabled = false; + } + mUninstallButton.setEnabled(enabled); if (enabled) { // Register listener @@ -375,6 +385,9 @@ public class InstalledAppDetails extends AppInfoBase .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(0, OPEN_PROTECTED_APPS, Menu.NONE, R.string.protected_apps) + .setIcon(getResources().getDrawable(R.drawable.folder_lock)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); } @Override @@ -399,6 +412,9 @@ public class InstalledAppDetails extends AppInfoBase menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(showIt); mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; menu.findItem(UNINSTALL_UPDATES).setVisible(mUpdatedSysApp && !mAppControlRestricted); + + menu.findItem(OPEN_PROTECTED_APPS).setVisible(mPackageInfo.applicationInfo.protect); + } @Override @@ -410,6 +426,10 @@ public class InstalledAppDetails extends AppInfoBase case UNINSTALL_UPDATES: showDialogInner(DLG_FACTORY_RESET, 0); return true; + case OPEN_PROTECTED_APPS: + // Verify protection for toggling protected component status + Intent protectedApps = new Intent(getActivity(), LockPatternActivity.class); + startActivityForResult(protectedApps, REQUEST_TOGGLE_PROTECTION); } return false; } @@ -435,6 +455,37 @@ public class InstalledAppDetails extends AppInfoBase if (!refreshUi()) { setIntentAndFinish(true, true); } + } else if (requestCode == REQUEST_TOGGLE_PROTECTION) { + switch (resultCode) { + case Activity.RESULT_OK: + new ToggleProtectedAppComponents().execute(); + break; + case Activity.RESULT_CANCELED: + // User failed to enter/confirm a lock pattern, do nothing + break; + } + } + } + + private class ToggleProtectedAppComponents extends AsyncTask<Void, Void, Void> { + @Override + protected void onPostExecute(Void aVoid) { + getActivity().invalidateOptionsMenu(); + if (!refreshUi()) { + setIntentAndFinish(true, true); + } + } + + @Override + protected Void doInBackground(Void... params) { + ArrayList<ComponentName> components = new ArrayList<ComponentName>(); + for (ActivityInfo aInfo : mPackageInfo.activities) { + components.add(new ComponentName(aInfo.packageName, aInfo.name)); + } + + ProtectedAppsReceiver.updateProtectedAppComponentsAndNotify(getActivity(), + components, PackageManager.COMPONENT_VISIBLE_STATUS); + return null; } } diff --git a/src/com/android/settings/applications/LockPatternActivity.java b/src/com/android/settings/applications/LockPatternActivity.java new file mode 100644 index 0000000..76ae423 --- /dev/null +++ b/src/com/android/settings/applications/LockPatternActivity.java @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2014 The CyanogenMod 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.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Base64; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import android.widget.Toast; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternView; +import com.android.settings.R; +import com.android.settings.cyanogenmod.ProtectedAccountView; +import com.android.settings.cyanogenmod.ProtectedAccountView.OnNotifyAccountReset; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.List; + +public class LockPatternActivity extends Activity implements OnNotifyAccountReset { + public static final String PATTERN_LOCK_PROTECTED_APPS = "pattern_lock_protected_apps"; + public static final String RECREATE_PATTERN = "recreate_pattern_lock"; + + private static final int MIN_PATTERN_SIZE = 4; + private static final int MAX_PATTERN_RETRY = 5; + private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; + + private static final int MENU_RESET = 0; + + LockPatternView mLockPatternView; + ProtectedAccountView mAccountView; + + TextView mPatternLockHeader; + MenuItem mItem; + Button mCancel; + Button mContinue; + byte[] mPatternHash; + + int mRetry = 0; + + boolean mCreate; + boolean mRetryPattern = true; + boolean mConfirming = false; + + Runnable mCancelPatternRunnable = new Runnable() { + public void run() { + mLockPatternView.clearPattern(); + mContinue.setEnabled(false); + + if (mCreate) { + if (mConfirming) { + mPatternLockHeader.setText(getResources() + .getString(R.string.lockpattern_need_to_confirm)); + } else { + mPatternLockHeader.setText(getResources() + .getString(R.string.lockpattern_recording_intro_header)); + mCancel.setText(getResources().getString(R.string.cancel)); + } + } else { + mPatternLockHeader.setText(getResources() + .getString(R.string.lockpattern_settings_enable_summary)); + } + } + }; + + View.OnClickListener mCancelOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mCreate && !mConfirming && !mRetryPattern) { + // Retry + mRetryPattern = true; + resetPatternState(true); + return; + } + setResult(RESULT_CANCELED); + finish(); + } + }; + + View.OnClickListener mContinueOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + Button btn = (Button) v; + if (mConfirming) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(PATTERN_LOCK_PROTECTED_APPS, + Base64.encodeToString(mPatternHash, Base64.DEFAULT)); + editor.commit(); + setResult(RESULT_OK); + finish(); + } else { + mConfirming = true; + mCancel.setText(getResources().getString(R.string.cancel)); + mLockPatternView.clearPattern(); + + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_need_to_confirm)); + btn.setText(getResources().getString(R.string.lockpattern_confirm_button_text)); + btn.setEnabled(false); + } + } + }; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.clear(); + if (!mCreate) { + menu.add(0, MENU_RESET, 0, R.string.lockpattern_reset_button) + .setIcon(R.drawable.ic_lockscreen_ime) + .setAlphabeticShortcut('r') + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + mItem = menu.findItem(0); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_RESET: + if (mAccountView.getVisibility() == View.VISIBLE) { + switchToPattern(false); + } else { + switchToAccount(); + } + return true; + default: + return false; + } + } + + @Override + public void onNotifyAccountReset() { + switchToPattern(true); + } + + private void switchToPattern(boolean reset) { + if (reset) { + resetPatternState(false); + } + mPatternLockHeader.setText(getResources() + .getString(R.string.lockpattern_settings_enable_summary)); + mItem.setIcon(R.drawable.ic_lockscreen_ime); + mAccountView.clearFocusOnInput(); + mAccountView.setVisibility(View.GONE); + mLockPatternView.setVisibility(View.VISIBLE); + } + + private void switchToAccount() { + mPatternLockHeader.setText(getResources() + .getString(R.string.lockpattern_settings_reset_summary)); + mItem.setIcon(R.drawable.ic_settings_lockscreen); + mAccountView.setVisibility(View.VISIBLE); + mLockPatternView.setVisibility(View.GONE); + } + + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.patternlock); + + mPatternLockHeader = (TextView) findViewById(R.id.pattern_lock_header); + mCancel = (Button) findViewById(R.id.pattern_lock_btn_cancel); + mCancel.setOnClickListener(mCancelOnClickListener); + mContinue = (Button) findViewById(R.id.pattern_lock_btn_continue); + mContinue.setOnClickListener(mContinueOnClickListener); + + mAccountView = (ProtectedAccountView) findViewById(R.id.lock_account_view); + mAccountView.setOnNotifyAccountResetCb(this); + mLockPatternView = (LockPatternView) findViewById(R.id.lock_pattern_view); + + resetPatternState(false); + + //Setup Pattern Lock View + mLockPatternView.setSaveEnabled(false); + mLockPatternView.setFocusable(false); + mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + + } + + private void resetPatternState(boolean clear) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + String pattern = prefs.getString(PATTERN_LOCK_PROTECTED_APPS, null); + mCreate = pattern == null || RECREATE_PATTERN.equals(getIntent().getAction()) + || clear; + + mPatternHash = null; + if (pattern != null) { + mPatternHash = Base64.decode(pattern, Base64.DEFAULT); + } + + mContinue.setEnabled(!mCreate); + mCancel.setVisibility(mCreate ? View.VISIBLE : View.GONE); + mCancel.setText(getResources().getString(R.string.cancel)); + mContinue.setVisibility(mCreate ? View.VISIBLE : View.GONE); + mPatternLockHeader.setText(mCreate + ? getResources().getString(R.string.lockpattern_recording_intro_header) + : getResources().getString(R.string.lockpattern_settings_enable_summary)); + mLockPatternView.clearPattern(); + + invalidateOptionsMenu(); + } + + private class UnlockPatternListener implements LockPatternView.OnPatternListener { + + public void onPatternStart() { + mLockPatternView.removeCallbacks(mCancelPatternRunnable); + + mPatternLockHeader.setText(getResources().getText( + R.string.lockpattern_recording_inprogress)); + mContinue.setEnabled(false); + } + + public void onPatternCleared() { + } + + public void onPatternDetected(List<LockPatternView.Cell> pattern) { + //Check inserted Pattern + if (mCreate) { + if (pattern.size() < MIN_PATTERN_SIZE) { + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_recording_incorrect_too_short, + LockPatternUtils.MIN_LOCK_PATTERN_SIZE)); + + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); + mCancel.setText(getResources() + .getString(R.string.lockpattern_retry_button_text)); + mRetryPattern = false; + return; + } + + if (mConfirming) { + if (Arrays.equals(mPatternHash, patternToHash(pattern))) { + mContinue.setText(getResources() + .getString(R.string.lockpattern_confirm_button_text)); + mContinue.setEnabled(true); + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_pattern_confirmed_header)); + } else { + mContinue.setEnabled(false); + + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_need_to_unlock_wrong)); + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + mLockPatternView.postDelayed(mCancelPatternRunnable, + PATTERN_CLEAR_TIMEOUT_MS); + } + } else { + //Save pattern, user needs to redraw to confirm + mCancel.setText(getResources() + .getString(R.string.lockpattern_retry_button_text)); + mRetryPattern = false; + + mPatternHash = patternToHash(pattern); + + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_pattern_entered_header)); + mContinue.setEnabled(true); + } + } else { + //Check against existing pattern + if (Arrays.equals(mPatternHash, patternToHash(pattern))) { + setResult(RESULT_OK); + finish(); + } else { + mRetry++; + mPatternLockHeader.setText(getResources().getString( + R.string.lockpattern_need_to_unlock_wrong)); + + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); + + if (mRetry >= MAX_PATTERN_RETRY) { + mLockPatternView.removeCallbacks(mCancelPatternRunnable); + Toast.makeText(getApplicationContext(), + getResources().getString( + R.string.lockpattern_too_many_failed_confirmation_attempts), + Toast.LENGTH_SHORT).show(); + switchToAccount(); + } + } + } + } + + public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {} + } + + /* + * Generate an SHA-1 hash for the pattern. Not the most secure, but it is + * at least a second level of protection. First level is that the file + * is in a location only readable by the system process. + * @param pattern the gesture pattern. + * @return the hash of the pattern in a byte array. + */ + public byte[] patternToHash(List<LockPatternView.Cell> pattern) { + if (pattern == null) { + return null; + } + + final int patternSize = pattern.size(); + byte[] res = new byte[patternSize]; + for (int i = 0; i < patternSize; i++) { + LockPatternView.Cell cell = pattern.get(i); + res[i] = (byte) (cell.getRow() * 3 + cell.getColumn()); + } + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] hash = md.digest(res); + return hash; + } catch (NoSuchAlgorithmException nsa) { + return res; + } + } +} diff --git a/src/com/android/settings/applications/ProtectedAppsActivity.java b/src/com/android/settings/applications/ProtectedAppsActivity.java new file mode 100644 index 0000000..5ff459a --- /dev/null +++ b/src/com/android/settings/applications/ProtectedAppsActivity.java @@ -0,0 +1,481 @@ +/* + * 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 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 HashSet<ComponentName> mProtectedApps = new HashSet<ComponentName>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + 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); + } + + if (!mUserIsAuth) { + // Require unlock + Intent lockPattern = new Intent(this, LockPatternActivity.class); + startActivityForResult(lockPattern, REQ_ENTER_PATTERN); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(NEEDS_UNLOCK, mUserIsAuth); + } + + @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.System.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(); + + // Don't stick around + if (mWaitUserAuth && !mUserIsAuth) { + 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 = true; + switch (resultCode) { + case RESULT_OK: + //Nothing to do, proceed! + mUserIsAuth = true; + break; + case RESULT_CANCELED: + // user failed to define a pattern, do not lock the folder + finish(); + break; + } + break; + case REQ_RESET_PATTERN: + mWaitUserAuth = true; + mUserIsAuth = false; + } + } + + @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.System.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 = false; + 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); + } + }; +} diff --git a/src/com/android/settings/cyanogenmod/ProtectedAccountView.java b/src/com/android/settings/cyanogenmod/ProtectedAccountView.java new file mode 100644 index 0000000..4b9e14e --- /dev/null +++ b/src/com/android/settings/cyanogenmod/ProtectedAccountView.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2014 The CyanogenMod 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.cyanogenmod; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.UserHandle; +import android.preference.PreferenceManager; +import android.text.InputFilter; +import android.text.LoginFilter; +import android.util.AttributeSet; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Toast; +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.R; +import com.android.settings.applications.LockPatternActivity; + +import java.io.IOException; + +/** + * When the user forgets their password a bunch of times, we fall back on their + * account's login/password to unlock protected apps (and reset their lock pattern). + */ +public class ProtectedAccountView extends LinearLayout implements View.OnClickListener { + + public static interface OnNotifyAccountReset { + void onNotifyAccountReset(); + } + + private EditText mLogin; + private EditText mPassword; + private Button mOk; + private Context mContext; + private LockPatternUtils mLockPatternUtils; + private OnNotifyAccountReset mNotifyAccountResetCb; + + /** + * Shown while making asynchronous check of password. + */ + private ProgressDialog mCheckingDialog; + + public ProtectedAccountView(Context context) { + this(context, null); + } + + public ProtectedAccountView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ProtectedAccountView(Context context, AttributeSet st, int ds) { + super(context, st, ds); + mContext = context; + mLockPatternUtils = new LockPatternUtils(mContext); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mLogin = (EditText) findViewById(R.id.login); + mLogin.setFilters(new InputFilter[] { new LoginFilter.UsernameFilterGeneric() } ); + mPassword = (EditText) findViewById(R.id.password); + + mOk = (Button) findViewById(R.id.ok); + mOk.setOnClickListener(this); + + reset(); + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, + Rect previouslyFocusedRect) { + // send focus to the login field + return mLogin.requestFocus(direction, previouslyFocusedRect); + } + + public boolean needsInput() { + return true; + } + + public void setOnNotifyAccountResetCb(OnNotifyAccountReset callback) { + this.mNotifyAccountResetCb = callback; + } + + public void clearFocusOnInput() { + mLogin.clearFocus(); + mPassword.clearFocus(); + + // hide keyboard + final InputMethodManager imm = (InputMethodManager) + mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mLogin.getWindowToken(), 0); + imm.hideSoftInputFromWindow(mPassword.getWindowToken(), 0); + } + + public void reset() { + mLogin.setText(""); + mPassword.setText(""); + mLogin.requestFocus(); + } + + /** {@inheritDoc} */ + public void cleanUp() { + if (mCheckingDialog != null) { + mCheckingDialog.hide(); + } + } + + public void onClick(View v) { + if (v == mOk) { + asyncCheckPassword(); + } + } + + private void postOnCheckPasswordResult(final boolean success) { + // ensure this runs on UI thread + mLogin.post(new Runnable() { + public void run() { + if (success) { + + Activity baseActivity = (Activity) mContext; + + if (!baseActivity.isFinishing()) { + // Remove pattern + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(mContext); + SharedPreferences.Editor editor = prefs.edit(); + editor.remove(LockPatternActivity.PATTERN_LOCK_PROTECTED_APPS); + editor.commit(); + + if (mNotifyAccountResetCb != null) { + mNotifyAccountResetCb.onNotifyAccountReset(); + } else { + baseActivity.setResult(Activity.RESULT_OK); + baseActivity.finish(); + } + } + } else { + Toast.makeText(mContext, + getResources().getString( + R.string.pa_login_incorrect_login), + Toast.LENGTH_SHORT).show(); + mPassword.setText(""); + } + } + }); + } + + /** + * Given the string the user entered in the 'username' field, find + * the stored account that they probably intended. Prefer, in order: + * + * - an exact match for what was typed, or + * - a case-insensitive match for what was typed, or + * - if they didn't include a domain, an exact match of the username, or + * - if they didn't include a domain, a case-insensitive + * match of the username. + * + * If there is a tie for the best match, choose neither -- + * the user needs to be more specific. + * + * @return an account name from the database, or null if we can't + * find a single best match. + */ + private Account findIntendedAccount(String username) { + Account[] accounts = AccountManager.get(mContext).getAccountsByTypeAsUser("com.google", + new UserHandle(ActivityManager.getCurrentUser())); + + // Try to figure out which account they meant if they + // typed only the username (and not the domain), or got + // the case wrong. + + Account bestAccount = null; + int bestScore = 0; + for (Account a: accounts) { + int score = 0; + if (username.equals(a.name)) { + score = 4; + } else if (username.equalsIgnoreCase(a.name)) { + score = 3; + } else if (username.indexOf('@') < 0) { + int i = a.name.indexOf('@'); + if (i >= 0) { + String aUsername = a.name.substring(0, i); + if (username.equals(aUsername)) { + score = 2; + } else if (username.equalsIgnoreCase(aUsername)) { + score = 1; + } + } + } + if (score > bestScore) { + bestAccount = a; + bestScore = score; + } else if (score == bestScore) { + bestAccount = null; + } + } + return bestAccount; + } + + private void asyncCheckPassword() { + final String login = mLogin.getText().toString(); + final String password = mPassword.getText().toString(); + Account account = findIntendedAccount(login); + if (account == null) { + postOnCheckPasswordResult(false); + return; + } + getProgressDialog().show(); + Bundle options = new Bundle(); + options.putString(AccountManager.KEY_PASSWORD, password); + AccountManager.get(mContext).confirmCredentialsAsUser(account, options, null /* activity */, + new AccountManagerCallback<Bundle>() { + public void run(AccountManagerFuture<Bundle> future) { + try { + final Bundle result = future.getResult(); + final boolean verified = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); + postOnCheckPasswordResult(verified); + } catch (OperationCanceledException e) { + postOnCheckPasswordResult(false); + } catch (IOException e) { + postOnCheckPasswordResult(false); + } catch (AuthenticatorException e) { + postOnCheckPasswordResult(false); + } finally { + mLogin.post(new Runnable() { + public void run() { + getProgressDialog().hide(); + } + }); + } + } + }, null /* handler */, new UserHandle(ActivityManager.getCurrentUser())); + } + + private Dialog getProgressDialog() { + if (mCheckingDialog == null) { + mCheckingDialog = new ProgressDialog(mContext); + mCheckingDialog.setMessage( + mContext.getString(R.string.pa_login_checking_password)); + mCheckingDialog.setIndeterminate(true); + mCheckingDialog.setCancelable(false); + mCheckingDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + return mCheckingDialog; + } +} + diff --git a/src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java b/src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java new file mode 100644 index 0000000..0b2e56f --- /dev/null +++ b/src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java @@ -0,0 +1,118 @@ +/* + * 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.cyanogenmod; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.provider.Settings; +import android.util.Log; + +import static cyanogenmod.content.Intent.ACTION_PROTECTED; +import static cyanogenmod.content.Intent.ACTION_PROTECTED_CHANGED; +import static cyanogenmod.content.Intent.EXTRA_PROTECTED_COMPONENTS; +import static cyanogenmod.content.Intent.EXTRA_PROTECTED_STATE; + +import cyanogenmod.providers.CMSettings; + +import java.util.ArrayList; +import java.util.HashSet; + +public class ProtectedAppsReceiver extends BroadcastReceiver { + private static final String TAG = "ProtectedAppsReceiver"; + + private static final String PROTECTED_APP_PERMISSION = cyanogenmod.platform.Manifest + .permission.PROTECTED_APP; + + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_PROTECTED.equals(intent.getAction())) { + boolean protect = intent.getBooleanExtra(EXTRA_PROTECTED_STATE, + PackageManager.COMPONENT_VISIBLE_STATUS); + ArrayList<ComponentName> components = + intent.getParcelableArrayListExtra(EXTRA_PROTECTED_COMPONENTS); + if (components != null) { + updateProtectedAppComponentsAndNotify(context, components, protect); + } + } + } + + public static void updateProtectedAppComponentsAndNotify(Context context, + ArrayList<ComponentName> components, boolean state) { + updateProtectedAppComponents(context, components, state); + updateSettingsSecure(context, components, state); + notifyProtectedChanged(context, components, state); + } + + public static void updateProtectedAppComponents(Context context, + ArrayList<ComponentName> components, boolean state) { + PackageManager pm = context.getPackageManager(); + + for (ComponentName component : components) { + try { + pm.setComponentProtectedSetting(component, state); + } catch (NoSuchMethodError nsm) { + Log.e(TAG, "Unable to protected app via PackageManager"); + } + } + } + + public static void updateSettingsSecure(Context context, + ArrayList<ComponentName> components, boolean state) { + ContentResolver resolver = context.getContentResolver(); + String hiddenComponents = CMSettings.System.getString(resolver, + CMSettings.Secure.PROTECTED_COMPONENTS); + HashSet<ComponentName> newComponentList = new HashSet<ComponentName>(); + + if (hiddenComponents != null) { + for (String flattened : hiddenComponents.split("\\|")) { + ComponentName cmp = ComponentName.unflattenFromString(flattened); + if (cmp != null) { + newComponentList.add(cmp); + } + } + } + + boolean update = state == PackageManager.COMPONENT_PROTECTED_STATUS + ? newComponentList.addAll(components) + : newComponentList.removeAll(components); + + if (update) { + StringBuilder flattenedList = new StringBuilder(); + for (ComponentName cmp : newComponentList) { + if (flattenedList.length() > 0) { + flattenedList.append("|"); + } + flattenedList.append(cmp.flattenToString()); + } + CMSettings.System.putString(resolver, CMSettings.Secure.PROTECTED_COMPONENTS, + flattenedList.toString()); + } + } + + public static void notifyProtectedChanged(Context context, + ArrayList<ComponentName> components, boolean state) { + Intent intent = new Intent(ACTION_PROTECTED_CHANGED); + intent.putExtra(EXTRA_PROTECTED_STATE, state); + intent.putExtra(EXTRA_PROTECTED_COMPONENTS, components); + + context.sendBroadcast(intent, PROTECTED_APP_PERMISSION); + } +} diff --git a/src/com/android/settings/widget/CheckableLinearLayout.java b/src/com/android/settings/widget/CheckableLinearLayout.java new file mode 100644 index 0000000..a4b9a7d --- /dev/null +++ b/src/com/android/settings/widget/CheckableLinearLayout.java @@ -0,0 +1,64 @@ +/* + * 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.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.CheckBox; +import android.widget.Checkable; +import android.widget.LinearLayout; +import com.android.settings.R; + +/* + * This class is useful for using inside of ListView that needs to have checkable items. + */ +public class CheckableLinearLayout extends LinearLayout implements Checkable { + private CheckBox mCheckBox; + + public CheckableLinearLayout(Context context) { + super(context); + } + + public CheckableLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mCheckBox = (CheckBox) findViewById(R.id.checkbox); + } + + @Override + public boolean isChecked() { + return mCheckBox.isChecked(); + } + + @Override + public void setChecked(boolean checked) { + mCheckBox.setChecked(checked); + } + + @Override + public void toggle() { + mCheckBox.toggle(); + } +}
\ No newline at end of file diff --git a/src/com/android/settings/widget/InertCheckBox.java b/src/com/android/settings/widget/InertCheckBox.java new file mode 100644 index 0000000..82a376f --- /dev/null +++ b/src/com/android/settings/widget/InertCheckBox.java @@ -0,0 +1,84 @@ +/* + * 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.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.widget.CheckBox; + + +// CheckBox that does not react to any user event in order to let the container handle them. +public class InertCheckBox extends CheckBox { + + @SuppressWarnings("unused") + public InertCheckBox(Context context) { + super(context); + } + + @SuppressWarnings("unused") + public InertCheckBox(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @SuppressWarnings("unused") + public InertCheckBox(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + // Make the checkbox not respond to any user event + return false; + } +}
\ No newline at end of file |