diff options
Diffstat (limited to 'src/com/android')
92 files changed, 7853 insertions, 3487 deletions
diff --git a/src/com/android/settings/AccessibilitySettings.java b/src/com/android/settings/AccessibilitySettings.java index 827af13..149e336 100644 --- a/src/com/android/settings/AccessibilitySettings.java +++ b/src/com/android/settings/AccessibilitySettings.java @@ -30,12 +30,12 @@ import android.content.SharedPreferences; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Configuration; +import android.database.ContentObserver; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemProperties; import android.preference.CheckBoxPreference; import android.preference.ListPreference; @@ -46,15 +46,12 @@ import android.preference.PreferenceScreen; import android.provider.Settings; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; -import android.util.Log; import android.view.Gravity; -import android.view.IWindowManager; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.Surface; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -63,6 +60,7 @@ import android.widget.Switch; import android.widget.TextView; import com.android.internal.content.PackageMonitor; +import com.android.internal.view.RotationPolicy; import com.android.settings.AccessibilitySettings.ToggleSwitch.OnBeforeCheckedChangeListener; import java.util.HashMap; @@ -76,8 +74,6 @@ import java.util.Set; */ public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable, Preference.OnPreferenceChangeListener { - private static final String TAG = "AccessibilitySettings"; - private static final String DEFAULT_SCREENREADER_MARKET_LINK = "market://search?q=pname:com.google.android.marvin.talkback"; @@ -92,9 +88,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':'; - private static final String KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE = - "key_accessibility_tutorial_launched_once"; - private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE = "key_install_accessibility_service_offered_once"; @@ -106,12 +99,10 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements private static final String TOGGLE_LARGE_TEXT_PREFERENCE = "toggle_large_text_preference"; private static final String TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE = "toggle_power_button_ends_call_preference"; - private static final String TOGGLE_AUTO_ROTATE_SCREEN_PREFERENCE = - "toggle_auto_rotate_screen_preference"; + private static final String TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE = + "toggle_lock_screen_rotation_preference"; private static final String TOGGLE_SPEAK_PASSWORD_PREFERENCE = "toggle_speak_password_preference"; - private static final String TOGGLE_TOUCH_EXPLORATION_PREFERENCE = - "toggle_touch_exploration_preference"; private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE = "select_long_press_timeout_preference"; private static final String TOGGLE_SCRIPT_INJECTION_PREFERENCE = @@ -154,15 +145,22 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements } }; + private final RotationPolicy.RotationPolicyListener mRotationPolicyListener = + new RotationPolicy.RotationPolicyListener() { + @Override + public void onChange() { + updateLockScreenRotationCheckbox(); + } + }; + // Preference controls. private PreferenceCategory mServicesCategory; private PreferenceCategory mSystemsCategory; private CheckBoxPreference mToggleLargeTextPreference; private CheckBoxPreference mTogglePowerButtonEndsCallPreference; - private CheckBoxPreference mToggleAutoRotateScreenPreference; + private CheckBoxPreference mToggleLockScreenRotationPreference; private CheckBoxPreference mToggleSpeakPasswordPreference; - private Preference mToggleTouchExplorationPreference; private ListPreference mSelectLongPressTimeoutPreference; private AccessibilityEnableScriptInjectionPreference mToggleScriptInjectionPreference; private Preference mNoServicesMessagePreference; @@ -184,12 +182,16 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements if (mServicesCategory.getPreference(0) == mNoServicesMessagePreference) { offerInstallAccessibilitySerivceOnce(); } - mSettingsPackageMonitor.register(getActivity(), false); + mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false); + RotationPolicy.registerRotationPolicyListener(getActivity(), + mRotationPolicyListener); } @Override public void onPause() { mSettingsPackageMonitor.unregister(); + RotationPolicy.unregisterRotationPolicyListener(getActivity(), + mRotationPolicyListener); super.onPause(); } @@ -213,8 +215,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements } else if (mTogglePowerButtonEndsCallPreference == preference) { handleTogglePowerButtonEndsCallPreferenceClick(); return true; - } else if (mToggleAutoRotateScreenPreference == preference) { - handleToggleAutoRotateScreenPreferenceClick(); + } else if (mToggleLockScreenRotationPreference == preference) { + handleLockScreenRotationPreferenceClick(); return true; } else if (mToggleSpeakPasswordPreference == preference) { handleToggleSpeakPasswordPreferenceClick(); @@ -239,18 +241,9 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF)); } - private void handleToggleAutoRotateScreenPreferenceClick() { - try { - IWindowManager wm = IWindowManager.Stub.asInterface( - ServiceManager.getService(Context.WINDOW_SERVICE)); - if (mToggleAutoRotateScreenPreference.isChecked()) { - wm.thawRotation(); - } else { - wm.freezeRotation(Surface.ROTATION_0); - } - } catch (RemoteException exc) { - Log.w(TAG, "Unable to save auto-rotate setting"); - } + private void handleLockScreenRotationPreferenceClick() { + RotationPolicy.setRotationLockForAccessibility(getActivity(), + !mToggleLockScreenRotationPreference.isChecked()); } private void handleToggleSpeakPasswordPreferenceClick() { @@ -275,17 +268,14 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements mSystemsCategory.removePreference(mTogglePowerButtonEndsCallPreference); } - // Auto-rotate screen - mToggleAutoRotateScreenPreference = - (CheckBoxPreference) findPreference(TOGGLE_AUTO_ROTATE_SCREEN_PREFERENCE); + // Lock screen rotation. + mToggleLockScreenRotationPreference = + (CheckBoxPreference) findPreference(TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE); // Speak passwords. mToggleSpeakPasswordPreference = (CheckBoxPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE); - // Touch exploration enabled. - mToggleTouchExplorationPreference = findPreference(TOGGLE_TOUCH_EXPLORATION_PREFERENCE); - // Long press timeout. mSelectLongPressTimeoutPreference = (ListPreference) findPreference(SELECT_LONG_PRESS_TIMEOUT_PREFERENCE); @@ -352,7 +342,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements } preference.setOrder(i); - preference.setFragment(ToggleAccessibilityServiceFragment.class.getName()); + preference.setFragment(ToggleAccessibilityServicePreferenceFragment.class.getName()); preference.setPersistent(true); Bundle extras = preference.getExtras(); @@ -360,7 +350,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements extras.putBoolean(EXTRA_CHECKED, serviceEnabled); extras.putString(EXTRA_TITLE, title); - String description = info.getDescription(); + String description = info.loadDescription(getPackageManager()); if (TextUtils.isEmpty(description)) { description = getString(R.string.accessibility_service_default_description); } @@ -438,34 +428,13 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements } // Auto-rotate screen - final boolean autoRotationEnabled = Settings.System.getInt(getContentResolver(), - Settings.System.ACCELEROMETER_ROTATION, 0) != 0; - mToggleAutoRotateScreenPreference.setChecked(autoRotationEnabled); + updateLockScreenRotationCheckbox(); // Speak passwords. final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; mToggleSpeakPasswordPreference.setChecked(speakPasswordEnabled); - // Touch exploration enabled. - if (AccessibilityManager.getInstance(getActivity()).isEnabled()) { - mSystemsCategory.addPreference(mToggleTouchExplorationPreference); - final boolean touchExplorationEnabled = (Settings.Secure.getInt(getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1); - if (touchExplorationEnabled) { - mToggleTouchExplorationPreference.setSummary( - getString(R.string.accessibility_service_state_on)); - mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, true); - } else { - mToggleTouchExplorationPreference.setSummary( - getString(R.string.accessibility_service_state_off)); - mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, false); - } - - } else { - mSystemsCategory.removePreference(mToggleTouchExplorationPreference); - } - // Long press timeout. final int longPressTimeout = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LONG_PRESS_TIMEOUT, mLongPressTimeoutDefault); @@ -479,6 +448,11 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements mToggleScriptInjectionPreference.setInjectionAllowed(scriptInjectionAllowed); } + private void updateLockScreenRotationCheckbox() { + mToggleLockScreenRotationPreference.setChecked( + !RotationPolicy.isRotationLocked(getActivity())); + } + private void offerInstallAccessibilitySerivceOnce() { // There is always one preference - if no services it is just a message. if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) { @@ -632,79 +606,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements } } - public static class ToggleAccessibilityServiceFragment extends TogglePreferenceFragment { - @Override - public void onPreferenceToggled(String preferenceKey, boolean enabled) { - // Parse the enabled services. - Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity()); - - // Determine enabled services and accessibility state. - ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey); - final boolean accessibilityEnabled; - if (enabled) { - // Enabling at least one service enables accessibility. - accessibilityEnabled = true; - enabledServices.add(toggledService); - } else { - // Check how many enabled and installed services are present. - int enabledAndInstalledServiceCount = 0; - Set<ComponentName> installedServices = sInstalledServices; - for (ComponentName enabledService : enabledServices) { - if (installedServices.contains(enabledService)) { - enabledAndInstalledServiceCount++; - } - } - // Disabling the last service disables accessibility. - accessibilityEnabled = enabledAndInstalledServiceCount > 1 - || (enabledAndInstalledServiceCount == 1 - && !installedServices.contains(toggledService)); - enabledServices.remove(toggledService); - } - - // Update the enabled services setting. - StringBuilder enabledServicesBuilder = new StringBuilder(); - // Keep the enabled services even if they are not installed since we have - // no way to know whether the application restore process has completed. - // In general the system should be responsible for the clean up not settings. - for (ComponentName enabledService : enabledServices) { - enabledServicesBuilder.append(enabledService.flattenToString()); - enabledServicesBuilder.append(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); - } - final int enabledServicesBuilderLength = enabledServicesBuilder.length(); - if (enabledServicesBuilderLength > 0) { - enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1); - } - Settings.Secure.putString(getContentResolver(), - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - enabledServicesBuilder.toString()); - - // Update accessibility enabled. - Settings.Secure.putInt(getContentResolver(), - Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0); - } - } - - public static class ToggleTouchExplorationFragment extends TogglePreferenceFragment { - @Override - public void onPreferenceToggled(String preferenceKey, boolean enabled) { - Settings.Secure.putInt(getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0); - if (enabled) { - SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE); - final boolean launchAccessibilityTutorial = !preferences.getBoolean( - KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE, false); - if (launchAccessibilityTutorial) { - preferences.edit().putBoolean(KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE, - true).commit(); - Intent intent = new Intent(AccessibilityTutorialActivity.ACTION); - getActivity().startActivity(intent); - } - } - } - } - - private abstract static class TogglePreferenceFragment extends SettingsPreferenceFragment - implements DialogInterface.OnClickListener { + public static class ToggleAccessibilityServicePreferenceFragment + extends SettingsPreferenceFragment implements DialogInterface.OnClickListener { private static final int DIALOG_ID_ENABLE_WARNING = 1; private static final int DIALOG_ID_DISABLE_WARNING = 2; @@ -717,6 +620,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements private CharSequence mEnableWarningMessage; private CharSequence mDisableWarningTitle; private CharSequence mDisableWarningMessage; + private Preference mSummaryPreference; private CharSequence mSettingsTitle; @@ -782,7 +686,54 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements super.onDestroyView(); } - public abstract void onPreferenceToggled(String preferenceKey, boolean value); + public void onPreferenceToggled(String preferenceKey, boolean enabled) { + // Parse the enabled services. + Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity()); + + // Determine enabled services and accessibility state. + ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey); + final boolean accessibilityEnabled; + if (enabled) { + // Enabling at least one service enables accessibility. + accessibilityEnabled = true; + enabledServices.add(toggledService); + } else { + // Check how many enabled and installed services are present. + int enabledAndInstalledServiceCount = 0; + Set<ComponentName> installedServices = sInstalledServices; + for (ComponentName enabledService : enabledServices) { + if (installedServices.contains(enabledService)) { + enabledAndInstalledServiceCount++; + } + } + // Disabling the last service disables accessibility. + accessibilityEnabled = enabledAndInstalledServiceCount > 1 + || (enabledAndInstalledServiceCount == 1 + && !installedServices.contains(toggledService)); + enabledServices.remove(toggledService); + } + + // Update the enabled services setting. + StringBuilder enabledServicesBuilder = new StringBuilder(); + // Keep the enabled services even if they are not installed since we have + // no way to know whether the application restore process has completed. + // In general the system should be responsible for the clean up not settings. + for (ComponentName enabledService : enabledServices) { + enabledServicesBuilder.append(enabledService.flattenToString()); + enabledServicesBuilder.append(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); + } + final int enabledServicesBuilderLength = enabledServicesBuilder.length(); + if (enabledServicesBuilderLength > 0) { + enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1); + } + Settings.Secure.putString(getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + enabledServicesBuilder.toString()); + + // Update accessibility enabled. + Settings.Secure.putInt(getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0); + } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { diff --git a/src/com/android/settings/AccessibilityTutorialActivity.java b/src/com/android/settings/AccessibilityTutorialActivity.java deleted file mode 100644 index 206aa94..0000000 --- a/src/com/android/settings/AccessibilityTutorialActivity.java +++ /dev/null @@ -1,698 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * - * 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; - -import android.app.Activity; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.provider.Settings; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.AnimationUtils; -import android.widget.AbsListView; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.GridView; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.ViewAnimator; - -import com.android.settings.R; - -import java.util.List; - -/** - * This class provides a short tutorial that introduces the user to the features - * available in Touch Exploration. - */ -public class AccessibilityTutorialActivity extends Activity { - - /** Intent action for launching this activity. */ - public static final String ACTION = "android.settings.ACCESSIBILITY_TUTORIAL"; - - /** Instance state saving constant for the active module. */ - private static final String KEY_ACTIVE_MODULE = "active_module"; - - /** The index of the module to show when first opening the tutorial. */ - private static final int DEFAULT_MODULE = 0; - - /** View animator for switching between modules. */ - private ViewAnimator mViewAnimator; - - private AccessibilityManager mAccessibilityManager; - - /** Should touch exploration be disabled when this activity is paused? */ - private boolean mDisableOnPause; - - private final AnimationListener mInAnimationListener = new AnimationListener() { - @Override - public void onAnimationEnd(Animation animation) { - final int index = mViewAnimator.getDisplayedChild(); - final TutorialModule module = (TutorialModule) mViewAnimator.getChildAt(index); - - activateModule(module); - } - - @Override - public void onAnimationRepeat(Animation animation) { - // Do nothing. - } - - @Override - public void onAnimationStart(Animation animation) { - // Do nothing. - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final Animation inAnimation = AnimationUtils.loadAnimation(this, - android.R.anim.slide_in_left); - inAnimation.setAnimationListener(mInAnimationListener); - - final Animation outAnimation = AnimationUtils.loadAnimation(this, - android.R.anim.slide_in_left); - - mViewAnimator = new ViewAnimator(this); - mViewAnimator.setInAnimation(inAnimation); - mViewAnimator.setOutAnimation(outAnimation); - mViewAnimator.addView(new TouchTutorialModule1(this, this)); - mViewAnimator.addView(new TouchTutorialModule2(this, this)); - - setContentView(mViewAnimator); - - mAccessibilityManager = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE); - - if (savedInstanceState != null) { - show(savedInstanceState.getInt(KEY_ACTIVE_MODULE, DEFAULT_MODULE)); - } else { - show(DEFAULT_MODULE); - } - } - - @Override - protected void onResume() { - super.onResume(); - - final ContentResolver cr = getContentResolver(); - - if (Settings.Secure.getInt(cr, Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 0) { - Settings.Secure.putInt(cr, Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1); - mDisableOnPause = true; - } else { - mDisableOnPause = false; - } - } - - @Override - protected void onPause() { - super.onPause(); - - if (mDisableOnPause) { - final ContentResolver cr = getContentResolver(); - Settings.Secure.putInt(cr, Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putInt(KEY_ACTIVE_MODULE, mViewAnimator.getDisplayedChild()); - } - - private void activateModule(TutorialModule module) { - module.activate(); - } - - private void deactivateModule(TutorialModule module) { - mAccessibilityManager.interrupt(); - mViewAnimator.setOnKeyListener(null); - module.deactivate(); - } - - private void interrupt() { - mAccessibilityManager.interrupt(); - } - - private void next() { - show(mViewAnimator.getDisplayedChild() + 1); - } - - private void previous() { - show(mViewAnimator.getDisplayedChild() - 1); - } - - private void show(int which) { - if ((which < 0) || (which >= mViewAnimator.getChildCount())) { - return; - } - - mAccessibilityManager.interrupt(); - - final int displayedIndex = mViewAnimator.getDisplayedChild(); - final TutorialModule displayedView = (TutorialModule) mViewAnimator.getChildAt( - displayedIndex); - deactivateModule(displayedView); - - mViewAnimator.setDisplayedChild(which); - } - - /** - * Loads application labels and icons. - */ - private static class AppsAdapter extends ArrayAdapter<ResolveInfo> { - protected final int mTextViewResourceId; - - private final int mIconSize; - private final View.OnHoverListener mDefaultHoverListener; - - private View.OnHoverListener mHoverListener; - - public AppsAdapter(Context context, int resource, int textViewResourceId) { - super(context, resource, textViewResourceId); - - mIconSize = context.getResources().getDimensionPixelSize(R.dimen.app_icon_size); - mTextViewResourceId = textViewResourceId; - mDefaultHoverListener = new View.OnHoverListener() { - @Override - public boolean onHover(View v, MotionEvent event) { - if (mHoverListener != null) { - return mHoverListener.onHover(v, event); - } else { - return false; - } - } - }; - - loadAllApps(); - } - - public CharSequence getLabel(int position) { - final PackageManager packageManager = getContext().getPackageManager(); - final ResolveInfo appInfo = getItem(position); - return appInfo.loadLabel(packageManager); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final PackageManager packageManager = getContext().getPackageManager(); - final View view = super.getView(position, convertView, parent); - view.setOnHoverListener(mDefaultHoverListener); - view.setTag(position); - - final ResolveInfo appInfo = getItem(position); - final CharSequence label = appInfo.loadLabel(packageManager); - final Drawable icon = appInfo.loadIcon(packageManager); - final TextView text = (TextView) view.findViewById(mTextViewResourceId); - - icon.setBounds(0, 0, mIconSize, mIconSize); - - populateView(text, label, icon); - - return view; - } - - private void loadAllApps() { - final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); - mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); - - final PackageManager pm = getContext().getPackageManager(); - final List<ResolveInfo> apps = pm.queryIntentActivities(mainIntent, 0); - - addAll(apps); - } - - protected void populateView(TextView text, CharSequence label, Drawable icon) { - text.setText(label); - text.setCompoundDrawables(null, icon, null, null); - } - - public void setOnHoverListener(View.OnHoverListener hoverListener) { - mHoverListener = hoverListener; - } - } - - /** - * Introduces using a finger to explore and interact with on-screen content. - */ - private static class TouchTutorialModule1 extends TutorialModule implements - View.OnHoverListener, AdapterView.OnItemClickListener { - /** - * Handles the case where the user overshoots the target area. - */ - private class HoverTargetHandler extends Handler { - private static final int MSG_ENTERED_TARGET = 1; - private static final int DELAY_ENTERED_TARGET = 500; - - private boolean mInsideTarget = false; - - public void enteredTarget() { - mInsideTarget = true; - mHandler.sendEmptyMessageDelayed(MSG_ENTERED_TARGET, DELAY_ENTERED_TARGET); - } - - public void exitedTarget() { - mInsideTarget = false; - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ENTERED_TARGET: - if (mInsideTarget) { - addInstruction(R.string.accessibility_tutorial_lesson_1_text_4, - mTargetName); - } else { - addInstruction(R.string.accessibility_tutorial_lesson_1_text_4_exited, - mTargetName); - setFlag(FLAG_TOUCHED_TARGET, false); - } - break; - } - } - } - - private static final int FLAG_TOUCH_ITEMS = 0x1; - private static final int FLAG_TOUCHED_ITEMS = 0x2; - private static final int FLAG_TOUCHED_TARGET = 0x4; - private static final int FLAG_TAPPED_TARGET = 0x8; - - private static final int MORE_EXPLORED_COUNT = 1; - private static final int DONE_EXPLORED_COUNT = 2; - - private final HoverTargetHandler mHandler; - private final AppsAdapter mAppsAdapter; - private final GridView mAllApps; - - private int mTouched = 0; - - private int mTargetPosition; - private CharSequence mTargetName; - - public TouchTutorialModule1(Context context, AccessibilityTutorialActivity controller) { - super(context, controller, R.layout.accessibility_tutorial_1, - R.string.accessibility_tutorial_lesson_1_title); - - mHandler = new HoverTargetHandler(); - - mAppsAdapter = new AppsAdapter(context, R.layout.accessibility_tutorial_app_icon, - R.id.app_icon); - mAppsAdapter.setOnHoverListener(this); - - mAllApps = (GridView) findViewById(R.id.all_apps); - mAllApps.setAdapter(mAppsAdapter); - mAllApps.setOnItemClickListener(this); - - findViewById(R.id.next_button).setOnHoverListener(this); - - setSkipVisible(true); - } - - @Override - public boolean onHover(View v, MotionEvent event) { - switch (v.getId()) { - case R.id.app_icon: - if (hasFlag(FLAG_TOUCH_ITEMS) && !hasFlag(FLAG_TOUCHED_ITEMS) && v.isEnabled() - && (event.getAction() == MotionEvent.ACTION_HOVER_ENTER)) { - mTouched++; - - if (mTouched >= DONE_EXPLORED_COUNT) { - setFlag(FLAG_TOUCHED_ITEMS, true); - addInstruction(R.string.accessibility_tutorial_lesson_1_text_3, - mTargetName); - } else if (mTouched == MORE_EXPLORED_COUNT) { - addInstruction(R.string.accessibility_tutorial_lesson_1_text_2_more); - } - - v.setEnabled(false); - } else if (hasFlag(FLAG_TOUCHED_ITEMS) - && ((Integer) v.getTag() == mTargetPosition)) { - if (!hasFlag(FLAG_TOUCHED_TARGET) - && (event.getAction() == MotionEvent.ACTION_HOVER_ENTER)) { - mHandler.enteredTarget(); - setFlag(FLAG_TOUCHED_TARGET, true); - } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { - mHandler.exitedTarget(); - } - } - break; - } - - return false; - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - if (hasFlag(FLAG_TOUCHED_TARGET) && !hasFlag(FLAG_TAPPED_TARGET) - && (position == mTargetPosition)) { - setFlag(FLAG_TAPPED_TARGET, true); - final CharSequence nextText = getContext().getText( - R.string.accessibility_tutorial_next); - addInstruction(R.string.accessibility_tutorial_lesson_1_text_5, nextText); - setNextVisible(true); - } - } - - @Override - public void onShown() { - final int first = mAllApps.getFirstVisiblePosition(); - final int last = mAllApps.getLastVisiblePosition(); - - mTargetPosition = 0; - mTargetName = mAppsAdapter.getLabel(mTargetPosition); - - addInstruction(R.string.accessibility_tutorial_lesson_1_text_1); - setFlag(FLAG_TOUCH_ITEMS, true); - } - } - - /** - * Introduces using two fingers to scroll through a list. - */ - private static class TouchTutorialModule2 extends TutorialModule implements - AbsListView.OnScrollListener, View.OnHoverListener { - private static final int FLAG_EXPLORE_LIST = 0x1; - private static final int FLAG_SCROLL_LIST = 0x2; - private static final int FLAG_COMPLETED_TUTORIAL = 0x4; - - private static final int MORE_EXPLORE_COUNT = 1; - private static final int DONE_EXPLORE_COUNT = 2; - private static final int MORE_SCROLL_COUNT = 2; - private static final int DONE_SCROLL_COUNT = 4; - - private final AppsAdapter mAppsAdapter; - - private int mExploreCount = 0; - private int mInitialVisibleItem = -1; - private int mScrollCount = 0; - - public TouchTutorialModule2(Context context, AccessibilityTutorialActivity controller) { - super(context, controller, R.layout.accessibility_tutorial_2, - R.string.accessibility_tutorial_lesson_2_title); - - mAppsAdapter = new AppsAdapter(context, android.R.layout.simple_list_item_1, - android.R.id.text1) { - @Override - protected void populateView(TextView text, CharSequence label, Drawable icon) { - text.setText(label); - text.setCompoundDrawables(icon, null, null, null); - } - }; - mAppsAdapter.setOnHoverListener(this); - - ((ListView) findViewById(R.id.list_view)).setAdapter(mAppsAdapter); - ((ListView) findViewById(R.id.list_view)).setOnScrollListener(this); - - setBackVisible(true); - } - - @Override - public boolean onHover(View v, MotionEvent e) { - if (e.getAction() != MotionEvent.ACTION_HOVER_ENTER) { - return false; - } - - switch (v.getId()) { - case android.R.id.text1: - if (hasFlag(FLAG_EXPLORE_LIST) && !hasFlag(FLAG_SCROLL_LIST)) { - mExploreCount++; - - if (mExploreCount >= DONE_EXPLORE_COUNT) { - addInstruction(R.string.accessibility_tutorial_lesson_2_text_3); - setFlag(FLAG_SCROLL_LIST, true); - } else if (mExploreCount == MORE_EXPLORE_COUNT) { - addInstruction(R.string.accessibility_tutorial_lesson_2_text_2_more); - } - } - break; - } - - return false; - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - if (hasFlag(FLAG_SCROLL_LIST) && !hasFlag(FLAG_COMPLETED_TUTORIAL)) { - if (mInitialVisibleItem < 0) { - mInitialVisibleItem = firstVisibleItem; - } - - final int scrollCount = Math.abs(mInitialVisibleItem - firstVisibleItem); - - if ((mScrollCount == scrollCount) || (scrollCount <= 0)) { - return; - } else { - mScrollCount = scrollCount; - } - - if (mScrollCount >= DONE_SCROLL_COUNT) { - final CharSequence finishText = getContext().getText( - R.string.accessibility_tutorial_finish); - addInstruction(R.string.accessibility_tutorial_lesson_2_text_4, finishText); - setFlag(FLAG_COMPLETED_TUTORIAL, true); - setFinishVisible(true); - } else if (mScrollCount == MORE_SCROLL_COUNT) { - addInstruction(R.string.accessibility_tutorial_lesson_2_text_3_more); - } - } - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - // Do nothing. - } - - @Override - public void onShown() { - addInstruction(R.string.accessibility_tutorial_lesson_2_text_1); - setFlag(FLAG_EXPLORE_LIST, true); - } - } - - /** - * Abstract class that represents a single module within a tutorial. - */ - private static abstract class TutorialModule extends FrameLayout implements OnClickListener { - private final AccessibilityTutorialActivity mController; - private final TextView mInstructions; - private final Button mSkip; - private final Button mBack; - private final Button mNext; - private final Button mFinish; - private final int mTitleResId; - - /** Which bit flags have been set. */ - private long mFlags; - - /** Whether this module is currently focused. */ - private boolean mIsVisible; - - /** Handler for sending accessibility events after the current UI action. */ - private InstructionHandler mHandler = new InstructionHandler(); - - /** - * Constructs a new tutorial module for the given context and controller - * with the specified layout. - * - * @param context The parent context. - * @param controller The parent tutorial controller. - * @param layoutResId The layout to use for this module. - */ - public TutorialModule(Context context, AccessibilityTutorialActivity controller, - int layoutResId, int titleResId) { - super(context); - - mController = controller; - mTitleResId = titleResId; - - final View container = LayoutInflater.from(context).inflate( - R.layout.accessibility_tutorial_container, this, true); - - mInstructions = (TextView) container.findViewById(R.id.instructions); - mSkip = (Button) container.findViewById(R.id.skip_button); - mSkip.setOnClickListener(this); - mBack = (Button) container.findViewById(R.id.back_button); - mBack.setOnClickListener(this); - mNext = (Button) container.findViewById(R.id.next_button); - mNext.setOnClickListener(this); - mFinish = (Button) container.findViewById(R.id.finish_button); - mFinish.setOnClickListener(this); - - final TextView title = (TextView) container.findViewById(R.id.title); - - if (title != null) { - title.setText(titleResId); - } - - final ViewGroup contentHolder = (ViewGroup) container.findViewById(R.id.content); - LayoutInflater.from(context).inflate(layoutResId, contentHolder, true); - } - - /** - * Called when this tutorial gains focus. - */ - public final void activate() { - mIsVisible = true; - - mFlags = 0; - mInstructions.setVisibility(View.GONE); - mController.setTitle(mTitleResId); - - onShown(); - } - - /** - * Formats an instruction string and adds it to the speaking queue. - * - * @param resId The resource id of the instruction string. - * @param formatArgs Optional formatting arguments. - * @see String#format(String, Object...) - */ - protected void addInstruction(final int resId, Object... formatArgs) { - if (!mIsVisible) { - return; - } - - final String text = mContext.getString(resId, formatArgs); - mHandler.addInstruction(text); - } - - private void addInstructionSync(CharSequence text) { - mInstructions.setVisibility(View.VISIBLE); - mInstructions.setText(text); - mInstructions.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - } - - /** - * Called when this tutorial loses focus. - */ - public void deactivate() { - mIsVisible = false; - - mController.interrupt(); - } - - /** - * Returns {@code true} if the flag with the specified id has been set. - * - * @param flagId The id of the flag to check for. - * @return {@code true} if the flag with the specified id has been set. - */ - protected boolean hasFlag(int flagId) { - return (mFlags & flagId) == flagId; - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.skip_button: - mController.finish(); - break; - case R.id.back_button: - mController.previous(); - break; - case R.id.next_button: - mController.next(); - break; - case R.id.finish_button: - mController.finish(); - break; - } - } - - public abstract void onShown(); - - /** - * Sets or removes the flag with the specified id. - * - * @param flagId The id of the flag to modify. - * @param value {@code true} to set the flag, {@code false} to remove - * it. - */ - protected void setFlag(int flagId, boolean value) { - if (value) { - mFlags |= flagId; - } else { - mFlags = ~(~mFlags | flagId); - } - } - - protected void setSkipVisible(boolean visible) { - mSkip.setVisibility(visible ? VISIBLE : GONE); - } - - protected void setBackVisible(boolean visible) { - mBack.setVisibility(visible ? VISIBLE : GONE); - } - - protected void setNextVisible(boolean visible) { - mNext.setVisibility(visible ? VISIBLE : GONE); - } - - protected void setFinishVisible(boolean visible) { - mFinish.setVisibility(visible ? VISIBLE : GONE); - } - - private class InstructionHandler extends Handler { - private static final int ADD_INSTRUCTION = 1; - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case ADD_INSTRUCTION: - final String text = (String) msg.obj; - addInstructionSync(text); - break; - } - } - - public void addInstruction(String text) { - obtainMessage(ADD_INSTRUCTION, text).sendToTarget(); - } - } - } - - /** - * Provides a tutorial-specific class name for fired accessibility events. - */ - public static class TutorialTextView extends TextView { - public TutorialTextView(Context context, AttributeSet attrs) { - super(context, attrs); - } - } -} diff --git a/src/com/android/settings/AccountPreference.java b/src/com/android/settings/AccountPreference.java index 824420d..2cc013c 100644 --- a/src/com/android/settings/AccountPreference.java +++ b/src/com/android/settings/AccountPreference.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import android.accounts.Account; import android.content.Context; -import android.content.Intent; import android.graphics.drawable.Drawable; import android.preference.Preference; import android.util.Log; @@ -36,25 +35,28 @@ public class AccountPreference extends Preference { public static final int SYNC_ENABLED = 0; // all know sync adapters are enabled and OK public static final int SYNC_DISABLED = 1; // no sync adapters are enabled public static final int SYNC_ERROR = 2; // one or more sync adapters have a problem + public static final int SYNC_IN_PROGRESS = 3; // currently syncing private int mStatus; private Account mAccount; private ArrayList<String> mAuthorities; - private Drawable mProviderIcon; private ImageView mSyncStatusIcon; - private ImageView mProviderIconView; + private boolean mShowTypeIcon; public AccountPreference(Context context, Account account, Drawable icon, - ArrayList<String> authorities) { + ArrayList<String> authorities, boolean showTypeIcon) { super(context); mAccount = account; mAuthorities = authorities; - mProviderIcon = icon; - setWidgetLayoutResource(R.layout.account_preference); + mShowTypeIcon = showTypeIcon; + if (showTypeIcon) { + setIcon(icon); + } else { + setIcon(getSyncStatusIcon(SYNC_DISABLED)); + } setTitle(mAccount.name); setSummary(""); setPersistent(false); - setSyncStatus(SYNC_DISABLED); - setIcon(mProviderIcon); + setSyncStatus(SYNC_DISABLED, false); } public Account getAccount() { @@ -68,25 +70,22 @@ public class AccountPreference extends Preference { @Override protected void onBindView(View view) { super.onBindView(view); - setSummary(getSyncStatusMessage(mStatus)); - mSyncStatusIcon = (ImageView) view.findViewById(R.id.syncStatusIcon); - mSyncStatusIcon.setImageResource(getSyncStatusIcon(mStatus)); - mSyncStatusIcon.setContentDescription(getSyncContentDescription(mStatus)); - } - - public void setProviderIcon(Drawable icon) { - mProviderIcon = icon; - if (mProviderIconView != null) { - mProviderIconView.setImageDrawable(icon); + if (!mShowTypeIcon) { + mSyncStatusIcon = (ImageView) view.findViewById(android.R.id.icon); + mSyncStatusIcon.setImageResource(getSyncStatusIcon(mStatus)); + mSyncStatusIcon.setContentDescription(getSyncContentDescription(mStatus)); } } - public void setSyncStatus(int status) { + public void setSyncStatus(int status, boolean updateSummary) { mStatus = status; - if (mSyncStatusIcon != null) { + if (!mShowTypeIcon && mSyncStatusIcon != null) { mSyncStatusIcon.setImageResource(getSyncStatusIcon(status)); + mSyncStatusIcon.setContentDescription(getSyncContentDescription(mStatus)); + } + if (updateSummary) { + setSummary(getSyncStatusMessage(status)); } - setSummary(getSyncStatusMessage(status)); } private int getSyncStatusMessage(int status) { @@ -101,6 +100,9 @@ public class AccountPreference extends Preference { case SYNC_ERROR: res = R.string.sync_error; break; + case SYNC_IN_PROGRESS: + res = R.string.sync_in_progress; + break; default: res = R.string.sync_error; Log.e(TAG, "Unknown sync status: " + status); @@ -120,6 +122,9 @@ public class AccountPreference extends Preference { case SYNC_ERROR: res = R.drawable.ic_sync_red_holo; break; + case SYNC_IN_PROGRESS: + res = R.drawable.ic_sync_green_holo; + break; default: res = R.drawable.ic_sync_red_holo; Log.e(TAG, "Unknown sync status: " + status); @@ -140,13 +145,4 @@ public class AccountPreference extends Preference { return getContext().getString(R.string.accessibility_sync_error); } } - - @Override - public int compareTo(Preference other) { - if (!(other instanceof AccountPreference)) { - // Put other preference types above us - return 1; - } - return mAccount.name.compareTo(((AccountPreference) other).mAccount.name); - } } diff --git a/src/com/android/settings/AllowBindAppWidgetActivity.java b/src/com/android/settings/AllowBindAppWidgetActivity.java new file mode 100644 index 0000000..2f54f8e --- /dev/null +++ b/src/com/android/settings/AllowBindAppWidgetActivity.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2012 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; + +import android.app.AlertDialog; +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.LayoutInflater; +import android.widget.CheckBox; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +/** + * This activity is displayed when an app launches the BIND_APPWIDGET intent. This allows apps + * that don't have the BIND_APPWIDGET permission to bind specific widgets. + */ +public class AllowBindAppWidgetActivity extends AlertActivity implements + DialogInterface.OnClickListener { + + private CheckBox mAlwaysUse; + private int mAppWidgetId; + private ComponentName mComponentName; + private String mCallingPackage; + private AppWidgetManager mAppWidgetManager; + + // Indicates whether this activity was closed because of a click + private boolean mClicked; + + public void onClick(DialogInterface dialog, int which) { + if (which == AlertDialog.BUTTON_POSITIVE) { + // By default, set the result to cancelled + setResult(RESULT_CANCELED); + if (mAppWidgetId != -1 && mComponentName != null && mCallingPackage != null) { + try { + mAppWidgetManager.bindAppWidgetId(mAppWidgetId, mComponentName); + Intent result = new Intent(); + result.putExtra("EXTRA_APPWIDGET_ID", mAppWidgetId); + setResult(RESULT_OK); + } catch (Exception e) { + Log.v("BIND_APPWIDGET", "Error binding widget with id " + + mAppWidgetId + " and component " + mComponentName); + } + } + boolean alwaysAllowBind = mAlwaysUse.isChecked(); + if (alwaysAllowBind != mAppWidgetManager.hasBindAppWidgetPermission(mCallingPackage)) { + mAppWidgetManager.setBindAppWidgetPermission(mCallingPackage, alwaysAllowBind); + } + } + finish(); + } + + protected void onDestroy() { + if (!mClicked) { + setResult(RESULT_CANCELED); + finish(); + } + super.onDestroy(); + } + + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = getIntent(); + CharSequence label = ""; + if (intent != null) { + try { + mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); + mComponentName = (ComponentName) + intent.getParcelableExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER); + mCallingPackage = getCallingPackage(); + PackageManager pm = getPackageManager(); + ApplicationInfo ai = pm.getApplicationInfo(mCallingPackage, 0); + label = pm.getApplicationLabel(ai); + } catch (Exception e) { + mAppWidgetId = -1; + mComponentName = null; + mCallingPackage = null; + Log.v("BIND_APPWIDGET", "Error getting parameters"); + setResult(RESULT_CANCELED); + finish(); + return; + } + } + AlertController.AlertParams ap = mAlertParams; + ap.mTitle = getString(R.string.allow_bind_app_widget_activity_allow_bind_title); + ap.mMessage = getString(R.string.allow_bind_app_widget_activity_allow_bind, label); + ap.mPositiveButtonText = getString(R.string.create); + ap.mNegativeButtonText = getString(android.R.string.cancel); + ap.mPositiveButtonListener = this; + ap.mNegativeButtonListener = this; + LayoutInflater inflater = + (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null); + mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse); + mAlwaysUse.setText(getString(R.string.allow_bind_app_widget_activity_always_allow_bind, label)); + + mAlwaysUse.setPadding(mAlwaysUse.getPaddingLeft(), + mAlwaysUse.getPaddingTop(), + mAlwaysUse.getPaddingRight(), + (int) (mAlwaysUse.getPaddingBottom() + + getResources().getDimension(R.dimen.bind_app_widget_dialog_checkbox_bottom_padding))); + + mAppWidgetManager = AppWidgetManager.getInstance(this); + mAlwaysUse.setChecked(mAppWidgetManager.hasBindAppWidgetPermission(mCallingPackage)); + + setupAlert(); + } +} diff --git a/src/com/android/settings/AppPicker.java b/src/com/android/settings/AppPicker.java new file mode 100644 index 0000000..e58b835 --- /dev/null +++ b/src/com/android/settings/AppPicker.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2008 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; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.android.settings.applications.AppViewHolder; + +import android.app.ActivityManagerNative; +import android.app.ListActivity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.os.Build; +import android.os.Bundle; +import android.os.Process; +import android.os.RemoteException; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +public class AppPicker extends ListActivity { + private AppListAdapter mAdapter; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mAdapter = new AppListAdapter(this); + if (mAdapter.getCount() <= 0) { + finish(); + } else { + setListAdapter(mAdapter); + } + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onStop() { + super.onStop(); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + MyApplicationInfo app = mAdapter.getItem(position); + Intent intent = new Intent(); + if (app.info != null) intent.setAction(app.info.packageName); + setResult(RESULT_OK, intent); + finish(); + } + + class MyApplicationInfo { + ApplicationInfo info; + CharSequence label; + } + + public class AppListAdapter extends ArrayAdapter<MyApplicationInfo> { + private final List<MyApplicationInfo> mPackageInfoList = new ArrayList<MyApplicationInfo>(); + private final LayoutInflater mInflater; + + public AppListAdapter(Context context) { + super(context, 0); + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + List<ApplicationInfo> pkgs = context.getPackageManager().getInstalledApplications(0); + for (int i=0; i<pkgs.size(); i++) { + ApplicationInfo ai = pkgs.get(i); + if (ai.uid == Process.SYSTEM_UID) { + continue; + } + // On a user build, we only allow debugging of apps that + // are marked as debuggable. Otherwise (for platform development) + // we allow all apps. + if ((ai.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0 + && "user".equals(Build.TYPE)) { + continue; + } + MyApplicationInfo info = new MyApplicationInfo(); + info.info = ai; + info.label = info.info.loadLabel(getPackageManager()).toString(); + mPackageInfoList.add(info); + } + Collections.sort(mPackageInfoList, sDisplayNameComparator); + MyApplicationInfo info = new MyApplicationInfo(); + info.label = context.getText(R.string.no_application); + mPackageInfoList.add(0, info); + addAll(mPackageInfoList); + } + + @Override + 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(mInflater, convertView); + convertView = holder.rootView; + MyApplicationInfo info = getItem(position); + holder.appName.setText(info.label); + if (info.info != null) { + holder.appIcon.setImageDrawable(info.info.loadIcon(getPackageManager())); + holder.appSize.setText(info.info.packageName); + } else { + holder.appIcon.setImageDrawable(null); + holder.appSize.setText(""); + } + holder.disabled.setVisibility(View.GONE); + holder.checkBox.setVisibility(View.GONE); + return convertView; + } + } + + private final static Comparator<MyApplicationInfo> sDisplayNameComparator + = new Comparator<MyApplicationInfo>() { + public final int + compare(MyApplicationInfo a, MyApplicationInfo b) { + return collator.compare(a.label, b.label); + } + + private final Collator collator = Collator.getInstance(); + }; +} diff --git a/src/com/android/settings/BiometricWeakLiveliness.java b/src/com/android/settings/BiometricWeakLiveliness.java new file mode 100644 index 0000000..6bba77f --- /dev/null +++ b/src/com/android/settings/BiometricWeakLiveliness.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012 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; + +import android.app.ActionBar; +import android.app.Activity; +import android.app.Fragment; +import android.os.Bundle; +import android.os.Handler; +import android.content.Intent; +import android.preference.PreferenceActivity; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; +import android.widget.Switch; +import com.android.settings.R; + +import com.android.internal.widget.LockPatternUtils; + +public class BiometricWeakLiveliness extends Fragment + implements CompoundButton.OnCheckedChangeListener { + private static final int CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_LIVELINESS_OFF = 125; + + private View mView; + private ChooseLockSettingsHelper mChooseLockSettingsHelper; + private LockPatternUtils mLockPatternUtils; + private Switch mActionBarSwitch; + private boolean mSuppressCheckChanged; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Activity activity = getActivity(); + + mActionBarSwitch = new Switch(activity); + mSuppressCheckChanged = false; + + if (activity instanceof PreferenceActivity) { + PreferenceActivity preferenceActivity = (PreferenceActivity) activity; + if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) { + final int padding = activity.getResources().getDimensionPixelSize( + R.dimen.action_bar_switch_padding); + mActionBarSwitch.setPadding(0, 0, padding, 0); + activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, + ActionBar.DISPLAY_SHOW_CUSTOM); + activity.getActionBar().setCustomView(mActionBarSwitch, new ActionBar.LayoutParams( + ActionBar.LayoutParams.WRAP_CONTENT, + ActionBar.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_VERTICAL | Gravity.RIGHT)); + activity.getActionBar().setTitle(R.string.biometric_weak_liveliness_title); + } + } + + mActionBarSwitch.setOnCheckedChangeListener(this); + + mLockPatternUtils = new LockPatternUtils(getActivity()); + mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity()); + mActionBarSwitch.setChecked(mLockPatternUtils.isBiometricWeakLivelinessEnabled()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mView = inflater.inflate(R.layout.biometric_weak_liveliness, container, false); + initView(mView); + return mView; + } + + private void initView(View view) { + mActionBarSwitch.setOnCheckedChangeListener(this); + mActionBarSwitch.setChecked(mLockPatternUtils.isBiometricWeakLivelinessEnabled()); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean desiredState) { + if (mSuppressCheckChanged) { + return; + } + mActionBarSwitch.setEnabled(false); + if (desiredState) { + mLockPatternUtils.setBiometricWeakLivelinessEnabled(true); + mActionBarSwitch.setChecked(true); + } else { + // In this case the user has just turned it off, but this action requires them + // to confirm their password. We need to turn the switch back on until + // they've confirmed their password + mActionBarSwitch.setChecked(true); + mActionBarSwitch.requestLayout(); + ChooseLockSettingsHelper helper = + new ChooseLockSettingsHelper(this.getActivity(), this); + if (!helper.launchConfirmationActivity( + CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_LIVELINESS_OFF, null, null)) { + // If this returns false, it means no password confirmation is required, so + // go ahead and turn it off here. + // Note: currently a backup is required for biometric_weak so this code path + // can't be reached, but is here in case things change in the future + mLockPatternUtils.setBiometricWeakLivelinessEnabled(false); + mActionBarSwitch.setChecked(false); + mActionBarSwitch.requestLayout(); + } + } + mActionBarSwitch.setEnabled(true); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_LIVELINESS_OFF && + resultCode == Activity.RESULT_OK) { + final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils(); + lockPatternUtils.setBiometricWeakLivelinessEnabled(false); + mSuppressCheckChanged = true; + mActionBarSwitch.setChecked(false); + mSuppressCheckChanged = false; + return; + } + } + +} diff --git a/src/com/android/settings/BrightnessPreference.java b/src/com/android/settings/BrightnessPreference.java index c34d212..eff5c50 100644 --- a/src/com/android/settings/BrightnessPreference.java +++ b/src/com/android/settings/BrightnessPreference.java @@ -45,6 +45,9 @@ public class BrightnessPreference extends SeekBarDialogPreference implements private int mOldAutomatic; private boolean mAutomaticAvailable; + private boolean mAutomaticMode; + + private int mCurBrightness = -1; private boolean mRestoredOldState; @@ -52,11 +55,14 @@ public class BrightnessPreference extends SeekBarDialogPreference implements // doesn't set the backlight to 0 and get stuck private int mScreenBrightnessDim = getContext().getResources().getInteger(com.android.internal.R.integer.config_screenBrightnessDim); - private static final int MAXIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_ON; + private static final int MAXIMUM_BACKLIGHT = android.os.PowerManager.BRIGHTNESS_ON; + + private static final int SEEK_BAR_RANGE = 10000; private ContentObserver mBrightnessObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { + mCurBrightness = -1; onBrightnessChanged(); } }; @@ -97,24 +103,26 @@ public class BrightnessPreference extends SeekBarDialogPreference implements super.onBindDialogView(view); mSeekBar = getSeekBar(view); - mSeekBar.setMax(MAXIMUM_BACKLIGHT - mScreenBrightnessDim); - mOldBrightness = getBrightness(0); - mSeekBar.setProgress(mOldBrightness - mScreenBrightnessDim); + mSeekBar.setMax(SEEK_BAR_RANGE); + mOldBrightness = getBrightness(); + mSeekBar.setProgress(mOldBrightness); mCheckBox = (CheckBox)view.findViewById(R.id.automatic_mode); if (mAutomaticAvailable) { mCheckBox.setOnCheckedChangeListener(this); mOldAutomatic = getBrightnessMode(0); - mCheckBox.setChecked(mOldAutomatic != 0); + mAutomaticMode = mOldAutomatic == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; + mCheckBox.setChecked(mAutomaticMode); + mSeekBar.setEnabled(!mAutomaticMode); } else { - mCheckBox.setVisibility(View.GONE); + mSeekBar.setEnabled(true); } mSeekBar.setOnSeekBarChangeListener(this); } public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) { - setBrightness(progress + mScreenBrightnessDim); + setBrightness(progress, false); } public void onStartTrackingTouch(SeekBar seekBar) { @@ -128,19 +136,29 @@ public class BrightnessPreference extends SeekBarDialogPreference implements public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { setMode(isChecked ? Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC : Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); - if (!isChecked) { - setBrightness(mSeekBar.getProgress() + mScreenBrightnessDim); - } + mSeekBar.setProgress(getBrightness()); + mSeekBar.setEnabled(!mAutomaticMode); + setBrightness(mSeekBar.getProgress(), false); } - private int getBrightness(int defaultValue) { - int brightness = defaultValue; - try { - brightness = Settings.System.getInt(getContext().getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS); - } catch (SettingNotFoundException snfe) { + private int getBrightness() { + int mode = getBrightnessMode(0); + float brightness = 0; + if (false && mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { + brightness = Settings.System.getFloat(getContext().getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0); + brightness = (brightness+1)/2; + } else { + if (mCurBrightness < 0) { + brightness = Settings.System.getInt(getContext().getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, 100); + } else { + brightness = mCurBrightness; + } + brightness = (brightness - mScreenBrightnessDim) + / (MAXIMUM_BACKLIGHT - mScreenBrightnessDim); } - return brightness; + return (int)(brightness*SEEK_BAR_RANGE); } private int getBrightnessMode(int defaultValue) { @@ -154,13 +172,15 @@ public class BrightnessPreference extends SeekBarDialogPreference implements } private void onBrightnessChanged() { - int brightness = getBrightness(MAXIMUM_BACKLIGHT); - mSeekBar.setProgress(brightness - mScreenBrightnessDim); + mSeekBar.setProgress(getBrightness()); } private void onBrightnessModeChanged() { - boolean checked = getBrightnessMode(0) != 0; + boolean checked = getBrightnessMode(0) + == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; mCheckBox.setChecked(checked); + mSeekBar.setProgress(getBrightness()); + mSeekBar.setEnabled(!checked); } @Override @@ -170,9 +190,7 @@ public class BrightnessPreference extends SeekBarDialogPreference implements final ContentResolver resolver = getContext().getContentResolver(); if (positiveResult) { - Settings.System.putInt(resolver, - Settings.System.SCREEN_BRIGHTNESS, - mSeekBar.getProgress() + mScreenBrightnessDim); + setBrightness(mSeekBar.getProgress(), true); } else { restoreOldState(); } @@ -187,30 +205,53 @@ public class BrightnessPreference extends SeekBarDialogPreference implements if (mAutomaticAvailable) { setMode(mOldAutomatic); } - if (!mAutomaticAvailable || mOldAutomatic == 0) { - setBrightness(mOldBrightness); - } + setBrightness(mOldBrightness, false); mRestoredOldState = true; + mCurBrightness = -1; } - private void setBrightness(int brightness) { - try { - IPowerManager power = IPowerManager.Stub.asInterface( - ServiceManager.getService("power")); - if (power != null) { - power.setBacklightBrightness(brightness); + private void setBrightness(int brightness, boolean write) { + if (mAutomaticMode) { + if (false) { + float valf = (((float)brightness*2)/SEEK_BAR_RANGE) - 1.0f; + try { + IPowerManager power = IPowerManager.Stub.asInterface( + ServiceManager.getService("power")); + if (power != null) { + power.setAutoBrightnessAdjustment(valf); + } + if (write) { + final ContentResolver resolver = getContext().getContentResolver(); + Settings.System.putFloat(resolver, + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, valf); + } + } catch (RemoteException doe) { + } + } + } else { + int range = (MAXIMUM_BACKLIGHT - mScreenBrightnessDim); + brightness = (brightness*range)/SEEK_BAR_RANGE + mScreenBrightnessDim; + try { + IPowerManager power = IPowerManager.Stub.asInterface( + ServiceManager.getService("power")); + if (power != null) { + power.setBacklightBrightness(brightness); + } + if (write) { + mCurBrightness = -1; + final ContentResolver resolver = getContext().getContentResolver(); + Settings.System.putInt(resolver, + Settings.System.SCREEN_BRIGHTNESS, brightness); + } else { + mCurBrightness = brightness; + } + } catch (RemoteException doe) { } - } catch (RemoteException doe) { - } } private void setMode(int mode) { - if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { - mSeekBar.setVisibility(View.GONE); - } else { - mSeekBar.setVisibility(View.VISIBLE); - } + mAutomaticMode = mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; Settings.System.putInt(getContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, mode); } @@ -226,6 +267,7 @@ public class BrightnessPreference extends SeekBarDialogPreference implements myState.progress = mSeekBar.getProgress(); myState.oldAutomatic = mOldAutomatic == 1; myState.oldProgress = mOldBrightness; + myState.curBrightness = mCurBrightness; // Restore the old state when the activity or dialog is being paused restoreOldState(); @@ -245,7 +287,8 @@ public class BrightnessPreference extends SeekBarDialogPreference implements mOldBrightness = myState.oldProgress; mOldAutomatic = myState.oldAutomatic ? 1 : 0; setMode(myState.automatic ? 1 : 0); - setBrightness(myState.progress + mScreenBrightnessDim); + setBrightness(myState.progress, false); + mCurBrightness = myState.curBrightness; } private static class SavedState extends BaseSavedState { @@ -254,6 +297,7 @@ public class BrightnessPreference extends SeekBarDialogPreference implements boolean oldAutomatic; int progress; int oldProgress; + int curBrightness; public SavedState(Parcel source) { super(source); @@ -261,6 +305,7 @@ public class BrightnessPreference extends SeekBarDialogPreference implements progress = source.readInt(); oldAutomatic = source.readInt() == 1; oldProgress = source.readInt(); + curBrightness = source.readInt(); } @Override @@ -270,6 +315,7 @@ public class BrightnessPreference extends SeekBarDialogPreference implements dest.writeInt(progress); dest.writeInt(oldAutomatic ? 1 : 0); dest.writeInt(oldProgress); + dest.writeInt(curBrightness); } public SavedState(Parcelable superState) { diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java index df421a2..0f9fcee 100644 --- a/src/com/android/settings/ChooseLockGeneric.java +++ b/src/com/android/settings/ChooseLockGeneric.java @@ -33,6 +33,8 @@ import android.widget.ListView; import com.android.internal.widget.LockPatternUtils; +import libcore.util.MutableBoolean; + public class ChooseLockGeneric extends PreferenceActivity { @Override @@ -56,6 +58,7 @@ public class ChooseLockGeneric extends PreferenceActivity { private static final int FALLBACK_REQUEST = 101; private static final String PASSWORD_CONFIRMED = "password_confirmed"; private static final String CONFIRM_CREDENTIALS = "confirm_credentials"; + private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation"; public static final String MINIMUM_QUALITY_KEY = "minimum_quality"; private static final boolean ALWAY_SHOW_TUTORIAL = true; @@ -64,6 +67,7 @@ public class ChooseLockGeneric extends PreferenceActivity { private DevicePolicyManager mDPM; private KeyStore mKeyStore; private boolean mPasswordConfirmed = false; + private boolean mWaitingForConfirmation = false; @Override public void onCreate(Bundle savedInstanceState) { @@ -80,20 +84,25 @@ public class ChooseLockGeneric extends PreferenceActivity { if (savedInstanceState != null) { mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED); + mWaitingForConfirmation = savedInstanceState.getBoolean(WAITING_FOR_CONFIRMATION); } - if (!mPasswordConfirmed) { + if (mPasswordConfirmed) { + updatePreferencesOrFinish(); + } else if (!mWaitingForConfirmation) { ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this.getActivity(), this); if (!helper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null)) { mPasswordConfirmed = true; // no password set, so no need to confirm updatePreferencesOrFinish(); + } else { + mWaitingForConfirmation = true; } - } else { - updatePreferencesOrFinish(); } } + + @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { @@ -141,6 +150,7 @@ public class ChooseLockGeneric extends PreferenceActivity { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); + mWaitingForConfirmation = false; if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) { mPasswordConfirmed = true; updatePreferencesOrFinish(); @@ -159,6 +169,7 @@ public class ChooseLockGeneric extends PreferenceActivity { super.onSaveInstanceState(outState); // Saved so we don't force user to re-enter their password if configuration changes outState.putBoolean(PASSWORD_CONFIRMED, mPasswordConfirmed); + outState.putBoolean(WAITING_FOR_CONFIRMATION, mWaitingForConfirmation); } private void updatePreferencesOrFinish() { @@ -167,23 +178,37 @@ public class ChooseLockGeneric extends PreferenceActivity { if (quality == -1) { // If caller didn't specify password quality, show UI and allow the user to choose. quality = intent.getIntExtra(MINIMUM_QUALITY_KEY, -1); - quality = upgradeQuality(quality); + MutableBoolean allowBiometric = new MutableBoolean(false); + quality = upgradeQuality(quality, allowBiometric); final PreferenceScreen prefScreen = getPreferenceScreen(); if (prefScreen != null) { prefScreen.removeAll(); } addPreferencesFromResource(R.xml.security_settings_picker); - disableUnusablePreferences(quality); + disableUnusablePreferences(quality, allowBiometric); } else { updateUnlockMethodAndFinish(quality, false); } } - private int upgradeQuality(int quality) { + /** increases the quality if necessary, and returns whether biometric is allowed */ + private int upgradeQuality(int quality, MutableBoolean allowBiometric) { quality = upgradeQualityForDPM(quality); - quality = upgradeQualityForEncryption(quality); quality = upgradeQualityForKeyStore(quality); - return quality; + int encryptionQuality = upgradeQualityForEncryption(quality); + if (encryptionQuality > quality) { + //The first case checks whether biometric is allowed, prior to the user making + //their selection from the list + if (allowBiometric != null) { + allowBiometric.value = quality <= + DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; + } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) { + //When the user has selected biometric we shouldn't change that due to + //encryption + return quality; + } + } + return encryptionQuality; } private int upgradeQualityForDPM(int quality) { @@ -228,7 +253,7 @@ public class ChooseLockGeneric extends PreferenceActivity { * * @param quality the requested quality. */ - private void disableUnusablePreferences(final int quality) { + private void disableUnusablePreferences(final int quality, MutableBoolean allowBiometric) { final PreferenceScreen entries = getPreferenceScreen(); final boolean onlyShowFallback = getActivity().getIntent() .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false); @@ -245,7 +270,8 @@ public class ChooseLockGeneric extends PreferenceActivity { } else if (KEY_UNLOCK_SET_NONE.equals(key)) { enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; } else if (KEY_UNLOCK_SET_BIOMETRIC_WEAK.equals(key)) { - enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; + enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK || + allowBiometric.value; visible = weakBiometricAvailable; // If not available, then don't show it. } else if (KEY_UNLOCK_SET_PATTERN.equals(key)) { enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; @@ -311,7 +337,7 @@ public class ChooseLockGeneric extends PreferenceActivity { final boolean isFallback = getActivity().getIntent() .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false); - quality = upgradeQuality(quality); + quality = upgradeQuality(quality, null); if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) { int minLength = mDPM.getPasswordMinimumLength(null); @@ -360,5 +386,11 @@ public class ChooseLockGeneric extends PreferenceActivity { } finish(); } + + @Override + protected int getHelpResource() { + return R.string.help_url_choose_lockscreen; + } + } } diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java index e246fce..c12d06c 100644 --- a/src/com/android/settings/CredentialStorage.java +++ b/src/com/android/settings/CredentialStorage.java @@ -25,6 +25,7 @@ import android.content.res.Resources; import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; +import android.security.Credentials; import android.security.KeyChain.KeyChainConnection; import android.security.KeyChain; import android.security.KeyStore; @@ -187,13 +188,38 @@ public final class CredentialStorage extends Activity { if (mInstallBundle != null && !mInstallBundle.isEmpty()) { Bundle bundle = mInstallBundle; mInstallBundle = null; - for (String key : bundle.keySet()) { - byte[] value = bundle.getByteArray(key); - if (value != null && !mKeyStore.put(key, value)) { + + if (bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) { + String key = bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME); + byte[] value = bundle.getByteArray(Credentials.EXTRA_USER_PRIVATE_KEY_DATA); + + if (!mKeyStore.importKey(key, value)) { Log.e(TAG, "Failed to install " + key); return; } } + + if (bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)) { + String certName = bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME); + byte[] certData = bundle.getByteArray(Credentials.EXTRA_USER_CERTIFICATE_DATA); + + if (!mKeyStore.put(certName, certData)) { + Log.e(TAG, "Failed to install " + certName); + return; + } + } + + if (bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)) { + String caListName = bundle.getString(Credentials.EXTRA_CA_CERTIFICATES_NAME); + byte[] caListData = bundle.getByteArray(Credentials.EXTRA_CA_CERTIFICATES_DATA); + + if (!mKeyStore.put(caListName, caListData)) { + Log.e(TAG, "Failed to install " + caListName); + return; + } + + } + setResult(RESULT_OK); } } @@ -312,8 +338,8 @@ public final class CredentialStorage extends Activity { Resources res = getResources(); boolean launched = new ChooseLockSettingsHelper(this) .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST, - res.getText(R.string.master_clear_gesture_prompt), - res.getText(R.string.master_clear_gesture_explanation)); + res.getText(R.string.credentials_install_gesture_prompt), + res.getText(R.string.credentials_install_gesture_explanation)); return launched; } diff --git a/src/com/android/settings/CryptKeeper.java b/src/com/android/settings/CryptKeeper.java index 655d8ad..5fb72ed 100644 --- a/src/com/android/settings/CryptKeeper.java +++ b/src/com/android/settings/CryptKeeper.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.media.AudioManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -32,12 +33,18 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.storage.IMountService; +import android.provider.Settings; import android.telephony.TelephonyManager; +import android.text.Editable; import android.text.TextUtils; +import android.text.TextWatcher; import android.util.Log; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; +import android.view.View.OnKeyListener; +import android.view.View.OnTouchListener; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; @@ -48,6 +55,7 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.Phone; import java.util.List; @@ -64,14 +72,19 @@ import java.util.List; * -n com.android.settings/.CryptKeeper * </pre> */ -public class CryptKeeper extends Activity implements TextView.OnEditorActionListener { +public class CryptKeeper extends Activity implements TextView.OnEditorActionListener, + OnKeyListener, OnTouchListener, TextWatcher { private static final String TAG = "CryptKeeper"; private static final String DECRYPT_STATE = "trigger_restart_framework"; - - private static final int UPDATE_PROGRESS = 1; - private static final int COOLDOWN = 2; - + /** Message sent to us to indicate encryption update progress. */ + private static final int MESSAGE_UPDATE_PROGRESS = 1; + /** Message sent to us to cool-down (waste user's time between password attempts) */ + private static final int MESSAGE_COOLDOWN = 2; + /** Message sent to us to indicate alerting the user that we are waiting for password entry */ + private static final int MESSAGE_NOTIFY = 3; + + // Constants used to control policy. private static final int MAX_FAILED_ATTEMPTS = 30; private static final int COOL_DOWN_ATTEMPTS = 10; private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds @@ -83,18 +96,21 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList private static final String EXTRA_FORCE_VIEW = "com.android.settings.CryptKeeper.DEBUG_FORCE_VIEW"; private static final String FORCE_VIEW_PROGRESS = "progress"; - private static final String FORCE_VIEW_ENTRY = "entry"; private static final String FORCE_VIEW_ERROR = "error"; + private static final String FORCE_VIEW_PASSWORD = "password"; - /** When encryption is detected, this flag indivates whether or not we've checked for erros. */ + /** When encryption is detected, this flag indicates whether or not we've checked for errors. */ private boolean mValidationComplete; private boolean mValidationRequested; /** A flag to indicate that the volume is in a bad state (e.g. partially encrypted). */ private boolean mEncryptionGoneBad; - + /** A flag to indicate when the back event should be ignored */ + private boolean mIgnoreBack = false; private int mCooldown; PowerManager.WakeLock mWakeLock; private EditText mPasswordEntry; + /** Number of calls to {@link #notifyUser()} to ignore before notifying. */ + private int mNotificationCountdown = 0; /** * Used to propagate state through configuration changes (e.g. screen rotation) @@ -107,19 +123,26 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList } } - // This activity is used to fade the screen to black after the password is entered. - public static class Blank extends Activity { + /** + * Activity used to fade the screen to black after the password is entered. + */ + public static class FadeToBlack extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.crypt_keeper_blank); } + /** Ignore all back events. */ + @Override + public void onBackPressed() { + return; + } } private class DecryptTask extends AsyncTask<String, Void, Integer> { @Override protected Integer doInBackground(String... params) { - IMountService service = getMountService(); + final IMountService service = getMountService(); try { return service.decryptStorage(params[0]); } catch (Exception e) { @@ -135,7 +158,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList // so this activity animates to black before the devices starts. Note // It has 1 second to complete the animation or it will be frozen // until the boot animation comes back up. - Intent intent = new Intent(CryptKeeper.this, Blank.class); + Intent intent = new Intent(CryptKeeper.this, FadeToBlack.class); finish(); startActivity(intent); } else if (failedAttempts == MAX_FAILED_ATTEMPTS) { @@ -145,10 +168,8 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList mCooldown = COOL_DOWN_INTERVAL; cooldown(); } else { - TextView tv = (TextView) findViewById(R.id.status); - tv.setText(R.string.try_again); - tv.setVisibility(View.VISIBLE); - + final TextView status = (TextView) findViewById(R.id.status); + status.setText(R.string.try_again); // Reenable the password entry mPasswordEntry.setEnabled(true); } @@ -158,7 +179,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList private class ValidationTask extends AsyncTask<Void, Void, Boolean> { @Override protected Boolean doInBackground(Void... params) { - IMountService service = getMountService(); + final IMountService service = getMountService(); try { Log.d(TAG, "Validating encryption state."); int state = service.getEncryptionState(); @@ -190,17 +211,23 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList @Override public void handleMessage(Message msg) { switch (msg.what) { - case UPDATE_PROGRESS: + case MESSAGE_UPDATE_PROGRESS: updateProgress(); break; - case COOLDOWN: + case MESSAGE_COOLDOWN: cooldown(); break; + + case MESSAGE_NOTIFY: + notifyUser(); + break; } } }; + private AudioManager mAudioManager; + /** @return whether or not this Activity was started for debugging the UI only. */ private boolean isDebugView() { return getIntent().hasExtra(EXTRA_FORCE_VIEW); @@ -211,12 +238,48 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList return viewType.equals(getIntent().getStringExtra(EXTRA_FORCE_VIEW)); } + /** + * Notify the user that we are awaiting input. Currently this sends an audio alert. + */ + private void notifyUser() { + if (mNotificationCountdown > 0) { + Log.d(TAG, "Counting down to notify user..." + mNotificationCountdown); + --mNotificationCountdown; + } else if (mAudioManager != null) { + Log.d(TAG, "Notifying user that we are waiting for input..."); + try { + // Play the standard keypress sound at full volume. This should be available on + // every device. We cannot play a ringtone here because media services aren't + // available yet. A DTMF-style tone is too soft to be noticed, and might not exist + // on tablet devices. The idea is to alert the user that something is needed: this + // does not have to be pleasing. + mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, 100); + } catch (Exception e) { + Log.w(TAG, "notifyUser: Exception while playing sound: " + e); + } + } + // Notify the user again in 5 seconds. + mHandler.removeMessages(MESSAGE_NOTIFY); + mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 5 * 1000); + } + + /** + * Ignore back events after the user has entered the decrypt screen and while the device is + * encrypting. + */ + @Override + public void onBackPressed() { + if (mIgnoreBack) + return; + super.onBackPressed(); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // If we are not encrypted or encrypting, get out quickly. - String state = SystemProperties.get("vold.decrypt"); + final String state = SystemProperties.get("vold.decrypt"); if (!isDebugView() && ("".equals(state) || DECRYPT_STATE.equals(state))) { // Disable the crypt keeper. PackageManager pm = getPackageManager(); @@ -234,18 +297,20 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList return; } - // Disable the status bar + // Disable the status bar, but do NOT disable back because the user needs a way to go + // from keyboard settings and back to the password screen. StatusBarManager sbm = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE); sbm.disable(StatusBarManager.DISABLE_EXPAND | StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_ALERTS | StatusBarManager.DISABLE_SYSTEM_INFO | StatusBarManager.DISABLE_HOME - | StatusBarManager.DISABLE_RECENT - | StatusBarManager.DISABLE_BACK); + | StatusBarManager.DISABLE_RECENT); + setAirplaneModeIfNecessary(); + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); // Check for (and recover) retained instance data - Object lastInstance = getLastNonConfigurationInstance(); + final Object lastInstance = getLastNonConfigurationInstance(); if (lastInstance instanceof NonConfigurationInstanceState) { NonConfigurationInstanceState retained = (NonConfigurationInstanceState) lastInstance; mWakeLock = retained.wakelock; @@ -261,7 +326,6 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList @Override public void onStart() { super.onStart(); - setupUi(); } @@ -276,11 +340,11 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList return; } - String progress = SystemProperties.get("vold.encrypt_progress"); + final String progress = SystemProperties.get("vold.encrypt_progress"); if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) { setContentView(R.layout.crypt_keeper_progress); encryptionProgressInit(); - } else if (mValidationComplete) { + } else if (mValidationComplete || isDebugView(FORCE_VIEW_PASSWORD)) { setContentView(R.layout.crypt_keeper_password_entry); passwordEntryInit(); } else if (!mValidationRequested) { @@ -293,9 +357,9 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList @Override public void onStop() { super.onStop(); - - mHandler.removeMessages(COOLDOWN); - mHandler.removeMessages(UPDATE_PROGRESS); + mHandler.removeMessages(MESSAGE_COOLDOWN); + mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS); + mHandler.removeMessages(MESSAGE_NOTIFY); } /** @@ -322,11 +386,13 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList } } + /** + * Start encrypting the device. + */ private void encryptionProgressInit() { // Accquire a partial wakelock to prevent the device from sleeping. Note // we never release this wakelock as we will be restarted after the device // is encrypted. - Log.d(TAG, "Encryption progress screen initializing."); if (mWakeLock == null) { Log.d(TAG, "Acquiring wakelock."); @@ -335,9 +401,11 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList mWakeLock.acquire(); } - ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar); - progressBar.setIndeterminate(true); - + ((ProgressBar) findViewById(R.id.progress_bar)).setIndeterminate(true); + // Ignore all back presses from now, both hard and soft keys. + mIgnoreBack = true; + // Start the first run of progress manually. This method sets up messages to occur at + // repeated intervals. updateProgress(); } @@ -346,29 +414,29 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList findViewById(R.id.encroid).setVisibility(View.GONE); // Show the reset button, failure text, and a divider - Button button = (Button) findViewById(R.id.factory_reset); + final Button button = (Button) findViewById(R.id.factory_reset); button.setVisibility(View.VISIBLE); button.setOnClickListener(new OnClickListener() { + @Override public void onClick(View v) { // Factory reset the device. sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR")); } }); - TextView tv = (TextView) findViewById(R.id.title); - tv.setText(R.string.crypt_keeper_failed_title); - - tv = (TextView) findViewById(R.id.status); - tv.setText(R.string.crypt_keeper_failed_summary); + // Alert the user of the failure. + ((TextView) findViewById(R.id.title)).setText(R.string.crypt_keeper_failed_title); + ((TextView) findViewById(R.id.status)).setText(R.string.crypt_keeper_failed_summary); - View view = findViewById(R.id.bottom_divider); + final View view = findViewById(R.id.bottom_divider); + // TODO(viki): Why would the bottom divider be missing in certain layouts? Investigate. if (view != null) { view.setVisibility(View.VISIBLE); } } private void updateProgress() { - String state = SystemProperties.get("vold.encrypt_progress"); + final String state = SystemProperties.get("vold.encrypt_progress"); if ("error_partially_encrypted".equals(state)) { showFactoryReset(); @@ -383,33 +451,33 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList Log.w(TAG, "Error parsing progress: " + e.toString()); } - CharSequence status = getText(R.string.crypt_keeper_setup_description); + final CharSequence status = getText(R.string.crypt_keeper_setup_description); Log.v(TAG, "Encryption progress: " + progress); - TextView tv = (TextView) findViewById(R.id.status); - tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress))); - + final TextView tv = (TextView) findViewById(R.id.status); + if (tv != null) { + tv.setText(TextUtils.expandTemplate(status, Integer.toString(progress))); + } // Check the progress every 5 seconds - mHandler.removeMessages(UPDATE_PROGRESS); - mHandler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 5000); + mHandler.removeMessages(MESSAGE_UPDATE_PROGRESS); + mHandler.sendEmptyMessageDelayed(MESSAGE_UPDATE_PROGRESS, 5000); } + /** Disable password input for a while to force the user to waste time between retries */ private void cooldown() { - TextView tv = (TextView) findViewById(R.id.status); + final TextView status = (TextView) findViewById(R.id.status); if (mCooldown <= 0) { - // Re-enable the password entry + // Re-enable the password entry and back presses. mPasswordEntry.setEnabled(true); - - tv.setVisibility(View.GONE); + mIgnoreBack = false; + status.setText(R.string.enter_password); } else { CharSequence template = getText(R.string.crypt_keeper_cooldown); - tv.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown))); - - tv.setVisibility(View.VISIBLE); + status.setText(TextUtils.expandTemplate(template, Integer.toString(mCooldown))); mCooldown--; - mHandler.removeMessages(COOLDOWN); - mHandler.sendEmptyMessageDelayed(COOLDOWN, 1000); // Tick every second + mHandler.removeMessages(MESSAGE_COOLDOWN); + mHandler.sendEmptyMessageDelayed(MESSAGE_COOLDOWN, 1000); // Tick every second } } @@ -417,19 +485,45 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList mPasswordEntry = (EditText) findViewById(R.id.passwordEntry); mPasswordEntry.setOnEditorActionListener(this); mPasswordEntry.requestFocus(); + // Become quiet when the user interacts with the Edit text screen. + mPasswordEntry.setOnKeyListener(this); + mPasswordEntry.setOnTouchListener(this); + mPasswordEntry.addTextChangedListener(this); + + // Disable the Emergency call button if the device has no voice telephone capability + final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); + if (!tm.isVoiceCapable()) { + final View emergencyCall = findViewById(R.id.emergencyCallButton); + if (emergencyCall != null) { + Log.d(TAG, "Removing the emergency Call button"); + emergencyCall.setVisibility(View.GONE); + } + } - View imeSwitcher = findViewById(R.id.switch_ime_button); + final View imeSwitcher = findViewById(R.id.switch_ime_button); final InputMethodManager imm = (InputMethodManager) getSystemService( Context.INPUT_METHOD_SERVICE); if (imeSwitcher != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) { imeSwitcher.setVisibility(View.VISIBLE); imeSwitcher.setOnClickListener(new OnClickListener() { + @Override public void onClick(View v) { imm.showInputMethodPicker(); } }); } + // We want to keep the screen on while waiting for input. In minimal boot mode, the device + // is completely non-functional, and we want the user to notice the device and enter a + // password. + if (mWakeLock == null) { + Log.d(TAG, "Acquiring wakelock."); + final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + if (pm != null) { + mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); + mWakeLock.acquire(); + } + } // Asynchronously throw up the IME, since there are issues with requesting it to be shown // immediately. mHandler.postDelayed(new Runnable() { @@ -439,6 +533,9 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList }, 0); updateEmergencyCallButtonState(); + // Notify the user in 120 seconds that we are waiting for him to enter the password. + mHandler.removeMessages(MESSAGE_NOTIFY); + mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 120 * 1000); } /** @@ -490,7 +587,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList } private IMountService getMountService() { - IBinder service = ServiceManager.getService("mount"); + final IBinder service = ServiceManager.getService("mount"); if (service != null) { return IMountService.Stub.asInterface(service); } @@ -501,7 +598,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) { // Get the password - String password = v.getText().toString(); + final String password = v.getText().toString(); if (TextUtils.isEmpty(password)) { return true; @@ -510,10 +607,10 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList // Now that we have the password clear the password field. v.setText(null); - // Disable the password entry while checking the password. This - // we either be reenabled if the password was wrong or after the - // cooldown period. + // Disable the password entry and back keypress while checking the password. These + // we either be re-enabled if the password was wrong or after the cooldown period. mPasswordEntry.setEnabled(false); + mIgnoreBack = true; Log.d(TAG, "Attempting to send command to decrypt"); new DecryptTask().execute(password); @@ -523,43 +620,72 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList return false; } - // - // Code to update the state of, and handle clicks from, the "Emergency call" button. - // - // This code is mostly duplicated from the corresponding code in - // LockPatternUtils and LockPatternKeyguardView under frameworks/base. - // + /** + * Set airplane mode on the device if it isn't an LTE device. + * Full story: In minimal boot mode, we cannot save any state. In particular, we cannot save + * any incoming SMS's. So SMSs that are received here will be silently dropped to the floor. + * That is bad. Also, we cannot receive any telephone calls in this state. So to avoid + * both these problems, we turn the radio off. However, on certain networks turning on and + * off the radio takes a long time. In such cases, we are better off leaving the radio + * running so the latency of an E911 call is short. + * The behavior after this is: + * 1. Emergency dialing: the emergency dialer has logic to force the device out of + * airplane mode and restart the radio. + * 2. Full boot: we read the persistent settings from the previous boot and restore the + * radio to whatever it was before it restarted. This also happens when rebooting a + * phone that has no encryption. + */ + private final void setAirplaneModeIfNecessary() { + final boolean isLteDevice = + TelephonyManager.getDefault().getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE; + if (!isLteDevice) { + Log.d(TAG, "Going into airplane mode."); + Settings.System.putInt(getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 1); + final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", true); + sendBroadcast(intent); + } + } + /** + * Code to update the state of, and handle clicks from, the "Emergency call" button. + * + * This code is mostly duplicated from the corresponding code in + * LockPatternUtils and LockPatternKeyguardView under frameworks/base. + */ private void updateEmergencyCallButtonState() { - Button button = (Button) findViewById(R.id.emergencyCallButton); + final Button emergencyCall = (Button) findViewById(R.id.emergencyCallButton); // The button isn't present at all in some configurations. - if (button == null) return; + if (emergencyCall == null) + return; if (isEmergencyCallCapable()) { - button.setVisibility(View.VISIBLE); - button.setOnClickListener(new View.OnClickListener() { + emergencyCall.setVisibility(View.VISIBLE); + emergencyCall.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { takeEmergencyCallAction(); } }); } else { - button.setVisibility(View.GONE); + emergencyCall.setVisibility(View.GONE); return; } - int newState = TelephonyManager.getDefault().getCallState(); + final int newState = TelephonyManager.getDefault().getCallState(); int textId; if (newState == TelephonyManager.CALL_STATE_OFFHOOK) { - // show "return to call" text and show phone icon + // Show "return to call" text and show phone icon textId = R.string.cryptkeeper_return_to_call; - int phoneCallIcon = R.drawable.stat_sys_phone_call; - button.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0); + final int phoneCallIcon = R.drawable.stat_sys_phone_call; + emergencyCall.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0); } else { textId = R.string.cryptkeeper_emergency_call; - int emergencyIcon = R.drawable.ic_emergency; - button.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0); + final int emergencyIcon = R.drawable.ic_emergency; + emergencyCall.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0); } - button.setText(textId); + emergencyCall.setText(textId); } private boolean isEmergencyCallCapable() { @@ -575,7 +701,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList } private void resumeCall() { - ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); + final ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); if (phone != null) { try { phone.showCallScreen(); @@ -586,9 +712,44 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList } private void launchEmergencyDialer() { - Intent intent = new Intent(ACTION_EMERGENCY_DIAL); + final Intent intent = new Intent(ACTION_EMERGENCY_DIAL); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(intent); } + + /** + * Listen to key events so we can disable sounds when we get a keyinput in EditText. + */ + private void delayAudioNotification() { + Log.d(TAG, "User entering password: delay audio notification"); + mNotificationCountdown = 20; + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + delayAudioNotification(); + return false; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + delayAudioNotification(); + return false; + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + return; + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + delayAudioNotification(); + } + + @Override + public void afterTextChanged(Editable s) { + return; + } } diff --git a/src/com/android/settings/CryptKeeperSettings.java b/src/com/android/settings/CryptKeeperSettings.java index 41a4be5..ce3ad9d 100644 --- a/src/com/android/settings/CryptKeeperSettings.java +++ b/src/com/android/settings/CryptKeeperSettings.java @@ -159,7 +159,14 @@ public class CryptKeeperSettings extends Fragment { */ private boolean runKeyguardConfirmation(int request) { // 1. Confirm that we have a sufficient PIN/Password to continue - int quality = new LockPatternUtils(getActivity()).getActivePasswordQuality(); + LockPatternUtils lockPatternUtils = new LockPatternUtils(getActivity()); + int quality = lockPatternUtils.getActivePasswordQuality(); + if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK + && lockPatternUtils.isLockPasswordEnabled()) { + // Use the alternate as the quality. We expect this to be + // PASSWORD_QUALITY_SOMETHING(pattern) or PASSWORD_QUALITY_NUMERIC(PIN). + quality = lockPatternUtils.getKeyguardStoredPasswordQuality(); + } if (quality < MIN_PASSWORD_QUALITY) { return false; } diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java index a2f0c3f..13512d4 100644 --- a/src/com/android/settings/DataUsageSummary.java +++ b/src/com/android/settings/DataUsageSummary.java @@ -35,12 +35,14 @@ import static android.net.NetworkTemplate.buildTemplateEthernet; import static android.net.NetworkTemplate.buildTemplateMobile3gLower; import static android.net.NetworkTemplate.buildTemplateMobile4g; import static android.net.NetworkTemplate.buildTemplateMobileAll; -import static android.net.NetworkTemplate.buildTemplateWifi; +import static android.net.NetworkTemplate.buildTemplateWifiWildcard; +import static android.net.TrafficStats.GB_IN_BYTES; +import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UID_REMOVED; import static android.net.TrafficStats.UID_TETHERING; +import static android.telephony.TelephonyManager.SIM_STATE_READY; import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; import static android.text.format.DateUtils.FORMAT_SHOW_DATE; -import static android.text.format.Time.TIMEZONE_UTC; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.settings.Utils.prepareCustomPreferencesList; @@ -67,25 +69,34 @@ import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.INetworkManagementService; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; +import android.os.UserId; import android.preference.Preference; +import android.preference.PreferenceActivity; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.format.Formatter; +import android.text.format.Time; import android.util.Log; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -93,7 +104,6 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; @@ -121,6 +131,7 @@ import com.android.internal.telephony.Phone; import com.android.settings.drawable.InsetBoundsDrawable; import com.android.settings.net.ChartData; import com.android.settings.net.ChartDataLoader; +import com.android.settings.net.DataUsageMeteredSettings; import com.android.settings.net.NetworkPolicyEditor; import com.android.settings.net.SummaryForAllUidLoader; import com.android.settings.net.UidDetail; @@ -131,7 +142,6 @@ import com.android.settings.widget.PieChartView; import com.google.android.collect.Lists; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -139,8 +149,8 @@ import java.util.Locale; import libcore.util.Objects; /** - * Panel show data usage history across various networks, including options to - * inspect based on usage cycle and control through {@link NetworkPolicy}. + * Panel showing data usage history across various networks, including options + * to inspect based on usage cycle and control through {@link NetworkPolicy}. */ public class DataUsageSummary extends Fragment { private static final String TAG = "DataUsage"; @@ -149,7 +159,9 @@ public class DataUsageSummary extends Fragment { // TODO: remove this testing code private static final boolean TEST_ANIM = false; private static final boolean TEST_RADIOS = false; + private static final String TEST_RADIOS_PROP = "test.radios"; + private static final String TEST_SUBSCRIBER_PROP = "test.subscriberid"; private static final String TAB_3G = "3g"; private static final String TAB_4G = "4g"; @@ -166,20 +178,19 @@ public class DataUsageSummary extends Fragment { private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict"; private static final String TAG_DENIED_RESTRICT = "deniedRestrict"; private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict"; + private static final String TAG_CONFIRM_AUTO_SYNC_CHANGE = "confirmAutoSyncChange"; private static final String TAG_APP_DETAILS = "appDetails"; private static final int LOADER_CHART_DATA = 2; private static final int LOADER_SUMMARY = 3; - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; - private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; - private INetworkManagementService mNetworkService; private INetworkStatsService mStatsService; - private INetworkPolicyManager mPolicyService; + private NetworkPolicyManager mPolicyManager; private ConnectivityManager mConnService; + private INetworkStatsSession mStatsSession; + private static final String PREF_FILE = "data_usage"; private static final String PREF_SHOW_WIFI = "show_wifi"; private static final String PREF_SHOW_ETHERNET = "show_ethernet"; @@ -230,7 +241,7 @@ public class DataUsageSummary extends Fragment { private NetworkTemplate mTemplate; private ChartData mChartData; - private int[] mAppDetailUids = null; + private AppItem mCurrentApp = null; private Intent mAppSettingsIntent; @@ -241,6 +252,7 @@ public class DataUsageSummary extends Fragment { private MenuItem mMenuDataRoaming; private MenuItem mMenuRestrictBackground; + private MenuItem mMenuAutoSync; /** Flag used to ignore listeners during binding. */ private boolean mBinding; @@ -250,24 +262,29 @@ public class DataUsageSummary extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Context context = getActivity(); mNetworkService = INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); mStatsService = INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); - mPolicyService = INetworkPolicyManager.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); - mConnService = (ConnectivityManager) getActivity().getSystemService( - Context.CONNECTIVITY_SERVICE); + mPolicyManager = NetworkPolicyManager.from(context); + mConnService = ConnectivityManager.from(context); mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); - mPolicyEditor = new NetworkPolicyEditor(mPolicyService); + mPolicyEditor = new NetworkPolicyEditor(mPolicyManager); mPolicyEditor.read(); mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false); mShowEthernet = mPrefs.getBoolean(PREF_SHOW_ETHERNET, false); + // override preferences when no mobile radio + if (!hasReadyMobileRadio(context)) { + mShowWifi = hasWifiRadio(context); + mShowEthernet = hasEthernet(context); + } + setHasOptionsMenu(true); } @@ -280,6 +297,12 @@ public class DataUsageSummary extends Fragment { mUidDetailProvider = new UidDetailProvider(context); + try { + mStatsSession = mStatsService.openSession(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + mTabHost = (TabHost) view.findViewById(android.R.id.tabhost); mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container); mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs); @@ -372,9 +395,6 @@ public class DataUsageSummary extends Fragment { mUsageSummary = (TextView) mHeader.findViewById(R.id.usage_summary); mEmpty = (TextView) mHeader.findViewById(android.R.id.empty); - // only assign layout transitions once first layout is finished - mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener); - mAdapter = new DataUsageAdapter(mUidDetailProvider, mInsetSide); mListView.setOnItemClickListener(mListListener); mListView.setAdapter(mAdapter); @@ -428,33 +448,53 @@ public class DataUsageSummary extends Fragment { final boolean appDetailMode = isAppDetailMode(); mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming); - mMenuDataRoaming.setVisible(hasMobileRadio(context) && !appDetailMode); + mMenuDataRoaming.setVisible(hasReadyMobileRadio(context) && !appDetailMode); mMenuDataRoaming.setChecked(getDataRoaming()); mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background); - mMenuRestrictBackground.setVisible(hasMobileRadio(context) && !appDetailMode); - mMenuRestrictBackground.setChecked(getRestrictBackground()); + mMenuRestrictBackground.setVisible(hasReadyMobileRadio(context) && !appDetailMode); + mMenuRestrictBackground.setChecked(mPolicyManager.getRestrictBackground()); + + mMenuAutoSync = menu.findItem(R.id.data_usage_menu_auto_sync); + mMenuAutoSync.setChecked(ContentResolver.getMasterSyncAutomatically()); final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g); - split4g.setVisible(hasMobile4gRadio(context) && !appDetailMode); + split4g.setVisible(hasReadyMobile4gRadio(context) && !appDetailMode); split4g.setChecked(isMobilePolicySplit()); final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi); - if (hasWifiRadio(context) && hasMobileRadio(context)) { + if (hasWifiRadio(context) && hasReadyMobileRadio(context)) { showWifi.setVisible(!appDetailMode); showWifi.setChecked(mShowWifi); } else { showWifi.setVisible(false); - mShowWifi = true; } final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet); - if (hasEthernet(context) && hasMobileRadio(context)) { + if (hasEthernet(context) && hasReadyMobileRadio(context)) { showEthernet.setVisible(!appDetailMode); showEthernet.setChecked(mShowEthernet); } else { showEthernet.setVisible(false); - mShowEthernet = true; + } + + final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered); + if (hasReadyMobileRadio(context) || hasWifiRadio(context)) { + metered.setVisible(!appDetailMode); + } else { + metered.setVisible(false); + } + + final MenuItem help = menu.findItem(R.id.data_usage_menu_help); + String helpUrl; + if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_data_usage))) { + Intent helpIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(helpUrl)); + helpIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + help.setIntent(helpIntent); + help.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } else { + help.setVisible(false); } } @@ -474,11 +514,7 @@ public class DataUsageSummary extends Fragment { case R.id.data_usage_menu_restrict_background: { final boolean restrictBackground = !item.isChecked(); if (restrictBackground) { - if (hasLimitedNetworks()) { - ConfirmRestrictFragment.show(this); - } else { - DeniedRestrictFragment.show(this); - } + ConfirmRestrictFragment.show(this); } else { // no confirmation to drop restriction setRestrictBackground(false); @@ -506,50 +542,55 @@ public class DataUsageSummary extends Fragment { updateTabs(); return true; } + case R.id.data_usage_menu_metered: { + final PreferenceActivity activity = (PreferenceActivity) getActivity(); + activity.startPreferencePanel(DataUsageMeteredSettings.class.getCanonicalName(), null, + R.string.data_usage_metered_title, null, this, 0); + return true; + } + case R.id.data_usage_menu_auto_sync: { + ConfirmAutoSyncChangeFragment.show(this, !item.isChecked()); + return true; + } } return false; } @Override - public void onDestroyView() { - super.onDestroyView(); - + public void onDestroy() { mDataEnabledView = null; mDisableAtLimitView = null; mUidDetailProvider.clearCache(); mUidDetailProvider = null; - } - @Override - public void onDestroy() { + TrafficStats.closeQuietly(mStatsSession); + if (this.isRemoving()) { getFragmentManager() .popBackStack(TAG_APP_DETAILS, FragmentManager.POP_BACK_STACK_INCLUSIVE); } + super.onDestroy(); } /** - * Listener to setup {@link LayoutTransition} after first layout pass. + * Build and assign {@link LayoutTransition} to various containers. Should + * only be assigned after initial layout is complete. */ - private OnGlobalLayoutListener mFirstLayoutListener = new OnGlobalLayoutListener() { - /** {@inheritDoc} */ - public void onGlobalLayout() { - mListView.getViewTreeObserver().removeGlobalOnLayoutListener(mFirstLayoutListener); - - mTabsContainer.setLayoutTransition(buildLayoutTransition()); - mHeader.setLayoutTransition(buildLayoutTransition()); - mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition()); - - final LayoutTransition chartTransition = buildLayoutTransition(); - chartTransition.setStartDelay(LayoutTransition.APPEARING, 0); - chartTransition.setStartDelay(LayoutTransition.DISAPPEARING, 0); - chartTransition.setAnimator(LayoutTransition.APPEARING, null); - chartTransition.setAnimator(LayoutTransition.DISAPPEARING, null); - mChart.setLayoutTransition(chartTransition); - } - }; + private void ensureLayoutTransitions() { + // skip when already setup + if (mChart.getLayoutTransition() != null) return; + + mTabsContainer.setLayoutTransition(buildLayoutTransition()); + mHeader.setLayoutTransition(buildLayoutTransition()); + mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition()); + + final LayoutTransition chartTransition = buildLayoutTransition(); + chartTransition.disableTransitionType(LayoutTransition.APPEARING); + chartTransition.disableTransitionType(LayoutTransition.DISAPPEARING); + mChart.setLayoutTransition(chartTransition); + } private static LayoutTransition buildLayoutTransition() { final LayoutTransition transition = new LayoutTransition(); @@ -570,10 +611,10 @@ public class DataUsageSummary extends Fragment { mTabHost.clearAllTabs(); final boolean mobileSplit = isMobilePolicySplit(); - if (mobileSplit && hasMobile4gRadio(context)) { + if (mobileSplit && hasReadyMobile4gRadio(context)) { mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g)); mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g)); - } else if (hasMobileRadio(context)) { + } else if (hasReadyMobileRadio(context)) { mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile)); } if (mShowWifi && hasWifiRadio(context)) { @@ -583,6 +624,7 @@ public class DataUsageSummary extends Fragment { mTabHost.addTab(buildTabSpec(TAB_ETHERNET, R.string.data_usage_tab_ethernet)); } + final boolean noTabs = mTabWidget.getTabCount() == 0; final boolean multipleTabs = mTabWidget.getTabCount() > 1; mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE); if (mIntentTab != null) { @@ -593,6 +635,9 @@ public class DataUsageSummary extends Fragment { mTabHost.setCurrentTabByTag(mIntentTab); } mIntentTab = null; + } else if (noTabs) { + // no usable tabs, so hide body + updateBody(); } else { // already hit updateBody() when added; ignore } @@ -602,7 +647,7 @@ public class DataUsageSummary extends Fragment { * Factory that provide empty {@link View} to make {@link TabHost} happy. */ private TabContentFactory mEmptyTabContent = new TabContentFactory() { - /** {@inheritDoc} */ + @Override public View createTabContent(String tag) { return new View(mTabHost.getContext()); } @@ -617,7 +662,7 @@ public class DataUsageSummary extends Fragment { } private OnTabChangeListener mTabListener = new OnTabChangeListener() { - /** {@inheritDoc} */ + @Override public void onTabChanged(String tabId) { // user changed tab; update body updateBody(); @@ -651,6 +696,9 @@ public class DataUsageSummary extends Fragment { mDataEnabledView.setVisibility(View.VISIBLE); + // TODO: remove mobile tabs when SIM isn't ready + final TelephonyManager tele = TelephonyManager.from(context); + if (TAB_MOBILE.equals(currentTab)) { setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile); setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit); @@ -672,7 +720,7 @@ public class DataUsageSummary extends Fragment { // wifi doesn't have any controls mDataEnabledView.setVisibility(View.GONE); mDisableAtLimitView.setVisibility(View.GONE); - mTemplate = buildTemplateWifi(); + mTemplate = buildTemplateWifiWildcard(); } else if (TAB_ETHERNET.equals(currentTab)) { // ethernet doesn't have any controls @@ -688,7 +736,7 @@ public class DataUsageSummary extends Fragment { // TODO: consider chaining two loaders together instead of reloading // network history when showing app detail. getLoaderManager().restartLoader(LOADER_CHART_DATA, - ChartDataLoader.buildArgs(mTemplate, mAppDetailUids), mChartDataCallbacks); + ChartDataLoader.buildArgs(mTemplate, mCurrentApp), mChartDataCallbacks); // detail mode can change visible menus, invalidate getActivity().invalidateOptionsMenu(); @@ -697,15 +745,11 @@ public class DataUsageSummary extends Fragment { } private boolean isAppDetailMode() { - return mAppDetailUids != null; - } - - private int getAppDetailPrimaryUid() { - return mAppDetailUids[0]; + return mCurrentApp != null; } /** - * Update UID details panels to match {@link #mAppDetailUids}, showing or + * Update UID details panels to match {@link #mCurrentApp}, showing or * hiding them depending on {@link #isAppDetailMode()}. */ private void updateAppDetail() { @@ -729,8 +773,8 @@ public class DataUsageSummary extends Fragment { mChart.bindNetworkPolicy(null); // show icon and all labels appearing under this app - final int primaryUid = getAppDetailPrimaryUid(); - final UidDetail detail = mUidDetailProvider.getUidDetail(primaryUid, true); + final int appId = mCurrentApp.appId; + final UidDetail detail = mUidDetailProvider.getUidDetail(appId, true); mAppIcon.setImageDrawable(detail.icon); mAppTitles.removeAllViews(); @@ -744,7 +788,7 @@ public class DataUsageSummary extends Fragment { // enable settings button when package provides it // TODO: target torwards entire UID instead of just first package - final String[] packageNames = pm.getPackagesForUid(primaryUid); + final String[] packageNames = pm.getPackagesForUid(appId); if (packageNames != null && packageNames.length > 0) { mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); mAppSettingsIntent.setPackage(packageNames[0]); @@ -752,25 +796,20 @@ public class DataUsageSummary extends Fragment { final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null; mAppSettings.setEnabled(matchFound); + mAppSettings.setVisibility(View.VISIBLE); } else { mAppSettingsIntent = null; - mAppSettings.setEnabled(false); + mAppSettings.setVisibility(View.GONE); } updateDetailData(); - if (NetworkPolicyManager.isUidValidForPolicy(context, primaryUid) - && !getRestrictBackground() && isBandwidthControlEnabled() - && hasMobileRadio(context)) { + if (UserId.isApp(appId) && !mPolicyManager.getRestrictBackground() + && isBandwidthControlEnabled() && hasReadyMobileRadio(context)) { setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background); - if (hasLimitedNetworks()) { - setPreferenceSummary(mAppRestrictView, - getString(R.string.data_usage_app_restrict_background_summary)); - } else { - setPreferenceSummary(mAppRestrictView, - getString(R.string.data_usage_app_restrict_background_summary_disabled)); - } + setPreferenceSummary(mAppRestrictView, + getString(R.string.data_usage_app_restrict_background_summary)); mAppRestrictView.setVisibility(View.VISIBLE); mAppRestrict.setChecked(getAppRestrictBackground()); @@ -840,48 +879,22 @@ public class DataUsageSummary extends Fragment { mMenuDataRoaming.setChecked(enabled); } - private boolean getRestrictBackground() { - try { - return mPolicyService.getRestrictBackground(); - } catch (RemoteException e) { - Log.w(TAG, "problem talking with policy service: " + e); - return false; - } - } - - private void setRestrictBackground(boolean restrictBackground) { - if (LOGD) Log.d(TAG, "setRestrictBackground()"); - try { - mPolicyService.setRestrictBackground(restrictBackground); - mMenuRestrictBackground.setChecked(restrictBackground); - } catch (RemoteException e) { - Log.w(TAG, "problem talking with policy service: " + e); - } + public void setRestrictBackground(boolean restrictBackground) { + mPolicyManager.setRestrictBackground(restrictBackground); + mMenuRestrictBackground.setChecked(restrictBackground); } private boolean getAppRestrictBackground() { - final int primaryUid = getAppDetailPrimaryUid(); - final int uidPolicy; - try { - uidPolicy = mPolicyService.getUidPolicy(primaryUid); - } catch (RemoteException e) { - // since we can't do much without policy, we bail hard. - throw new RuntimeException("problem reading network policy", e); - } - + final int appId = mCurrentApp.appId; + final int uidPolicy = mPolicyManager.getAppPolicy(appId); return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; } private void setAppRestrictBackground(boolean restrictBackground) { if (LOGD) Log.d(TAG, "setAppRestrictBackground()"); - final int primaryUid = getAppDetailPrimaryUid(); - try { - mPolicyService.setUidPolicy(primaryUid, - restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); - } catch (RemoteException e) { - throw new RuntimeException("unable to save policy", e); - } - + final int appId = mCurrentApp.appId; + mPolicyManager.setAppPolicy(appId, + restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); mAppRestrict.setChecked(restrictBackground); } @@ -997,7 +1010,7 @@ public class DataUsageSummary extends Fragment { } private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() { - /** {@inheritDoc} */ + @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (mBinding) return; @@ -1018,7 +1031,7 @@ public class DataUsageSummary extends Fragment { }; private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() { - /** {@inheritDoc} */ + @Override public void onClick(View v) { final boolean disableAtLimit = !mDisableAtLimit.isChecked(); if (disableAtLimit) { @@ -1032,21 +1045,15 @@ public class DataUsageSummary extends Fragment { }; private View.OnClickListener mAppRestrictListener = new View.OnClickListener() { - /** {@inheritDoc} */ + @Override public void onClick(View v) { final boolean restrictBackground = !mAppRestrict.isChecked(); if (restrictBackground) { - if (hasLimitedNetworks()) { - // enabling restriction; show confirmation dialog which - // eventually calls setRestrictBackground() once user - // confirms. - ConfirmAppRestrictFragment.show(DataUsageSummary.this); - } else { - // no limited networks; show dialog to guide user towards - // setting a network limit. doesn't mutate restrict state. - DeniedRestrictFragment.show(DataUsageSummary.this); - } + // enabling restriction; show confirmation dialog which + // eventually calls setRestrictBackground() once user + // confirms. + ConfirmAppRestrictFragment.show(DataUsageSummary.this); } else { setAppRestrictBackground(false); } @@ -1054,25 +1061,31 @@ public class DataUsageSummary extends Fragment { }; private OnClickListener mAppSettingsListener = new OnClickListener() { - /** {@inheritDoc} */ + @Override public void onClick(View v) { + if (!isAdded()) return; + // TODO: target torwards entire UID instead of just first package startActivity(mAppSettingsIntent); } }; private OnItemClickListener mListListener = new OnItemClickListener() { - /** {@inheritDoc} */ + @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final Context context = view.getContext(); - final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position); - final UidDetail detail = mUidDetailProvider.getUidDetail(app.uids[0], true); - AppDetailsFragment.show(DataUsageSummary.this, app.uids, detail.label); + final AppItem app = (AppItem) parent.getItemAtPosition(position); + + // TODO: sigh, remove this hack once we understand 6450986 + if (mUidDetailProvider == null || app == null) return; + + final UidDetail detail = mUidDetailProvider.getUidDetail(app.appId, true); + AppDetailsFragment.show(DataUsageSummary.this, app, detail.label); } }; private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { - /** {@inheritDoc} */ + @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position); if (cycle instanceof CycleChangeItem) { @@ -1097,7 +1110,7 @@ public class DataUsageSummary extends Fragment { } } - /** {@inheritDoc} */ + @Override public void onNothingSelected(AdapterView<?> parent) { // ignored } @@ -1154,20 +1167,30 @@ public class DataUsageSummary extends Fragment { final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0; final String totalPhrase = Formatter.formatFileSize(context, totalBytes); - final String rangePhrase = formatDateRange(context, start, end, false); + final String rangePhrase = formatDateRange(context, start, end); - mUsageSummary.setText( - getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase)); + final int summaryRes; + if (TAB_MOBILE.equals(mCurrentTab) || TAB_3G.equals(mCurrentApp) + || TAB_4G.equals(mCurrentApp)) { + summaryRes = R.string.data_usage_total_during_range_mobile; + } else { + summaryRes = R.string.data_usage_total_during_range; + } + + mUsageSummary.setText(getString(summaryRes, totalPhrase, rangePhrase)); + + // initial layout is finished above, ensure we have transitions + ensureLayoutTransitions(); } private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks< ChartData>() { - /** {@inheritDoc} */ + @Override public Loader<ChartData> onCreateLoader(int id, Bundle args) { - return new ChartDataLoader(getActivity(), mStatsService, args); + return new ChartDataLoader(getActivity(), mStatsSession, args); } - /** {@inheritDoc} */ + @Override public void onLoadFinished(Loader<ChartData> loader, ChartData data) { mChartData = data; mChart.bindNetworkStats(mChartData.network); @@ -1183,7 +1206,7 @@ public class DataUsageSummary extends Fragment { } } - /** {@inheritDoc} */ + @Override public void onLoaderReset(Loader<ChartData> loader) { mChartData = null; mChart.bindNetworkStats(null); @@ -1193,20 +1216,22 @@ public class DataUsageSummary extends Fragment { private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks< NetworkStats>() { - /** {@inheritDoc} */ + @Override public Loader<NetworkStats> onCreateLoader(int id, Bundle args) { - return new SummaryForAllUidLoader(getActivity(), mStatsService, args); + return new SummaryForAllUidLoader(getActivity(), mStatsSession, args); } - /** {@inheritDoc} */ + @Override public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) { - mAdapter.bindStats(data); + final int[] restrictedAppIds = mPolicyManager.getAppsWithPolicy( + POLICY_REJECT_METERED_BACKGROUND); + mAdapter.bindStats(data, restrictedAppIds); updateEmptyVisible(); } - /** {@inheritDoc} */ + @Override public void onLoaderReset(Loader<NetworkStats> loader) { - mAdapter.bindStats(null); + mAdapter.bindStats(null, new int[0]); updateEmptyVisible(); } @@ -1216,50 +1241,55 @@ public class DataUsageSummary extends Fragment { } }; + @Deprecated private boolean isMobilePolicySplit() { final Context context = getActivity(); - if (hasMobileRadio(context)) { - final String subscriberId = getActiveSubscriberId(context); - return mPolicyEditor.isMobilePolicySplit(subscriberId); + if (hasReadyMobileRadio(context)) { + final TelephonyManager tele = TelephonyManager.from(context); + return mPolicyEditor.isMobilePolicySplit(getActiveSubscriberId(context)); } else { return false; } } + @Deprecated private void setMobilePolicySplit(boolean split) { - final String subscriberId = getActiveSubscriberId(getActivity()); - mPolicyEditor.setMobilePolicySplit(subscriberId, split); + final Context context = getActivity(); + if (hasReadyMobileRadio(context)) { + final TelephonyManager tele = TelephonyManager.from(context); + mPolicyEditor.setMobilePolicySplit(getActiveSubscriberId(context), split); + } } private static String getActiveSubscriberId(Context context) { - final TelephonyManager telephony = (TelephonyManager) context.getSystemService( - Context.TELEPHONY_SERVICE); - return telephony.getSubscriberId(); + final TelephonyManager tele = TelephonyManager.from(context); + final String actualSubscriberId = tele.getSubscriberId(); + return SystemProperties.get(TEST_SUBSCRIBER_PROP, actualSubscriberId); } private DataUsageChartListener mChartListener = new DataUsageChartListener() { - /** {@inheritDoc} */ + @Override public void onInspectRangeChanged() { if (LOGD) Log.d(TAG, "onInspectRangeChanged()"); updateDetailData(); } - /** {@inheritDoc} */ + @Override public void onWarningChanged() { setPolicyWarningBytes(mChart.getWarningBytes()); } - /** {@inheritDoc} */ + @Override public void onLimitChanged() { setPolicyLimitBytes(mChart.getLimitBytes()); } - /** {@inheritDoc} */ + @Override public void requestWarningEdit() { WarningEditorFragment.show(DataUsageSummary.this); } - /** {@inheritDoc} */ + @Override public void requestLimitEdit() { LimitEditorFragment.show(DataUsageSummary.this); } @@ -1278,7 +1308,7 @@ public class DataUsageSummary extends Fragment { } public CycleItem(Context context, long start, long end) { - this.label = formatDateRange(context, start, end, true); + this.label = formatDateRange(context, start, end); this.start = start; this.end = end; } @@ -1297,7 +1327,7 @@ public class DataUsageSummary extends Fragment { return false; } - /** {@inheritDoc} */ + @Override public int compareTo(CycleItem another) { return Long.compare(start, another.start); } @@ -1307,14 +1337,13 @@ public class DataUsageSummary extends Fragment { private static final java.util.Formatter sFormatter = new java.util.Formatter( sBuilder, Locale.getDefault()); - public static String formatDateRange(Context context, long start, long end, boolean utcTime) { + public static String formatDateRange(Context context, long start, long end) { final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; - final String timezone = utcTime ? TIMEZONE_UTC : null; synchronized (sBuilder) { sBuilder.setLength(0); - return DateUtils - .formatDateRange(context, sFormatter, start, end, flags, timezone).toString(); + return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null) + .toString(); } } @@ -1377,25 +1406,54 @@ public class DataUsageSummary extends Fragment { } } - private static class AppUsageItem implements Comparable<AppUsageItem> { - public int[] uids; + public static class AppItem implements Comparable<AppItem>, Parcelable { + public final int appId; + public boolean restricted; + public SparseBooleanArray uids = new SparseBooleanArray(); public long total; - public AppUsageItem(int uid) { - uids = new int[] { uid }; + public AppItem(int appId) { + this.appId = appId; + } + + public AppItem(Parcel parcel) { + appId = parcel.readInt(); + uids = parcel.readSparseBooleanArray(); + total = parcel.readLong(); } public void addUid(int uid) { - if (contains(uids, uid)) return; - final int length = uids.length; - uids = Arrays.copyOf(uids, length + 1); - uids[length] = uid; + uids.put(uid, true); } - /** {@inheritDoc} */ - public int compareTo(AppUsageItem another) { + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(appId); + dest.writeSparseBooleanArray(uids); + dest.writeLong(total); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public int compareTo(AppItem another) { return Long.compare(another.total, total); } + + public static final Creator<AppItem> CREATOR = new Creator<AppItem>() { + @Override + public AppItem createFromParcel(Parcel in) { + return new AppItem(in); + } + + @Override + public AppItem[] newArray(int size) { + return new AppItem[size]; + } + }; } /** @@ -1405,7 +1463,7 @@ public class DataUsageSummary extends Fragment { private final UidDetailProvider mProvider; private final int mInsetSide; - private ArrayList<AppUsageItem> mItems = Lists.newArrayList(); + private ArrayList<AppItem> mItems = Lists.newArrayList(); private long mLargest; public DataUsageAdapter(UidDetailProvider provider, int insetSide) { @@ -1416,33 +1474,43 @@ public class DataUsageSummary extends Fragment { /** * Bind the given {@link NetworkStats}, or {@code null} to clear list. */ - public void bindStats(NetworkStats stats) { + public void bindStats(NetworkStats stats, int[] restrictedAppIds) { mItems.clear(); - final AppUsageItem systemItem = new AppUsageItem(android.os.Process.SYSTEM_UID); - final SparseArray<AppUsageItem> knownUids = new SparseArray<AppUsageItem>(); + final AppItem systemItem = new AppItem(android.os.Process.SYSTEM_UID); + final SparseArray<AppItem> knownUids = new SparseArray<AppItem>(); NetworkStats.Entry entry = null; final int size = stats != null ? stats.size() : 0; for (int i = 0; i < size; i++) { entry = stats.getValues(i, entry); - final int uid = entry.uid; - final boolean isApp = uid >= android.os.Process.FIRST_APPLICATION_UID - && uid <= android.os.Process.LAST_APPLICATION_UID; - if (isApp || uid == UID_REMOVED || uid == UID_TETHERING) { - AppUsageItem item = knownUids.get(uid); + final boolean isApp = UserId.isApp(entry.uid); + final int appId = isApp ? UserId.getAppId(entry.uid) : entry.uid; + if (isApp || appId == UID_REMOVED || appId == UID_TETHERING) { + AppItem item = knownUids.get(appId); if (item == null) { - item = new AppUsageItem(uid); - knownUids.put(uid, item); + item = new AppItem(appId); + knownUids.put(appId, item); mItems.add(item); } item.total += entry.rxBytes + entry.txBytes; + item.addUid(entry.uid); } else { systemItem.total += entry.rxBytes + entry.txBytes; - systemItem.addUid(uid); + systemItem.addUid(entry.uid); + } + } + + for (int appId : restrictedAppIds) { + AppItem item = knownUids.get(appId); + if (item == null) { + item = new AppItem(appId); + item.total = -1; + mItems.add(item); } + item.restricted = true; } if (systemItem.total > 0) { @@ -1466,7 +1534,7 @@ public class DataUsageSummary extends Fragment { @Override public long getItemId(int position) { - return mItems.get(position).uids[0]; + return mItems.get(position).appId; } @Override @@ -1487,10 +1555,16 @@ public class DataUsageSummary extends Fragment { android.R.id.progress); // kick off async load of app details - final AppUsageItem item = mItems.get(position); + final AppItem item = mItems.get(position); UidDetailTask.bindView(mProvider, item, convertView); - text1.setText(Formatter.formatFileSize(context, item.total)); + if (item.restricted && item.total <= 0) { + text1.setText(R.string.data_usage_app_restricted); + progress.setVisibility(View.GONE); + } else { + text1.setText(Formatter.formatFileSize(context, item.total)); + progress.setVisibility(View.VISIBLE); + } final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0; progress.setProgress(percentTotal); @@ -1504,13 +1578,13 @@ public class DataUsageSummary extends Fragment { * {@link DataUsageSummary}. */ public static class AppDetailsFragment extends Fragment { - private static final String EXTRA_UIDS = "uids"; + private static final String EXTRA_APP = "app"; - public static void show(DataUsageSummary parent, int[] uids, CharSequence label) { + public static void show(DataUsageSummary parent, AppItem app, CharSequence label) { if (!parent.isAdded()) return; final Bundle args = new Bundle(); - args.putIntArray(EXTRA_UIDS, uids); + args.putParcelable(EXTRA_APP, app); final AppDetailsFragment fragment = new AppDetailsFragment(); fragment.setArguments(args); @@ -1519,14 +1593,14 @@ public class DataUsageSummary extends Fragment { ft.add(fragment, TAG_APP_DETAILS); ft.addToBackStack(TAG_APP_DETAILS); ft.setBreadCrumbTitle(label); - ft.commit(); + ft.commitAllowingStateLoss(); } @Override public void onStart() { super.onStart(); final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - target.mAppDetailUids = getArguments().getIntArray(EXTRA_UIDS); + target.mCurrentApp = getArguments().getParcelable(EXTRA_APP); target.updateBody(); } @@ -1534,7 +1608,7 @@ public class DataUsageSummary extends Fragment { public void onStop() { super.onStop(); final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - target.mAppDetailUids = null; + target.mCurrentApp = null; target.updateBody(); } } @@ -1552,22 +1626,21 @@ public class DataUsageSummary extends Fragment { final Resources res = parent.getResources(); final CharSequence message; + final long minLimitBytes = (long) ( + parent.mPolicyEditor.getPolicy(parent.mTemplate).warningBytes * 1.2f); final long limitBytes; // TODO: customize default limits based on network template final String currentTab = parent.mCurrentTab; if (TAB_3G.equals(currentTab)) { - message = buildDialogMessage(res, R.string.data_usage_tab_3g); - limitBytes = 5 * GB_IN_BYTES; + message = res.getString(R.string.data_usage_limit_dialog_mobile); + limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); } else if (TAB_4G.equals(currentTab)) { - message = buildDialogMessage(res, R.string.data_usage_tab_4g); - limitBytes = 5 * GB_IN_BYTES; + message = res.getString(R.string.data_usage_limit_dialog_mobile); + limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); } else if (TAB_MOBILE.equals(currentTab)) { - message = buildDialogMessage(res, R.string.data_usage_list_mobile); - limitBytes = 5 * GB_IN_BYTES; - } else if (TAB_WIFI.equals(currentTab)) { - message = buildDialogMessage(res, R.string.data_usage_tab_wifi); - limitBytes = 5 * GB_IN_BYTES; + message = res.getString(R.string.data_usage_limit_dialog_mobile); + limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); } else { throw new IllegalArgumentException("unknown current tab: " + currentTab); } @@ -1582,10 +1655,6 @@ public class DataUsageSummary extends Fragment { dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT); } - private static CharSequence buildDialogMessage(Resources res, int networkResId) { - return res.getString(R.string.data_usage_limit_dialog, res.getString(networkResId)); - } - @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Context context = getActivity(); @@ -1598,6 +1667,7 @@ public class DataUsageSummary extends Fragment { builder.setMessage(message); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); if (target != null) { @@ -1653,9 +1723,11 @@ public class DataUsageSummary extends Fragment { builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { final int cycleDay = cycleDayPicker.getValue(); - editor.setPolicyCycleDay(template, cycleDay); + final String cycleTimezone = new Time().timezone; + editor.setPolicyCycleDay(template, cycleDay, cycleTimezone); target.updatePolicy(true); } }); @@ -1712,6 +1784,7 @@ public class DataUsageSummary extends Fragment { builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { // clear focus to finish pending text edits bytesPicker.clearFocus(); @@ -1774,6 +1847,7 @@ public class DataUsageSummary extends Fragment { builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { // clear focus to finish pending text edits bytesPicker.clearFocus(); @@ -1807,6 +1881,7 @@ public class DataUsageSummary extends Fragment { builder.setMessage(R.string.data_usage_disable_mobile); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); if (target != null) { @@ -1823,7 +1898,7 @@ public class DataUsageSummary extends Fragment { /** * Dialog to request user confirmation before setting - * {@link Settings.Secure#DATA_ROAMING}. + * {@link android.provider.Settings.Secure#DATA_ROAMING}. */ public static class ConfirmDataRoamingFragment extends DialogFragment { public static void show(DataUsageSummary parent) { @@ -1843,6 +1918,7 @@ public class DataUsageSummary extends Fragment { builder.setMessage(R.string.roaming_warning); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); if (target != null) { @@ -1878,6 +1954,7 @@ public class DataUsageSummary extends Fragment { builder.setMessage(getString(R.string.data_usage_restrict_background)); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); if (target != null) { @@ -1940,6 +2017,7 @@ public class DataUsageSummary extends Fragment { builder.setMessage(R.string.data_usage_app_restrict_dialog); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); if (target != null) { @@ -1954,6 +2032,56 @@ public class DataUsageSummary extends Fragment { } /** + * Dialog to inform user about changing auto-sync setting + */ + public static class ConfirmAutoSyncChangeFragment extends DialogFragment { + private static final String SAVE_ENABLING = "enabling"; + private boolean mEnabling; + + public static void show(DataUsageSummary parent, boolean enabling) { + if (!parent.isAdded()) return; + + final ConfirmAutoSyncChangeFragment dialog = new ConfirmAutoSyncChangeFragment(); + dialog.mEnabling = enabling; + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_CONFIRM_AUTO_SYNC_CHANGE); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + if (savedInstanceState != null) { + mEnabling = savedInstanceState.getBoolean(SAVE_ENABLING); + } + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + if (!mEnabling) { + builder.setTitle(R.string.data_usage_auto_sync_off_dialog_title); + builder.setMessage(R.string.data_usage_auto_sync_off_dialog); + } else { + builder.setTitle(R.string.data_usage_auto_sync_on_dialog_title); + builder.setMessage(R.string.data_usage_auto_sync_on_dialog); + } + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ContentResolver.setMasterSyncAutomatically(mEnabling); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + + return builder.create(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(SAVE_ENABLING, mEnabling); + } + } + + /** * Compute default tab that should be selected, based on * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra. */ @@ -1981,23 +2109,23 @@ public class DataUsageSummary extends Fragment { */ private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> { private final UidDetailProvider mProvider; - private final AppUsageItem mItem; + private final AppItem mItem; private final View mTarget; - private UidDetailTask(UidDetailProvider provider, AppUsageItem item, View target) { + private UidDetailTask(UidDetailProvider provider, AppItem item, View target) { mProvider = checkNotNull(provider); mItem = checkNotNull(item); mTarget = checkNotNull(target); } public static void bindView( - UidDetailProvider provider, AppUsageItem item, View target) { + UidDetailProvider provider, AppItem item, View target) { final UidDetailTask existing = (UidDetailTask) target.getTag(); if (existing != null) { existing.cancel(false); } - final UidDetail cachedDetail = provider.getUidDetail(item.uids[0], false); + final UidDetail cachedDetail = provider.getUidDetail(item.appId, false); if (cachedDetail != null) { bindView(cachedDetail, target); } else { @@ -2026,7 +2154,7 @@ public class DataUsageSummary extends Fragment { @Override protected UidDetail doInBackground(Void... params) { - return mProvider.getUidDetail(mItem.uids[0], true); + return mProvider.getUidDetail(mItem.appId, true); } @Override @@ -2036,22 +2164,24 @@ public class DataUsageSummary extends Fragment { } /** - * Test if device has a mobile data radio. + * Test if device has a mobile data radio with SIM in ready state. */ - private static boolean hasMobileRadio(Context context) { + public static boolean hasReadyMobileRadio(Context context) { if (TEST_RADIOS) { return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile"); } - final ConnectivityManager conn = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); - return conn.isNetworkSupported(TYPE_MOBILE); + final ConnectivityManager conn = ConnectivityManager.from(context); + final TelephonyManager tele = TelephonyManager.from(context); + + // require both supported network and ready SIM + return conn.isNetworkSupported(TYPE_MOBILE) && tele.getSimState() == SIM_STATE_READY; } /** * Test if device has a mobile 4G data radio. */ - private static boolean hasMobile4gRadio(Context context) { + public static boolean hasReadyMobile4gRadio(Context context) { if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) { return false; } @@ -2059,40 +2189,53 @@ public class DataUsageSummary extends Fragment { return SystemProperties.get(TEST_RADIOS_PROP).contains("4g"); } - final ConnectivityManager conn = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); - final TelephonyManager telephony = (TelephonyManager) context.getSystemService( - Context.TELEPHONY_SERVICE); + final ConnectivityManager conn = ConnectivityManager.from(context); + final TelephonyManager tele = TelephonyManager.from(context); final boolean hasWimax = conn.isNetworkSupported(TYPE_WIMAX); - final boolean hasLte = telephony.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE; + final boolean hasLte = (tele.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE) + && hasReadyMobileRadio(context); return hasWimax || hasLte; } /** * Test if device has a Wi-Fi data radio. */ - private static boolean hasWifiRadio(Context context) { + public static boolean hasWifiRadio(Context context) { if (TEST_RADIOS) { return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi"); } - final ConnectivityManager conn = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); + final ConnectivityManager conn = ConnectivityManager.from(context); return conn.isNetworkSupported(TYPE_WIFI); } /** * Test if device has an ethernet network connection. */ - private static boolean hasEthernet(Context context) { + public boolean hasEthernet(Context context) { if (TEST_RADIOS) { return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet"); } - final ConnectivityManager conn = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); - return conn.isNetworkSupported(TYPE_ETHERNET); + final ConnectivityManager conn = ConnectivityManager.from(context); + final boolean hasEthernet = conn.isNetworkSupported(TYPE_ETHERNET); + + final long ethernetBytes; + if (mStatsSession != null) { + try { + ethernetBytes = mStatsSession.getSummaryForNetwork( + NetworkTemplate.buildTemplateEthernet(), Long.MIN_VALUE, Long.MAX_VALUE) + .getTotalBytes(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } else { + ethernetBytes = 0; + } + + // only show ethernet when both hardware present and traffic has occurred + return hasEthernet && ethernetBytes > 0; } /** @@ -2126,6 +2269,7 @@ public class DataUsageSummary extends Fragment { * Build string describing currently limited networks, which defines when * background data is restricted. */ + @Deprecated private CharSequence buildLimitedNetworksString() { final List<CharSequence> limited = buildLimitedNetworksList(); @@ -2141,22 +2285,28 @@ public class DataUsageSummary extends Fragment { * Build list of currently limited networks, which defines when background * data is restricted. */ + @Deprecated private List<CharSequence> buildLimitedNetworksList() { final Context context = getActivity(); - final String subscriberId = getActiveSubscriberId(context); // build combined list of all limited networks final ArrayList<CharSequence> limited = Lists.newArrayList(); - if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) { - limited.add(getText(R.string.data_usage_list_mobile)); - } - if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) { - limited.add(getText(R.string.data_usage_tab_3g)); - } - if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) { - limited.add(getText(R.string.data_usage_tab_4g)); + + final TelephonyManager tele = TelephonyManager.from(context); + if (tele.getSimState() == SIM_STATE_READY) { + final String subscriberId = getActiveSubscriberId(context); + if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) { + limited.add(getText(R.string.data_usage_list_mobile)); + } + if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) { + limited.add(getText(R.string.data_usage_tab_3g)); + } + if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) { + limited.add(getText(R.string.data_usage_tab_4g)); + } } - if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifi())) { + + if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifiWildcard())) { limited.add(getText(R.string.data_usage_tab_wifi)); } if (mPolicyEditor.hasLimitedPolicy(buildTemplateEthernet())) { @@ -2202,13 +2352,4 @@ public class DataUsageSummary extends Fragment { summary.setVisibility(View.VISIBLE); summary.setText(string); } - - private static boolean contains(int[] haystack, int needle) { - for (int value : haystack) { - if (value == needle) { - return true; - } - } - return false; - } } diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java index 2ffae19..29c58ab 100644 --- a/src/com/android/settings/DevelopmentSettings.java +++ b/src/com/android/settings/DevelopmentSettings.java @@ -16,16 +16,24 @@ package com.android.settings; +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; + +import android.app.ActionBar; +import android.app.Activity; import android.app.ActivityManagerNative; +import android.app.ActivityThread; import android.app.AlertDialog; import android.app.Dialog; +import android.app.DialogFragment; import android.app.backup.IBackupManager; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.VerifierDeviceIdentity; +import android.os.AsyncTask; import android.os.BatteryManager; import android.os.Build; import android.os.Bundle; @@ -35,41 +43,58 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemProperties; +import android.os.Trace; import android.preference.CheckBoxPreference; import android.preference.ListPreference; +import android.preference.MultiCheckPreference; import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; -import android.preference.Preference.OnPreferenceChangeListener; import android.provider.Settings; import android.text.TextUtils; +import android.view.Gravity; +import android.view.HardwareRenderer; import android.view.IWindowManager; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.Switch; + +import java.util.ArrayList; /* * Displays preferences for application developers. */ public class DevelopmentSettings extends PreferenceFragment implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener, - OnPreferenceChangeListener { + OnPreferenceChangeListener, CompoundButton.OnCheckedChangeListener { private static final String ENABLE_ADB = "enable_adb"; - - private static final String VERIFIER_DEVICE_IDENTIFIER = "verifier_device_identifier"; private static final String KEEP_SCREEN_ON = "keep_screen_on"; private static final String ALLOW_MOCK_LOCATION = "allow_mock_location"; private static final String HDCP_CHECKING_KEY = "hdcp_checking"; private static final String HDCP_CHECKING_PROPERTY = "persist.sys.hdcp_checking"; + private static final String ENFORCE_READ_EXTERNAL = "enforce_read_external"; private static final String LOCAL_BACKUP_PASSWORD = "local_backup_password"; private static final String HARDWARE_UI_PROPERTY = "persist.sys.ui.hw"; + private static final String DEBUG_APP_KEY = "debug_app"; + private static final String WAIT_FOR_DEBUGGER_KEY = "wait_for_debugger"; private static final String STRICT_MODE_KEY = "strict_mode"; private static final String POINTER_LOCATION_KEY = "pointer_location"; private static final String SHOW_TOUCHES_KEY = "show_touches"; private static final String SHOW_SCREEN_UPDATES_KEY = "show_screen_updates"; + private static final String DISABLE_OVERLAYS_KEY = "disable_overlays"; private static final String SHOW_CPU_USAGE_KEY = "show_cpu_usage"; private static final String FORCE_HARDWARE_UI_KEY = "force_hw_ui"; + private static final String TRACK_FRAME_TIME_KEY = "track_frame_time"; + private static final String SHOW_HW_SCREEN_UPDATES_KEY = "show_hw_screen_udpates"; + private static final String DEBUG_LAYOUT_KEY = "debug_layout"; private static final String WINDOW_ANIMATION_SCALE_KEY = "window_animation_scale"; private static final String TRANSITION_ANIMATION_SCALE_KEY = "transition_animation_scale"; + private static final String ANIMATOR_DURATION_SCALE_KEY = "animator_duration_scale"; + + private static final String ENABLE_TRACES_KEY = "enable_traces"; private static final String IMMEDIATELY_DESTROY_ACTIVITIES_KEY = "immediately_destroy_activities"; @@ -77,32 +102,56 @@ public class DevelopmentSettings extends PreferenceFragment private static final String SHOW_ALL_ANRS_KEY = "show_all_anrs"; + private static final String TAG_CONFIRM_ENFORCE = "confirm_enforce"; + + private static final int RESULT_DEBUG_APP = 1000; + private IWindowManager mWindowManager; private IBackupManager mBackupManager; + private Switch mEnabledSwitch; + private boolean mLastEnabledState; + private boolean mHaveDebugSettings; + private boolean mDontPokeProperties; + private CheckBoxPreference mEnableAdb; private CheckBoxPreference mKeepScreenOn; + private CheckBoxPreference mEnforceReadExternal; private CheckBoxPreference mAllowMockLocation; private PreferenceScreen mPassword; + private String mDebugApp; + private Preference mDebugAppPref; + private CheckBoxPreference mWaitForDebugger; + private CheckBoxPreference mStrictMode; private CheckBoxPreference mPointerLocation; private CheckBoxPreference mShowTouches; private CheckBoxPreference mShowScreenUpdates; + private CheckBoxPreference mDisableOverlays; private CheckBoxPreference mShowCpuUsage; private CheckBoxPreference mForceHardwareUi; + private CheckBoxPreference mTrackFrameTime; + private CheckBoxPreference mShowHwScreenUpdates; + private CheckBoxPreference mDebugLayout; private ListPreference mWindowAnimationScale; private ListPreference mTransitionAnimationScale; + private ListPreference mAnimatorDurationScale; + private MultiCheckPreference mEnableTracesPref; private CheckBoxPreference mImmediatelyDestroyActivities; private ListPreference mAppProcessLimit; private CheckBoxPreference mShowAllANRs; - // To track whether Yes was clicked in the adb warning dialog - private boolean mOkClicked; + private final ArrayList<Preference> mAllPrefs = new ArrayList<Preference>(); + private final ArrayList<CheckBoxPreference> mResetCbPrefs + = new ArrayList<CheckBoxPreference>(); - private Dialog mOkDialog; + // To track whether a confirmation dialog was clicked. + private boolean mDialogClicked; + private Dialog mEnableDialog; + private Dialog mAdbDialog; @Override public void onCreate(Bundle icicle) { @@ -114,75 +163,204 @@ public class DevelopmentSettings extends PreferenceFragment addPreferencesFromResource(R.xml.development_prefs); - mEnableAdb = (CheckBoxPreference) findPreference(ENABLE_ADB); - mKeepScreenOn = (CheckBoxPreference) findPreference(KEEP_SCREEN_ON); - mAllowMockLocation = (CheckBoxPreference) findPreference(ALLOW_MOCK_LOCATION); + mEnableAdb = findAndInitCheckboxPref(ENABLE_ADB); + mKeepScreenOn = findAndInitCheckboxPref(KEEP_SCREEN_ON); + mEnforceReadExternal = findAndInitCheckboxPref(ENFORCE_READ_EXTERNAL); + mAllowMockLocation = findAndInitCheckboxPref(ALLOW_MOCK_LOCATION); mPassword = (PreferenceScreen) findPreference(LOCAL_BACKUP_PASSWORD); - - mStrictMode = (CheckBoxPreference) findPreference(STRICT_MODE_KEY); - mPointerLocation = (CheckBoxPreference) findPreference(POINTER_LOCATION_KEY); - mShowTouches = (CheckBoxPreference) findPreference(SHOW_TOUCHES_KEY); - mShowScreenUpdates = (CheckBoxPreference) findPreference(SHOW_SCREEN_UPDATES_KEY); - mShowCpuUsage = (CheckBoxPreference) findPreference(SHOW_CPU_USAGE_KEY); - mForceHardwareUi = (CheckBoxPreference) findPreference(FORCE_HARDWARE_UI_KEY); + mAllPrefs.add(mPassword); + + mDebugAppPref = findPreference(DEBUG_APP_KEY); + mAllPrefs.add(mDebugAppPref); + mWaitForDebugger = findAndInitCheckboxPref(WAIT_FOR_DEBUGGER_KEY); + mStrictMode = findAndInitCheckboxPref(STRICT_MODE_KEY); + mPointerLocation = findAndInitCheckboxPref(POINTER_LOCATION_KEY); + mShowTouches = findAndInitCheckboxPref(SHOW_TOUCHES_KEY); + mShowScreenUpdates = findAndInitCheckboxPref(SHOW_SCREEN_UPDATES_KEY); + mDisableOverlays = findAndInitCheckboxPref(DISABLE_OVERLAYS_KEY); + mShowCpuUsage = findAndInitCheckboxPref(SHOW_CPU_USAGE_KEY); + mForceHardwareUi = findAndInitCheckboxPref(FORCE_HARDWARE_UI_KEY); + mTrackFrameTime = findAndInitCheckboxPref(TRACK_FRAME_TIME_KEY); + mShowHwScreenUpdates = findAndInitCheckboxPref(SHOW_HW_SCREEN_UPDATES_KEY); + mDebugLayout = findAndInitCheckboxPref(DEBUG_LAYOUT_KEY); mWindowAnimationScale = (ListPreference) findPreference(WINDOW_ANIMATION_SCALE_KEY); + mAllPrefs.add(mWindowAnimationScale); mWindowAnimationScale.setOnPreferenceChangeListener(this); mTransitionAnimationScale = (ListPreference) findPreference(TRANSITION_ANIMATION_SCALE_KEY); + mAllPrefs.add(mTransitionAnimationScale); mTransitionAnimationScale.setOnPreferenceChangeListener(this); + mAnimatorDurationScale = (ListPreference) findPreference(ANIMATOR_DURATION_SCALE_KEY); + mAllPrefs.add(mAnimatorDurationScale); + mAnimatorDurationScale.setOnPreferenceChangeListener(this); + mEnableTracesPref = (MultiCheckPreference)findPreference(ENABLE_TRACES_KEY); + String[] traceValues = new String[Trace.TRACE_TAGS.length]; + for (int i=Trace.TRACE_FLAGS_START_BIT; i<traceValues.length; i++) { + traceValues[i] = Integer.toString(1<<i); + } + mEnableTracesPref.setEntries(Trace.TRACE_TAGS); + mEnableTracesPref.setEntryValues(traceValues); + mAllPrefs.add(mEnableTracesPref); + mEnableTracesPref.setOnPreferenceChangeListener(this); mImmediatelyDestroyActivities = (CheckBoxPreference) findPreference( IMMEDIATELY_DESTROY_ACTIVITIES_KEY); + mAllPrefs.add(mImmediatelyDestroyActivities); + mResetCbPrefs.add(mImmediatelyDestroyActivities); mAppProcessLimit = (ListPreference) findPreference(APP_PROCESS_LIMIT_KEY); + mAllPrefs.add(mAppProcessLimit); mAppProcessLimit.setOnPreferenceChangeListener(this); mShowAllANRs = (CheckBoxPreference) findPreference( SHOW_ALL_ANRS_KEY); + mAllPrefs.add(mShowAllANRs); + mResetCbPrefs.add(mShowAllANRs); - final Preference verifierDeviceIdentifier = findPreference(VERIFIER_DEVICE_IDENTIFIER); - final PackageManager pm = getActivity().getPackageManager(); - final VerifierDeviceIdentity verifierIndentity = pm.getVerifierDeviceIdentity(); - if (verifierIndentity != null) { - verifierDeviceIdentifier.setSummary(verifierIndentity.toString()); + Preference hdcpChecking = findPreference(HDCP_CHECKING_KEY); + if (hdcpChecking != null) { + mAllPrefs.add(hdcpChecking); } - removeHdcpOptionsForProduction(); } + private CheckBoxPreference findAndInitCheckboxPref(String key) { + CheckBoxPreference pref = (CheckBoxPreference) findPreference(key); + if (pref == null) { + throw new IllegalArgumentException("Cannot find preference with key = " + key); + } + mAllPrefs.add(pref); + mResetCbPrefs.add(pref); + return pref; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Activity activity = getActivity(); + mEnabledSwitch = new Switch(activity); + + final int padding = activity.getResources().getDimensionPixelSize( + R.dimen.action_bar_switch_padding); + mEnabledSwitch.setPadding(0, 0, padding, 0); + mEnabledSwitch.setOnCheckedChangeListener(this); + } + + @Override + public void onStart() { + super.onStart(); + final Activity activity = getActivity(); + activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, + ActionBar.DISPLAY_SHOW_CUSTOM); + activity.getActionBar().setCustomView(mEnabledSwitch, new ActionBar.LayoutParams( + ActionBar.LayoutParams.WRAP_CONTENT, + ActionBar.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_VERTICAL | Gravity.RIGHT)); + } + + @Override + public void onStop() { + super.onStop(); + final Activity activity = getActivity(); + activity.getActionBar().setDisplayOptions(0, ActionBar.DISPLAY_SHOW_CUSTOM); + activity.getActionBar().setCustomView(null); + } + private void removeHdcpOptionsForProduction() { if ("user".equals(Build.TYPE)) { Preference hdcpChecking = findPreference(HDCP_CHECKING_KEY); if (hdcpChecking != null) { // Remove the preference getPreferenceScreen().removePreference(hdcpChecking); + mAllPrefs.remove(hdcpChecking); } } } + private void setPrefsEnabledState(boolean enabled) { + for (int i = 0; i < mAllPrefs.size(); i++) { + mAllPrefs.get(i).setEnabled(enabled); + } + updateAllOptions(); + } + @Override public void onResume() { super.onResume(); final ContentResolver cr = getActivity().getContentResolver(); - mEnableAdb.setChecked(Settings.Secure.getInt(cr, + mLastEnabledState = Settings.Secure.getInt(cr, + Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; + mEnabledSwitch.setChecked(mLastEnabledState); + setPrefsEnabledState(mLastEnabledState); + + if (mHaveDebugSettings && !mLastEnabledState) { + // Overall debugging is disabled, but there are some debug + // settings that are enabled. This is an invalid state. Switch + // to debug settings being enabled, so the user knows there is + // stuff enabled and can turn it all off if they want. + Settings.Secure.putInt(getActivity().getContentResolver(), + Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED, 1); + mLastEnabledState = true; + setPrefsEnabledState(mLastEnabledState); + } + } + + void updateCheckBox(CheckBoxPreference checkBox, boolean value) { + checkBox.setChecked(value); + mHaveDebugSettings |= value; + } + + private void updateAllOptions() { + final Context context = getActivity(); + final ContentResolver cr = context.getContentResolver(); + mHaveDebugSettings = false; + updateCheckBox(mEnableAdb, Settings.Secure.getInt(cr, Settings.Secure.ADB_ENABLED, 0) != 0); - mKeepScreenOn.setChecked(Settings.System.getInt(cr, + updateCheckBox(mKeepScreenOn, Settings.System.getInt(cr, Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0) != 0); - mAllowMockLocation.setChecked(Settings.Secure.getInt(cr, + updateCheckBox(mEnforceReadExternal, isPermissionEnforced(context, READ_EXTERNAL_STORAGE)); + updateCheckBox(mAllowMockLocation, Settings.Secure.getInt(cr, Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0); updateHdcpValues(); updatePasswordSummary(); + updateDebuggerOptions(); updateStrictModeVisualOptions(); updatePointerLocationOptions(); updateShowTouchesOptions(); updateFlingerOptions(); updateCpuUsageOptions(); updateHardwareUiOptions(); + updateTrackFrameTimeOptions(); + updateShowHwScreenUpdatesOptions(); + updateDebugLayoutOptions(); updateAnimationScaleOptions(); + updateEnableTracesOptions(); updateImmediatelyDestroyActivitiesOptions(); updateAppProcessLimitOptions(); updateShowAllANRsOptions(); } + private void resetDangerousOptions() { + mDontPokeProperties = true; + for (int i=0; i<mResetCbPrefs.size(); i++) { + CheckBoxPreference cb = mResetCbPrefs.get(i); + if (cb.isChecked()) { + cb.setChecked(false); + onPreferenceTreeClick(null, cb); + } + } + resetDebuggerOptions(); + writeAnimationScaleOption(0, mWindowAnimationScale, null); + writeAnimationScaleOption(1, mTransitionAnimationScale, null); + writeAnimationScaleOption(2, mAnimatorDurationScale, null); + writeEnableTracesOptions(0); + writeAppProcessLimitOptions(null); + mHaveDebugSettings = false; + updateAllOptions(); + mDontPokeProperties = false; + pokeSystemProperties(); + } + private void updateHdcpValues() { int index = 1; // Defaults to drm-only. Needs to match with R.array.hdcp_checking_values ListPreference hdcpChecking = (ListPreference) findPreference(HDCP_CHECKING_KEY); @@ -214,12 +392,52 @@ public class DevelopmentSettings extends PreferenceFragment } } + private void writeDebuggerOptions() { + try { + ActivityManagerNative.getDefault().setDebugApp( + mDebugApp, mWaitForDebugger.isChecked(), true); + } catch (RemoteException ex) { + } + } + + private static void resetDebuggerOptions() { + try { + ActivityManagerNative.getDefault().setDebugApp( + null, false, true); + } catch (RemoteException ex) { + } + } + + private void updateDebuggerOptions() { + mDebugApp = Settings.System.getString( + getActivity().getContentResolver(), Settings.System.DEBUG_APP); + updateCheckBox(mWaitForDebugger, Settings.System.getInt( + getActivity().getContentResolver(), Settings.System.WAIT_FOR_DEBUGGER, 0) != 0); + if (mDebugApp != null && mDebugApp.length() > 0) { + String label; + try { + ApplicationInfo ai = getActivity().getPackageManager().getApplicationInfo(mDebugApp, + PackageManager.GET_DISABLED_COMPONENTS); + CharSequence lab = getActivity().getPackageManager().getApplicationLabel(ai); + label = lab != null ? lab.toString() : mDebugApp; + } catch (PackageManager.NameNotFoundException e) { + label = mDebugApp; + } + mDebugAppPref.setSummary(getResources().getString(R.string.debug_app_set, label)); + mWaitForDebugger.setEnabled(true); + mHaveDebugSettings = true; + } else { + mDebugAppPref.setSummary(getResources().getString(R.string.debug_app_not_set)); + mWaitForDebugger.setEnabled(false); + } + } + // Returns the current state of the system property that controls // strictmode flashes. One of: // 0: not explicitly set one way or another // 1: on // 2: off - private int currentStrictModeActiveIndex() { + private static int currentStrictModeActiveIndex() { if (TextUtils.isEmpty(SystemProperties.get(StrictMode.VISUAL_PROPERTY))) { return 0; } @@ -236,7 +454,7 @@ public class DevelopmentSettings extends PreferenceFragment } private void updateStrictModeVisualOptions() { - mStrictMode.setChecked(currentStrictModeActiveIndex() == 1); + updateCheckBox(mStrictMode, currentStrictModeActiveIndex() == 1); } private void writePointerLocationOptions() { @@ -245,7 +463,7 @@ public class DevelopmentSettings extends PreferenceFragment } private void updatePointerLocationOptions() { - mPointerLocation.setChecked(Settings.System.getInt(getActivity().getContentResolver(), + updateCheckBox(mPointerLocation, Settings.System.getInt(getActivity().getContentResolver(), Settings.System.POINTER_LOCATION, 0) != 0); } @@ -255,7 +473,7 @@ public class DevelopmentSettings extends PreferenceFragment } private void updateShowTouchesOptions() { - mShowTouches.setChecked(Settings.System.getInt(getActivity().getContentResolver(), + updateCheckBox(mShowTouches, Settings.System.getInt(getActivity().getContentResolver(), Settings.System.SHOW_TOUCHES, 0) != 0); } @@ -273,9 +491,11 @@ public class DevelopmentSettings extends PreferenceFragment @SuppressWarnings("unused") int enableGL = reply.readInt(); int showUpdates = reply.readInt(); - mShowScreenUpdates.setChecked(showUpdates != 0); + updateCheckBox(mShowScreenUpdates, showUpdates != 0); @SuppressWarnings("unused") int showBackground = reply.readInt(); + int disableOverlays = reply.readInt(); + updateCheckBox(mDisableOverlays, disableOverlays != 0); reply.recycle(); data.recycle(); } @@ -283,13 +503,14 @@ public class DevelopmentSettings extends PreferenceFragment } } - private void writeFlingerOptions() { + private void writeShowUpdatesOption() { try { IBinder flinger = ServiceManager.getService("SurfaceFlinger"); if (flinger != null) { Parcel data = Parcel.obtain(); data.writeInterfaceToken("android.ui.ISurfaceComposer"); - data.writeInt(mShowScreenUpdates.isChecked() ? 1 : 0); + final int showUpdates = mShowScreenUpdates.isChecked() ? 1 : 0; + data.writeInt(showUpdates); flinger.transact(1002, data, null, 0); data.recycle(); @@ -299,16 +520,67 @@ public class DevelopmentSettings extends PreferenceFragment } } + private void writeDisableOverlaysOption() { + try { + IBinder flinger = ServiceManager.getService("SurfaceFlinger"); + if (flinger != null) { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + final int disableOverlays = mDisableOverlays.isChecked() ? 1 : 0; + data.writeInt(disableOverlays); + flinger.transact(1008, data, null, 0); + data.recycle(); + + updateFlingerOptions(); + } + } catch (RemoteException ex) { + } + } + private void updateHardwareUiOptions() { - mForceHardwareUi.setChecked(SystemProperties.getBoolean(HARDWARE_UI_PROPERTY, false)); + updateCheckBox(mForceHardwareUi, SystemProperties.getBoolean(HARDWARE_UI_PROPERTY, false)); } private void writeHardwareUiOptions() { SystemProperties.set(HARDWARE_UI_PROPERTY, mForceHardwareUi.isChecked() ? "true" : "false"); + pokeSystemProperties(); + } + + private void updateTrackFrameTimeOptions() { + updateCheckBox(mTrackFrameTime, + SystemProperties.getBoolean(HardwareRenderer.PROFILE_PROPERTY, false)); + } + + private void writeTrackFrameTimeOptions() { + SystemProperties.set(HardwareRenderer.PROFILE_PROPERTY, + mTrackFrameTime.isChecked() ? "true" : "false"); + pokeSystemProperties(); + } + + private void updateShowHwScreenUpdatesOptions() { + updateCheckBox(mShowHwScreenUpdates, + SystemProperties.getBoolean(HardwareRenderer.DEBUG_DIRTY_REGIONS_PROPERTY, false)); + } + + private void writeShowHwScreenUpdatesOptions() { + SystemProperties.set(HardwareRenderer.DEBUG_DIRTY_REGIONS_PROPERTY, + mShowHwScreenUpdates.isChecked() ? "true" : "false"); + pokeSystemProperties(); + } + + private void updateDebugLayoutOptions() { + updateCheckBox(mDebugLayout, + SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false)); + } + + private void writeDebugLayoutOptions() { + SystemProperties.set(View.DEBUG_LAYOUT_PROPERTY, + mDebugLayout.isChecked() ? "true" : "false"); + pokeSystemProperties(); } private void updateCpuUsageOptions() { - mShowCpuUsage.setChecked(Settings.System.getInt(getActivity().getContentResolver(), + updateCheckBox(mShowCpuUsage, Settings.System.getInt(getActivity().getContentResolver(), Settings.System.SHOW_PROCESSES, 0) != 0); } @@ -334,13 +606,16 @@ public class DevelopmentSettings extends PreferenceFragment } private void updateImmediatelyDestroyActivitiesOptions() { - mImmediatelyDestroyActivities.setChecked(Settings.System.getInt( + updateCheckBox(mImmediatelyDestroyActivities, Settings.System.getInt( getActivity().getContentResolver(), Settings.System.ALWAYS_FINISH_ACTIVITIES, 0) != 0); } private void updateAnimationScaleValue(int which, ListPreference pref) { try { float scale = mWindowManager.getAnimationScale(which); + if (scale != 1) { + mHaveDebugSettings = true; + } CharSequence[] values = pref.getEntryValues(); for (int i=0; i<values.length; i++) { float val = Float.parseFloat(values[i].toString()); @@ -359,11 +634,12 @@ public class DevelopmentSettings extends PreferenceFragment private void updateAnimationScaleOptions() { updateAnimationScaleValue(0, mWindowAnimationScale); updateAnimationScaleValue(1, mTransitionAnimationScale); + updateAnimationScaleValue(2, mAnimatorDurationScale); } private void writeAnimationScaleOption(int which, ListPreference pref, Object newValue) { try { - float scale = Float.parseFloat(newValue.toString()); + float scale = newValue != null ? Float.parseFloat(newValue.toString()) : 1; mWindowManager.setAnimationScale(which, scale); updateAnimationScaleValue(which, pref); } catch (RemoteException e) { @@ -377,6 +653,9 @@ public class DevelopmentSettings extends PreferenceFragment for (int i=0; i<values.length; i++) { int val = Integer.parseInt(values[i].toString()); if (val >= limit) { + if (i != 0) { + mHaveDebugSettings = true; + } mAppProcessLimit.setValueIndex(i); mAppProcessLimit.setSummary(mAppProcessLimit.getEntries()[i]); return; @@ -390,7 +669,7 @@ public class DevelopmentSettings extends PreferenceFragment private void writeAppProcessLimitOptions(Object newValue) { try { - int limit = Integer.parseInt(newValue.toString()); + int limit = newValue != null ? Integer.parseInt(newValue.toString()) : -1; ActivityManagerNative.getDefault().setProcessLimit(limit); updateAppProcessLimitOptions(); } catch (RemoteException e) { @@ -404,10 +683,92 @@ public class DevelopmentSettings extends PreferenceFragment } private void updateShowAllANRsOptions() { - mShowAllANRs.setChecked(Settings.Secure.getInt( + updateCheckBox(mShowAllANRs, Settings.Secure.getInt( getActivity().getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0); } + private void updateEnableTracesOptions() { + String strValue = SystemProperties.get(Trace.PROPERTY_TRACE_TAG_ENABLEFLAGS); + long flags = SystemProperties.getLong(Trace.PROPERTY_TRACE_TAG_ENABLEFLAGS, 0); + String[] values = mEnableTracesPref.getEntryValues(); + int numSet = 0; + for (int i=Trace.TRACE_FLAGS_START_BIT; i<values.length; i++) { + boolean set = (flags&(1<<i)) != 0; + mEnableTracesPref.setValue(i-Trace.TRACE_FLAGS_START_BIT, set); + if (set) { + numSet++; + } + } + if (numSet == 0) { + mEnableTracesPref.setSummary(R.string.enable_traces_summary_none); + } else if (numSet == values.length) { + mHaveDebugSettings = true; + mEnableTracesPref.setSummary(R.string.enable_traces_summary_all); + } else { + mHaveDebugSettings = true; + mEnableTracesPref.setSummary(getString(R.string.enable_traces_summary_num, numSet)); + } + } + + private void writeEnableTracesOptions() { + long value = 0; + String[] values = mEnableTracesPref.getEntryValues(); + for (int i=Trace.TRACE_FLAGS_START_BIT; i<values.length; i++) { + if (mEnableTracesPref.getValue(i-Trace.TRACE_FLAGS_START_BIT)) { + value |= 1<<i; + } + } + writeEnableTracesOptions(value); + // Make sure summary is updated. + updateEnableTracesOptions(); + } + + private void writeEnableTracesOptions(long value) { + SystemProperties.set(Trace.PROPERTY_TRACE_TAG_ENABLEFLAGS, + "0x" + Long.toString(value, 16)); + pokeSystemProperties(); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView == mEnabledSwitch) { + if (isChecked != mLastEnabledState) { + if (isChecked) { + mDialogClicked = false; + if (mEnableDialog != null) dismissDialogs(); + mEnableDialog = new AlertDialog.Builder(getActivity()).setMessage( + getActivity().getResources().getString( + R.string.dev_settings_warning_message)) + .setTitle(R.string.dev_settings_warning_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(android.R.string.yes, this) + .setNegativeButton(android.R.string.no, this) + .show(); + mEnableDialog.setOnDismissListener(this); + } else { + resetDangerousOptions(); + Settings.Secure.putInt(getActivity().getContentResolver(), + Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED, 0); + mLastEnabledState = isChecked; + setPrefsEnabledState(mLastEnabledState); + } + } + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == RESULT_DEBUG_APP) { + if (resultCode == Activity.RESULT_OK) { + mDebugApp = data.getAction(); + writeDebuggerOptions(); + updateDebuggerOptions(); + } + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { @@ -417,16 +778,16 @@ public class DevelopmentSettings extends PreferenceFragment if (preference == mEnableAdb) { if (mEnableAdb.isChecked()) { - mOkClicked = false; - if (mOkDialog != null) dismissDialog(); - mOkDialog = new AlertDialog.Builder(getActivity()).setMessage( + mDialogClicked = false; + if (mAdbDialog != null) dismissDialogs(); + mAdbDialog = new AlertDialog.Builder(getActivity()).setMessage( getActivity().getResources().getString(R.string.adb_warning_message)) .setTitle(R.string.adb_warning_title) .setIcon(android.R.drawable.ic_dialog_alert) .setPositiveButton(android.R.string.yes, this) .setNegativeButton(android.R.string.no, this) .show(); - mOkDialog.setOnDismissListener(this); + mAdbDialog.setOnDismissListener(this); } else { Settings.Secure.putInt(getActivity().getContentResolver(), Settings.Secure.ADB_ENABLED, 0); @@ -436,10 +797,20 @@ public class DevelopmentSettings extends PreferenceFragment Settings.System.STAY_ON_WHILE_PLUGGED_IN, mKeepScreenOn.isChecked() ? (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB) : 0); + } else if (preference == mEnforceReadExternal) { + if (mEnforceReadExternal.isChecked()) { + ConfirmEnforceFragment.show(this); + } else { + setPermissionEnforced(getActivity(), READ_EXTERNAL_STORAGE, false); + } } else if (preference == mAllowMockLocation) { Settings.Secure.putInt(getActivity().getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, mAllowMockLocation.isChecked() ? 1 : 0); + } else if (preference == mDebugAppPref) { + startActivityForResult(new Intent(getActivity(), AppPicker.class), RESULT_DEBUG_APP); + } else if (preference == mWaitForDebugger) { + writeDebuggerOptions(); } else if (preference == mStrictMode) { writeStrictModeVisualOptions(); } else if (preference == mPointerLocation) { @@ -447,7 +818,9 @@ public class DevelopmentSettings extends PreferenceFragment } else if (preference == mShowTouches) { writeShowTouchesOptions(); } else if (preference == mShowScreenUpdates) { - writeFlingerOptions(); + writeShowUpdatesOption(); + } else if (preference == mDisableOverlays) { + writeDisableOverlaysOption(); } else if (preference == mShowCpuUsage) { writeCpuUsageOptions(); } else if (preference == mImmediatelyDestroyActivities) { @@ -456,6 +829,12 @@ public class DevelopmentSettings extends PreferenceFragment writeShowAllANRsOptions(); } else if (preference == mForceHardwareUi) { writeHardwareUiOptions(); + } else if (preference == mTrackFrameTime) { + writeTrackFrameTimeOptions(); + } else if (preference == mShowHwScreenUpdates) { + writeShowHwScreenUpdatesOptions(); + } else if (preference == mDebugLayout) { + writeDebugLayoutOptions(); } return false; @@ -466,6 +845,7 @@ public class DevelopmentSettings extends PreferenceFragment if (HDCP_CHECKING_KEY.equals(preference.getKey())) { SystemProperties.set(HDCP_CHECKING_PROPERTY, newValue.toString()); updateHdcpValues(); + pokeSystemProperties(); return true; } else if (preference == mWindowAnimationScale) { writeAnimationScaleOption(0, mWindowAnimationScale, newValue); @@ -473,6 +853,12 @@ public class DevelopmentSettings extends PreferenceFragment } else if (preference == mTransitionAnimationScale) { writeAnimationScaleOption(1, mTransitionAnimationScale, newValue); return true; + } else if (preference == mAnimatorDurationScale) { + writeAnimationScaleOption(2, mAnimatorDurationScale, newValue); + return true; + } else if (preference == mEnableTracesPref) { + writeEnableTracesOptions(); + return true; } else if (preference == mAppProcessLimit) { writeAppProcessLimitOptions(newValue); return true; @@ -480,33 +866,144 @@ public class DevelopmentSettings extends PreferenceFragment return false; } - private void dismissDialog() { - if (mOkDialog == null) return; - mOkDialog.dismiss(); - mOkDialog = null; + private void dismissDialogs() { + if (mAdbDialog != null) { + mAdbDialog.dismiss(); + mAdbDialog = null; + } + if (mEnableDialog != null) { + mEnableDialog.dismiss(); + mEnableDialog = null; + } } public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_POSITIVE) { - mOkClicked = true; - Settings.Secure.putInt(getActivity().getContentResolver(), - Settings.Secure.ADB_ENABLED, 1); - } else { - // Reset the toggle - mEnableAdb.setChecked(false); + if (dialog == mAdbDialog) { + if (which == DialogInterface.BUTTON_POSITIVE) { + mDialogClicked = true; + Settings.Secure.putInt(getActivity().getContentResolver(), + Settings.Secure.ADB_ENABLED, 1); + } else { + // Reset the toggle + mEnableAdb.setChecked(false); + } + } else if (dialog == mEnableDialog) { + if (which == DialogInterface.BUTTON_POSITIVE) { + mDialogClicked = true; + Settings.Secure.putInt(getActivity().getContentResolver(), + Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED, 1); + mLastEnabledState = true; + setPrefsEnabledState(mLastEnabledState); + } else { + // Reset the toggle + mEnabledSwitch.setChecked(false); + } } } public void onDismiss(DialogInterface dialog) { // Assuming that onClick gets called first - if (!mOkClicked) { - mEnableAdb.setChecked(false); + if (dialog == mAdbDialog) { + if (!mDialogClicked) { + mEnableAdb.setChecked(false); + } + mAdbDialog = null; + } else if (dialog == mEnableDialog) { + if (!mDialogClicked) { + mEnabledSwitch.setChecked(false); + } + mEnableDialog = null; } } @Override public void onDestroy() { - dismissDialog(); + dismissDialogs(); super.onDestroy(); } + + void pokeSystemProperties() { + if (!mDontPokeProperties) { + (new SystemPropPoker()).execute(); + } + } + + static class SystemPropPoker extends AsyncTask<Void, Void, Void> { + @Override + protected Void doInBackground(Void... params) { + String[] services; + try { + services = ServiceManager.listServices(); + } catch (RemoteException e) { + return null; + } + for (String service : services) { + IBinder obj = ServiceManager.checkService(service); + if (obj != null) { + Parcel data = Parcel.obtain(); + try { + obj.transact(IBinder.SYSPROPS_TRANSACTION, data, null, 0); + } catch (RemoteException e) { + } + data.recycle(); + } + } + return null; + } + } + + /** + * Dialog to confirm enforcement of {@link #READ_EXTERNAL_STORAGE}. + */ + public static class ConfirmEnforceFragment extends DialogFragment { + public static void show(DevelopmentSettings parent) { + final ConfirmEnforceFragment dialog = new ConfirmEnforceFragment(); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_CONFIRM_ENFORCE); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.enforce_read_external_confirm_title); + builder.setMessage(R.string.enforce_read_external_confirm_message); + + builder.setPositiveButton(android.R.string.ok, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + setPermissionEnforced(context, READ_EXTERNAL_STORAGE, true); + ((DevelopmentSettings) getTargetFragment()).updateAllOptions(); + } + }); + builder.setNegativeButton(android.R.string.cancel, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ((DevelopmentSettings) getTargetFragment()).updateAllOptions(); + } + }); + + return builder.create(); + } + } + + private static boolean isPermissionEnforced(Context context, String permission) { + try { + return ActivityThread.getPackageManager().isPermissionEnforced(READ_EXTERNAL_STORAGE); + } catch (RemoteException e) { + throw new RuntimeException("Problem talking with PackageManager", e); + } + } + + private static void setPermissionEnforced( + Context context, String permission, boolean enforced) { + try { + // TODO: offload to background thread + ActivityThread.getPackageManager() + .setPermissionEnforced(READ_EXTERNAL_STORAGE, enforced); + } catch (RemoteException e) { + throw new RuntimeException("Problem talking with PackageManager", e); + } + } } diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java index 5887140..fc162e2 100644 --- a/src/com/android/settings/DisplaySettings.java +++ b/src/com/android/settings/DisplaySettings.java @@ -24,11 +24,8 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; -import android.database.ContentObserver; import android.os.Bundle; -import android.os.Handler; import android.os.RemoteException; -import android.os.ServiceManager; import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; @@ -36,8 +33,9 @@ import android.preference.PreferenceScreen; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Log; -import android.view.IWindowManager; -import android.view.Surface; + +import com.android.internal.view.RotationPolicy; +import com.android.settings.DreamSettings; import java.util.ArrayList; @@ -52,6 +50,7 @@ public class DisplaySettings extends SettingsPreferenceFragment implements private static final String KEY_ACCELEROMETER = "accelerometer"; private static final String KEY_FONT_SIZE = "font_size"; private static final String KEY_NOTIFICATION_PULSE = "notification_pulse"; + private static final String KEY_SCREEN_SAVER = "screensaver"; private CheckBoxPreference mAccelerometer; private ListPreference mFontSizePref; @@ -60,10 +59,12 @@ public class DisplaySettings extends SettingsPreferenceFragment implements private final Configuration mCurConfig = new Configuration(); private ListPreference mScreenTimeoutPreference; + private Preference mScreenSaverPreference; - private ContentObserver mAccelerometerRotationObserver = new ContentObserver(new Handler()) { + private final RotationPolicy.RotationPolicyListener mRotationPolicyListener = + new RotationPolicy.RotationPolicyListener() { @Override - public void onChange(boolean selfChange) { + public void onChange() { updateAccelerometerRotationCheckbox(); } }; @@ -77,7 +78,19 @@ public class DisplaySettings extends SettingsPreferenceFragment implements mAccelerometer = (CheckBoxPreference) findPreference(KEY_ACCELEROMETER); mAccelerometer.setPersistent(false); + if (RotationPolicy.isRotationLockToggleSupported(getActivity())) { + // If rotation lock is supported, then we do not provide this option in + // Display settings. However, is still available in Accessibility settings. + getPreferenceScreen().removePreference(mAccelerometer); + } + mScreenSaverPreference = findPreference(KEY_SCREEN_SAVER); + if (mScreenSaverPreference != null + && getResources().getBoolean( + com.android.internal.R.bool.config_enableDreams) == false) { + getPreferenceScreen().removePreference(mScreenSaverPreference); + } + mScreenTimeoutPreference = (ListPreference) findPreference(KEY_SCREEN_TIMEOUT); final long currentTimeout = Settings.System.getLong(resolver, SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE); @@ -102,6 +115,7 @@ public class DisplaySettings extends SettingsPreferenceFragment implements Log.e(TAG, Settings.System.NOTIFICATION_LIGHT_PULSE + " not found"); } } + } private void updateTimeoutPreferenceDescription(long currentTimeout) { @@ -198,27 +212,36 @@ public class DisplaySettings extends SettingsPreferenceFragment implements super.onResume(); updateState(); - getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), true, - mAccelerometerRotationObserver); + + RotationPolicy.registerRotationPolicyListener(getActivity(), + mRotationPolicyListener); } @Override public void onPause() { super.onPause(); - getContentResolver().unregisterContentObserver(mAccelerometerRotationObserver); + RotationPolicy.unregisterRotationPolicyListener(getActivity(), + mRotationPolicyListener); } private void updateState() { updateAccelerometerRotationCheckbox(); readFontSizePreference(mFontSizePref); + updateScreenSaverSummary(); + } + + private void updateScreenSaverSummary() { + mScreenSaverPreference.setSummary( + DreamSettings.isScreenSaverEnabled(mScreenSaverPreference.getContext()) + ? R.string.screensaver_settings_summary_on + : R.string.screensaver_settings_summary_off); } private void updateAccelerometerRotationCheckbox() { - mAccelerometer.setChecked(Settings.System.getInt( - getContentResolver(), - Settings.System.ACCELEROMETER_ROTATION, 0) != 0); + if (getActivity() == null) return; + + mAccelerometer.setChecked(!RotationPolicy.isRotationLocked(getActivity())); } public void writeFontSizePreference(Object objValue) { @@ -233,17 +256,8 @@ public class DisplaySettings extends SettingsPreferenceFragment implements @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (preference == mAccelerometer) { - try { - IWindowManager wm = IWindowManager.Stub.asInterface( - ServiceManager.getService(Context.WINDOW_SERVICE)); - if (mAccelerometer.isChecked()) { - wm.thawRotation(); - } else { - wm.freezeRotation(Surface.ROTATION_0); - } - } catch (RemoteException exc) { - Log.w(TAG, "Unable to save auto-rotate setting"); - } + RotationPolicy.setRotationLockForAccessibility( + getActivity(), !mAccelerometer.isChecked()); } else if (preference == mNotificationPulse) { boolean value = mNotificationPulse.isChecked(); Settings.System.putInt(getContentResolver(), Settings.System.NOTIFICATION_LIGHT_PULSE, diff --git a/src/com/android/settings/DreamComponentPreference.java b/src/com/android/settings/DreamComponentPreference.java new file mode 100644 index 0000000..2114dd1 --- /dev/null +++ b/src/com/android/settings/DreamComponentPreference.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2011 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; + +import static android.provider.Settings.Secure.SCREENSAVER_COMPONENT; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.preference.Preference; +import android.provider.Settings; +import android.service.dreams.IDreamManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class DreamComponentPreference extends Preference { + private static final String TAG = "DreamComponentPreference"; + + private static final boolean SHOW_DOCK_APPS = false; + private static final boolean SHOW_DREAM_SERVICES = true; + private static final boolean SHOW_DREAM_ACTIVITIES = false; + + private final PackageManager pm; + private final ContentResolver resolver; + private final Collator sCollator = Collator.getInstance(); + + public DreamComponentPreference(Context context, AttributeSet attrs) { + super(context, attrs); + pm = getContext().getPackageManager(); + resolver = getContext().getContentResolver(); + + refreshFromSettings(); + } + + private void refreshFromSettings() { + ComponentName cn = null; + IDreamManager dm = IDreamManager.Stub.asInterface( + ServiceManager.getService("dreams")); + try { + cn = dm.getDreamComponent(); + } catch (RemoteException ex) { + setSummary("(unknown)"); + return; + } + + try { + setSummary(pm.getActivityInfo(cn, 0).loadLabel(pm)); + } catch (PackageManager.NameNotFoundException ex) { + try { + setSummary(pm.getServiceInfo(cn, 0).loadLabel(pm)); + } catch (PackageManager.NameNotFoundException ex2) { + setSummary("(unknown)"); + } + } + } + + // Group by package, then by name. + Comparator<ResolveInfo> sResolveInfoComparator = new Comparator<ResolveInfo>() { + @Override + public int compare(ResolveInfo a, ResolveInfo b) { + CharSequence sa, sb; + + ApplicationInfo aia = a.activityInfo != null ? a.activityInfo.applicationInfo : a.serviceInfo.applicationInfo; + ApplicationInfo aib = b.activityInfo != null ? b.activityInfo.applicationInfo : b.serviceInfo.applicationInfo; + + if (!aia.equals(aib)) { + sa = pm.getApplicationLabel(aia); + sb = pm.getApplicationLabel(aib); + } else { + sa = a.loadLabel(pm); + if (sa == null) { + sa = (a.activityInfo != null) ? a.activityInfo.name : a.serviceInfo.name; + } + sb = b.loadLabel(pm); + if (sb == null) { + sb = (b.activityInfo != null) ? b.activityInfo.name : b.serviceInfo.name; + } + } + return sCollator.compare(sa.toString(), sb.toString()); + } + }; + + public class DreamListAdapter extends BaseAdapter implements ListAdapter { + private ArrayList<ResolveInfo> results; + private final LayoutInflater inflater; + + public DreamListAdapter(Context context) { + Intent choosy = new Intent(Intent.ACTION_MAIN) + .addCategory("android.intent.category.DREAM"); + + inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + results = new ArrayList<ResolveInfo>(); + + if (SHOW_DREAM_ACTIVITIES) { + results.addAll(pm.queryIntentActivities(choosy, PackageManager.GET_META_DATA)); + } + + if (SHOW_DREAM_SERVICES) { + results.addAll(pm.queryIntentServices(choosy, PackageManager.GET_META_DATA)); + } + + // Group by package + Collections.sort(results, sResolveInfoComparator); + + if (SHOW_DOCK_APPS) { + choosy = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_DESK_DOCK); + + List<ResolveInfo> dockApps = pm.queryIntentActivities(choosy, 0); + for (ResolveInfo app : dockApps) { + // do not insert duplicate packages + int pos = Collections.binarySearch(results, app, sResolveInfoComparator); + if (pos < 0) { + results.add(-1-pos, app); + } + } + } + } + + @Override + public int getCount() { + return results.size(); + } + + @Override + public Object getItem(int position) { + return results.get(position); + } + + @Override + public long getItemId (int position) { + return (long) position; + } + + private CharSequence loadDescription(ResolveInfo ri) { + CharSequence desc = null; + if (ri != null) { + Bundle metaData = (ri.activityInfo != null) ? ri.activityInfo.metaData : ri.serviceInfo.metaData; + Log.d(TAG, "loadDescription: ri=" + ri + " metaData=" + metaData); + if (metaData != null) { + desc = metaData.getCharSequence("android.screensaver.description"); + Log.d(TAG, "loadDescription: desc=" + desc); + if (desc != null) { + desc = desc.toString().trim(); + } + } + } + return desc; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = (convertView != null) + ? convertView + : inflater.inflate(R.layout.dream_picker_row, parent, false); + ResolveInfo ri = results.get(position); + ((TextView)row.findViewById(R.id.title)).setText(ri.loadLabel(pm)); + ((ImageView)row.findViewById(R.id.icon)).setImageDrawable(ri.loadIcon(pm)); + return row; + } + } + + @Override + protected void onClick() { + final DreamListAdapter list = new DreamListAdapter(getContext()); + AlertDialog alert = new AlertDialog.Builder(getContext()) + .setAdapter( + list, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ResolveInfo ri = (ResolveInfo)list.getItem(which); + String pn = (ri.activityInfo != null) ? ri.activityInfo.applicationInfo.packageName + : ri.serviceInfo.applicationInfo.packageName; + String n = (ri.activityInfo != null) ? ri.activityInfo.name : ri.serviceInfo.name; + ComponentName cn = new ComponentName(pn, n); + + setSummary(ri.loadLabel(pm)); + //getContext().startActivity(intent); + + IDreamManager dm = IDreamManager.Stub.asInterface( + ServiceManager.getService("dreams")); + try { + dm.setDreamComponent(cn); + } catch (RemoteException ex) { + // too bad, so sad, oh mom, oh dad + } + } + }) + .create(); + alert.show(); + } +} diff --git a/src/com/android/settings/DreamSettings.java b/src/com/android/settings/DreamSettings.java new file mode 100644 index 0000000..d9953aa --- /dev/null +++ b/src/com/android/settings/DreamSettings.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2010 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; + +import static android.provider.Settings.Secure.SCREENSAVER_ENABLED; +import static android.provider.Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK; + +import android.app.ActionBar; +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.app.admin.DevicePolicyManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Configuration; +import android.database.ContentObserver; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.provider.Settings; +import android.util.Log; +import android.view.Gravity; +import android.view.IWindowManager; +import android.widget.CompoundButton; +import android.widget.Switch; + +import java.util.ArrayList; + +public class DreamSettings extends SettingsPreferenceFragment { + private static final String TAG = "DreamSettings"; + + private static final String KEY_ACTIVATE_ON_DOCK = "activate_on_dock"; + + private CheckBoxPreference mActivateOnDockPreference; + + private Switch mEnableSwitch; + private Enabler mEnabler; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + addPreferencesFromResource(R.xml.dream_settings); + + mActivateOnDockPreference = (CheckBoxPreference) findPreference(KEY_ACTIVATE_ON_DOCK); + + final Activity activity = getActivity(); + + mEnableSwitch = new Switch(activity); + + if (activity instanceof PreferenceActivity) { + PreferenceActivity preferenceActivity = (PreferenceActivity) activity; + // note: we do not check onIsHidingHeaders() or onIsMultiPane() because there's no + // switch in the left-hand pane to control this; we need to show the ON/OFF in our + // fragment every time + final int padding = activity.getResources().getDimensionPixelSize( + R.dimen.action_bar_switch_padding); + mEnableSwitch.setPadding(0, 0, padding, 0); + activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, + ActionBar.DISPLAY_SHOW_CUSTOM); + activity.getActionBar().setCustomView(mEnableSwitch, new ActionBar.LayoutParams( + ActionBar.LayoutParams.WRAP_CONTENT, + ActionBar.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_VERTICAL | Gravity.RIGHT)); + activity.getActionBar().setTitle(R.string.screensaver_settings_title); + } + + mEnabler = new Enabler(activity, mEnableSwitch); + } + + public static boolean isScreenSaverEnabled(Context context) { + return 0 != Settings.Secure.getInt( + context.getContentResolver(), SCREENSAVER_ENABLED, 1); + } + + public static void setScreenSaverEnabled(Context context, boolean enabled) { + Settings.Secure.putInt( + context.getContentResolver(), SCREENSAVER_ENABLED, enabled ? 1 : 0); + } + + public static class Enabler implements CompoundButton.OnCheckedChangeListener { + private final Context mContext; + private Switch mSwitch; + + public Enabler(Context context, Switch switch_) { + mContext = context; + setSwitch(switch_); + } + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + setScreenSaverEnabled(mContext, isChecked); + } + public void setSwitch(Switch switch_) { + if (mSwitch == switch_) return; + if (mSwitch != null) mSwitch.setOnCheckedChangeListener(null); + mSwitch = switch_; + mSwitch.setOnCheckedChangeListener(this); + + final boolean enabled = isScreenSaverEnabled(mContext); + mSwitch.setChecked(enabled); + } + public void pause() { + mSwitch.setOnCheckedChangeListener(null); + } + public void resume() { + mSwitch.setOnCheckedChangeListener(this); + } + } + + @Override + public void onResume() { + if (mEnabler != null) { + mEnabler.resume(); + } + + final boolean currentActivateOnDock = 0 != Settings.Secure.getInt(getContentResolver(), + SCREENSAVER_ACTIVATE_ON_DOCK, 1); + mActivateOnDockPreference.setChecked(currentActivateOnDock); + super.onResume(); + } + + @Override + public void onPause() { + if (mEnabler != null) { + mEnabler.pause(); + } + + super.onPause(); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference == mActivateOnDockPreference) { + Settings.Secure.putInt(getContentResolver(), + SCREENSAVER_ACTIVATE_ON_DOCK, + mActivateOnDockPreference.isChecked() ? 1 : 0); + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + } +} diff --git a/src/com/android/settings/DreamTesterPreference.java b/src/com/android/settings/DreamTesterPreference.java new file mode 100644 index 0000000..87a142c --- /dev/null +++ b/src/com/android/settings/DreamTesterPreference.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2011 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; + +import static android.provider.Settings.Secure.SCREENSAVER_COMPONENT; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.preference.Preference; +import android.provider.Settings; +import android.service.dreams.IDreamManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +public class DreamTesterPreference extends Preference { + private static final String TAG = "DreamTesterPreference"; + + private final PackageManager pm; + private final ContentResolver resolver; + + public DreamTesterPreference(Context context, AttributeSet attrs) { + super(context, attrs); + pm = getContext().getPackageManager(); + resolver = getContext().getContentResolver(); + } + + @Override + protected void onClick() { + String component = Settings.Secure.getString(resolver, SCREENSAVER_COMPONENT); + Log.v(TAG, "component=" + component); + if (component != null) { + ComponentName cn = ComponentName.unflattenFromString(component); + Log.v(TAG, "cn=" + cn); +// Intent intent = new Intent(Intent.ACTION_MAIN) +// .setComponent(cn) +// .addFlags( +// Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS +// ) +// .putExtra("android.dreams.TEST", true); +// getContext().startService(intent); + IDreamManager dm = IDreamManager.Stub.asInterface( + ServiceManager.getService("dreams")); + try { + dm.testDream(cn); + } catch (RemoteException ex) { + // too bad, so sad, oh mom, oh dad + } + } + } +} diff --git a/src/com/android/settings/EditPinPreference.java b/src/com/android/settings/EditPinPreference.java index 362bed1..1877d43 100644 --- a/src/com/android/settings/EditPinPreference.java +++ b/src/com/android/settings/EditPinPreference.java @@ -19,8 +19,7 @@ package com.android.settings; import android.app.Dialog; import android.content.Context; import android.preference.EditTextPreference; -import android.text.method.DigitsKeyListener; -import android.text.method.PasswordTransformationMethod; +import android.text.InputType; import android.util.AttributeSet; import android.view.View; import android.widget.EditText; @@ -52,12 +51,11 @@ class EditPinPreference extends EditTextPreference { protected void onBindDialogView(View view) { super.onBindDialogView(view); - final EditText editText = (EditText) view.findViewById(android.R.id.edit); + final EditText editText = getEditText(); if (editText != null) { - editText.setSingleLine(true); - editText.setTransformationMethod(PasswordTransformationMethod.getInstance()); - editText.setKeyListener(DigitsKeyListener.getInstance()); + editText.setInputType(InputType.TYPE_CLASS_NUMBER | + InputType.TYPE_NUMBER_VARIATION_PASSWORD); } } diff --git a/src/com/android/settings/NsdEnabler.java b/src/com/android/settings/NsdEnabler.java new file mode 100644 index 0000000..acdf92e --- /dev/null +++ b/src/com/android/settings/NsdEnabler.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012 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; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.nsd.NsdManager; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceScreen; + +import com.android.settings.R; + +/** + * NsdEnabler is a helper to manage network service discovery on/off checkbox state. + */ +public class NsdEnabler implements Preference.OnPreferenceChangeListener { + private final Context mContext; + private final CheckBoxPreference mCheckbox; + private final IntentFilter mIntentFilter; + private NsdManager mNsdManager; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (NsdManager.ACTION_NSD_STATE_CHANGED.equals(action)) { + handleNsdStateChanged(intent.getIntExtra(NsdManager.EXTRA_NSD_STATE, + NsdManager.NSD_STATE_DISABLED)); + } + } + }; + + public NsdEnabler(Context context, CheckBoxPreference checkBoxPreference) { + mContext = context; + mCheckbox = checkBoxPreference; + mNsdManager = (NsdManager) mContext.getSystemService(Context.NSD_SERVICE); + mIntentFilter = new IntentFilter(NsdManager.ACTION_NSD_STATE_CHANGED); + } + + public void resume() { + mContext.registerReceiver(mReceiver, mIntentFilter); + mCheckbox.setOnPreferenceChangeListener(this); + } + + public void pause() { + mContext.unregisterReceiver(mReceiver); + mCheckbox.setOnPreferenceChangeListener(null); + } + + public boolean onPreferenceChange(Preference preference, Object value) { + + final boolean desiredState = (Boolean) value; + mCheckbox.setEnabled(false); + mNsdManager.setEnabled(desiredState); + return false; + } + + private void handleNsdStateChanged(int newState) { + switch (newState) { + case NsdManager.NSD_STATE_DISABLED: + mCheckbox.setChecked(false); + mCheckbox.setEnabled(true); + break; + case NsdManager.NSD_STATE_ENABLED: + mCheckbox.setChecked(true); + mCheckbox.setEnabled(true); + break; + } + } +} diff --git a/src/com/android/settings/PointerSpeedPreference.java b/src/com/android/settings/PointerSpeedPreference.java index 6a6a71f..3f40f9a 100644 --- a/src/com/android/settings/PointerSpeedPreference.java +++ b/src/com/android/settings/PointerSpeedPreference.java @@ -19,22 +19,20 @@ package com.android.settings; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.hardware.input.InputManager; import android.os.Bundle; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; -import android.os.RemoteException; -import android.os.ServiceManager; import android.preference.SeekBarDialogPreference; import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; import android.util.AttributeSet; -import android.view.IWindowManager; import android.view.View; import android.widget.SeekBar; public class PointerSpeedPreference extends SeekBarDialogPreference implements SeekBar.OnSeekBarChangeListener { + private final InputManager mIm; private SeekBar mSeekBar; private int mOldSpeed; @@ -42,9 +40,6 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements private boolean mTouchInProgress; - private static final int MIN_SPEED = -7; - private static final int MAX_SPEED = 7; - private ContentObserver mSpeedObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { @@ -54,6 +49,7 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements public PointerSpeedPreference(Context context, AttributeSet attrs) { super(context, attrs); + mIm = (InputManager)getContext().getSystemService(Context.INPUT_SERVICE); } @Override @@ -72,15 +68,15 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements super.onBindDialogView(view); mSeekBar = getSeekBar(view); - mSeekBar.setMax(MAX_SPEED - MIN_SPEED); - mOldSpeed = getSpeed(0); - mSeekBar.setProgress(mOldSpeed - MIN_SPEED); + mSeekBar.setMax(InputManager.MAX_POINTER_SPEED - InputManager.MIN_POINTER_SPEED); + mOldSpeed = mIm.getPointerSpeed(getContext()); + mSeekBar.setProgress(mOldSpeed - InputManager.MIN_POINTER_SPEED); mSeekBar.setOnSeekBarChangeListener(this); } public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) { if (!mTouchInProgress) { - setSpeed(progress + MIN_SPEED); + mIm.tryPointerSpeed(progress + InputManager.MIN_POINTER_SPEED); } } @@ -90,22 +86,12 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements public void onStopTrackingTouch(SeekBar seekBar) { mTouchInProgress = false; - setSpeed(seekBar.getProgress() + MIN_SPEED); - } - - private int getSpeed(int defaultValue) { - int speed = defaultValue; - try { - speed = Settings.System.getInt(getContext().getContentResolver(), - Settings.System.POINTER_SPEED); - } catch (SettingNotFoundException snfe) { - } - return speed; + mIm.tryPointerSpeed(seekBar.getProgress() + InputManager.MIN_POINTER_SPEED); } private void onSpeedChanged() { - int speed = getSpeed(0); - mSeekBar.setProgress(speed - MIN_SPEED); + int speed = mIm.getPointerSpeed(getContext()); + mSeekBar.setProgress(speed - InputManager.MIN_POINTER_SPEED); } @Override @@ -115,8 +101,8 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements final ContentResolver resolver = getContext().getContentResolver(); if (positiveResult) { - Settings.System.putInt(resolver, Settings.System.POINTER_SPEED, - mSeekBar.getProgress() + MIN_SPEED); + mIm.setPointerSpeed(getContext(), + mSeekBar.getProgress() + InputManager.MIN_POINTER_SPEED); } else { restoreOldState(); } @@ -127,21 +113,10 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements private void restoreOldState() { if (mRestoredOldState) return; - setSpeed(mOldSpeed); + mIm.tryPointerSpeed(mOldSpeed); mRestoredOldState = true; } - private void setSpeed(int speed) { - try { - IWindowManager wm = IWindowManager.Stub.asInterface( - ServiceManager.getService("window")); - if (wm != null) { - wm.setPointerSpeed(speed); - } - } catch (RemoteException e) { - } - } - @Override protected Parcelable onSaveInstanceState() { final Parcelable superState = super.onSaveInstanceState(); @@ -169,7 +144,7 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements super.onRestoreInstanceState(myState.getSuperState()); mOldSpeed = myState.oldSpeed; mSeekBar.setProgress(myState.progress); - setSpeed(myState.progress + MIN_SPEED); + mIm.tryPointerSpeed(myState.progress + InputManager.MIN_POINTER_SPEED); } private static class SavedState extends BaseSavedState { diff --git a/src/com/android/settings/PrivacySettings.java b/src/com/android/settings/PrivacySettings.java index 3f3b9ad..e2433bd 100644 --- a/src/com/android/settings/PrivacySettings.java +++ b/src/com/android/settings/PrivacySettings.java @@ -203,4 +203,9 @@ public class PrivacySettings extends SettingsPreferenceFragment implements mAutoRestore.setEnabled(enable); mConfigure.setEnabled(enable); } + + @Override + protected int getHelpResource() { + return R.string.help_url_backup_reset; + } } diff --git a/src/com/android/settings/ProgressCategory.java b/src/com/android/settings/ProgressCategory.java index c1b25d8..625aa59 100644 --- a/src/com/android/settings/ProgressCategory.java +++ b/src/com/android/settings/ProgressCategory.java @@ -36,13 +36,10 @@ public class ProgressCategory extends ProgressCategoryBase { @Override public void onBindView(View view) { super.onBindView(view); - final TextView scanning = (TextView) view.findViewById(R.id.scanning_text); final View progressBar = view.findViewById(R.id.scanning_progress); - scanning.setText(mProgress ? R.string.progress_scanning : R.string.progress_tap_to_pair); boolean noDeviceFound = (getPreferenceCount() == 0 || (getPreferenceCount() == 1 && getPreference(0) == mNoDeviceFoundPreference)); - scanning.setVisibility(noDeviceFound ? View.GONE : View.VISIBLE); progressBar.setVisibility(mProgress ? View.VISIBLE : View.GONE); if (mProgress || !noDeviceFound) { diff --git a/src/com/android/settings/RingerVolumePreference.java b/src/com/android/settings/RingerVolumePreference.java index 5845c8f..a79f4a5 100644 --- a/src/com/android/settings/RingerVolumePreference.java +++ b/src/com/android/settings/RingerVolumePreference.java @@ -116,8 +116,8 @@ public class RingerVolumePreference extends VolumePreference { boolean muted = mAudioManager.isStreamMute(streamType); if (mCheckBoxes[i] != null) { - if (streamType == AudioManager.STREAM_RING && muted - && mAudioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) { + if ((streamType == AudioManager.STREAM_RING) && + (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE)) { mCheckBoxes[i].setImageResource( com.android.internal.R.drawable.ic_audio_ring_notif_vibrate); } else { @@ -126,9 +126,13 @@ public class RingerVolumePreference extends VolumePreference { } } if (mSeekBars[i] != null) { - final int volume = muted ? mAudioManager.getLastAudibleStreamVolume(streamType) - : mAudioManager.getStreamVolume(streamType); + final int volume = mAudioManager.getStreamVolume(streamType); mSeekBars[i].setProgress(volume); + if (streamType != mAudioManager.getMasterStreamType() && muted) { + mSeekBars[i].setEnabled(false); + } else { + mSeekBars[i].setEnabled(true); + } } } } @@ -169,9 +173,6 @@ public class RingerVolumePreference extends VolumePreference { } } - final int silentableStreams = System.getInt(getContext().getContentResolver(), - System.MODE_RINGER_STREAMS_AFFECTED, - ((1 << AudioSystem.STREAM_NOTIFICATION) | (1 << AudioSystem.STREAM_RING))); // Register callbacks for mute/unmute buttons for (int i = 0; i < mCheckBoxes.length; i++) { ImageView checkbox = (ImageView) view.findViewById(CHECKBOX_VIEW_ID[i]); diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java index adf8c37..6b67730 100644 --- a/src/com/android/settings/SecuritySettings.java +++ b/src/com/android/settings/SecuritySettings.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; +import android.os.UserId; import android.os.Vibrator; import android.preference.CheckBoxPreference; import android.preference.ListPreference; @@ -53,13 +54,14 @@ public class SecuritySettings extends SettingsPreferenceFragment private static final String KEY_UNLOCK_SET_OR_CHANGE = "unlock_set_or_change"; private static final String KEY_BIOMETRIC_WEAK_IMPROVE_MATCHING = "biometric_weak_improve_matching"; + private static final String KEY_BIOMETRIC_WEAK_LIVELINESS = "biometric_weak_liveliness"; private static final String KEY_LOCK_ENABLED = "lockenabled"; private static final String KEY_VISIBLE_PATTERN = "visiblepattern"; private static final String KEY_TACTILE_FEEDBACK_ENABLED = "unlock_tactile_feedback"; private static final String KEY_SECURITY_CATEGORY = "security_category"; private static final String KEY_LOCK_AFTER_TIMEOUT = "lock_after_timeout"; private static final int SET_OR_CHANGE_LOCK_METHOD_REQUEST = 123; - private static final int CONFIRM_EXISTING_FOR_BIOMETRIC_IMPROVE_REQUEST = 124; + private static final int CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_IMPROVE_REQUEST = 124; // Misc Settings private static final String KEY_SIM_LOCK = "sim_lock"; @@ -74,6 +76,7 @@ public class SecuritySettings extends SettingsPreferenceFragment private LockPatternUtils mLockPatternUtils; private ListPreference mLockAfter; + private PreferenceScreen mBiometricWeakLiveliness; private CheckBoxPreference mVisiblePattern; private CheckBoxPreference mTactileFeedback; @@ -137,15 +140,17 @@ public class SecuritySettings extends SettingsPreferenceFragment DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); - switch (dpm.getStorageEncryptionStatus()) { - case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE: - // The device is currently encrypted. - addPreferencesFromResource(R.xml.security_settings_encrypted); - break; - case DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE: - // This device supports encryption but isn't encrypted. - addPreferencesFromResource(R.xml.security_settings_unencrypted); - break; + if (UserId.myUserId() == 0) { + switch (dpm.getStorageEncryptionStatus()) { + case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE: + // The device is currently encrypted. + addPreferencesFromResource(R.xml.security_settings_encrypted); + break; + case DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE: + // This device supports encryption but isn't encrypted. + addPreferencesFromResource(R.xml.security_settings_unencrypted); + break; + } } // lock after preference @@ -155,6 +160,10 @@ public class SecuritySettings extends SettingsPreferenceFragment updateLockAfterPreferenceSummary(); } + // biometric weak liveliness + mBiometricWeakLiveliness = + (PreferenceScreen) root.findPreference(KEY_BIOMETRIC_WEAK_LIVELINESS); + // visible pattern mVisiblePattern = (CheckBoxPreference) root.findPreference(KEY_VISIBLE_PATTERN); @@ -183,13 +192,17 @@ public class SecuritySettings extends SettingsPreferenceFragment } } + if (UserId.myUserId() > 0) { + return root; + } + // Rest are for primary user... + // Append the rest of the settings addPreferencesFromResource(R.xml.security_settings_misc); - // Do not display SIM lock for CDMA phone + // Do not display SIM lock for devices without an Icc card TelephonyManager tm = TelephonyManager.getDefault(); - if ((TelephonyManager.PHONE_TYPE_CDMA == tm.getCurrentPhoneType()) && - (tm.getLteOnCdmaMode() != Phone.LTE_ON_CDMA_TRUE)) { + if (!tm.hasIccCard()) { root.removePreference(root.findPreference(KEY_SIM_LOCK)); } else { // Disable SIM lock if sim card is missing or unknown @@ -239,7 +252,9 @@ public class SecuritySettings extends SettingsPreferenceFragment public void onClick(DialogInterface dialog, int which) { if (dialog == mWarnInstallApps && which == DialogInterface.BUTTON_POSITIVE) { setNonMarketAppsAllowed(true); - mToggleAppInstallation.setChecked(true); + if (mToggleAppInstallation != null) { + mToggleAppInstallation.setChecked(true); + } } } @@ -322,6 +337,11 @@ public class SecuritySettings extends SettingsPreferenceFragment createPreferenceHierarchy(); final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils(); + if (mBiometricWeakLiveliness != null) { + mBiometricWeakLiveliness.setSummary(lockPatternUtils.isBiometricWeakLivelinessEnabled()? + R.string.biometric_weak_liveliness_on_summary: + R.string.biometric_weak_liveliness_off_summary); + } if (mVisiblePattern != null) { mVisiblePattern.setChecked(lockPatternUtils.isVisiblePatternEnabled()); } @@ -332,11 +352,15 @@ public class SecuritySettings extends SettingsPreferenceFragment mPowerButtonInstantlyLocks.setChecked(lockPatternUtils.getPowerButtonInstantlyLocks()); } - mShowPassword.setChecked(Settings.System.getInt(getContentResolver(), - Settings.System.TEXT_SHOW_PASSWORD, 1) != 0); + if (mShowPassword != null) { + mShowPassword.setChecked(Settings.System.getInt(getContentResolver(), + Settings.System.TEXT_SHOW_PASSWORD, 1) != 0); + } KeyStore.State state = KeyStore.getInstance().state(); - mResetCredentials.setEnabled(state != KeyStore.State.UNINITIALIZED); + if (mResetCredentials != null) { + mResetCredentials.setEnabled(state != KeyStore.State.UNINITIALIZED); + } } @Override @@ -351,8 +375,12 @@ public class SecuritySettings extends SettingsPreferenceFragment ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this.getActivity(), this); if (!helper.launchConfirmationActivity( - CONFIRM_EXISTING_FOR_BIOMETRIC_IMPROVE_REQUEST, null, null)) { - startBiometricWeakImprove(); // no password set, so no need to confirm + CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_IMPROVE_REQUEST, null, null)) { + // If this returns false, it means no password confirmation is required, so + // go ahead and start improve. + // Note: currently a backup is required for biometric_weak so this code path + // can't be reached, but is here in case things change in the future + startBiometricWeakImprove(); } } else if (KEY_LOCK_ENABLED.equals(key)) { lockPatternUtils.setLockPatternEnabled(isToggled(preference)); @@ -390,7 +418,7 @@ public class SecuritySettings extends SettingsPreferenceFragment @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == CONFIRM_EXISTING_FOR_BIOMETRIC_IMPROVE_REQUEST && + if (requestCode == CONFIRM_EXISTING_FOR_BIOMETRIC_WEAK_IMPROVE_REQUEST && resultCode == Activity.RESULT_OK) { startBiometricWeakImprove(); return; diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index c9f5c73..5826154 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -16,20 +16,34 @@ package com.android.settings; +import com.android.internal.util.ArrayUtils; import com.android.settings.accounts.AccountSyncSettings; +import com.android.settings.accounts.AuthenticatorHelper; +import com.android.settings.accounts.ManageAccountsSettings; +import com.android.settings.applications.ManageApplications; import com.android.settings.bluetooth.BluetoothEnabler; +import com.android.settings.deviceinfo.Memory; import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.wifi.WifiEnabler; +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.OnAccountsUpdateListener; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserId; import android.preference.Preference; import android.preference.PreferenceActivity; +import android.preference.PreferenceActivity.Header; import android.preference.PreferenceFragment; import android.text.TextUtils; import android.util.Log; @@ -45,13 +59,16 @@ import android.widget.Switch; import android.widget.TextView; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; /** * Top-level settings activity to handle single pane and double pane UI layout. */ -public class Settings extends PreferenceActivity implements ButtonBarHandler { +public class Settings extends PreferenceActivity + implements ButtonBarHandler, OnAccountsUpdateListener { private static final String LOG_TAG = "Settings"; private static final String META_DATA_KEY_HEADER_ID = @@ -75,24 +92,50 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { private Header mParentHeader; private boolean mInLocalHeaderSwitch; + // Show only these settings for restricted users + private int[] SETTINGS_FOR_RESTRICTED = { + R.id.wifi_settings, + R.id.bluetooth_settings, + R.id.sound_settings, + R.id.display_settings, + R.id.security_settings, + R.id.account_settings, + R.id.about_settings + }; + + private boolean mEnableUserManagement = false; + // TODO: Update Call Settings based on airplane mode state. protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>(); private List<Header> mHeaders; + private AuthenticatorHelper mAuthenticatorHelper; + private Header mLastHeader; + private boolean mListeningToAccountUpdates; + @Override protected void onCreate(Bundle savedInstanceState) { if (getIntent().getBooleanExtra(EXTRA_CLEAR_UI_OPTIONS, false)) { getWindow().setUiOptions(0); } + if (android.provider.Settings.Secure.getInt(getContentResolver(), "multiuser_enabled", -1) + > 0) { + mEnableUserManagement = true; + } + + mAuthenticatorHelper = new AuthenticatorHelper(); + mAuthenticatorHelper.updateAuthDescriptions(this); + mAuthenticatorHelper.onAccountsUpdated(this, null); + getMetaData(); mInLocalHeaderSwitch = true; super.onCreate(savedInstanceState); mInLocalHeaderSwitch = false; if (!onIsHidingHeaders() && onIsMultiPane()) { - highlightHeader(); + highlightHeader(mTopLevelHeaderId); // Force the title so that it doesn't get overridden by a direct launch of // a specific settings screen. setTitle(R.string.settings_label); @@ -118,9 +161,11 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { }); } - // TODO Add support for android.R.id.home in all Setting's onOptionsItemSelected - // getActionBar().setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP, - // ActionBar.DISPLAY_HOME_AS_UP); + // Override up navigation for multi-pane, since we handle it in the fragment breadcrumbs + if (onIsMultiPane()) { + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setHomeButtonEnabled(false); + } } @Override @@ -156,6 +201,14 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { } } + @Override + public void onDestroy() { + super.onDestroy(); + if (mListeningToAccountUpdates) { + AccountManager.get(this).removeOnAccountsUpdatedListener(this); + } + } + private void switchToHeaderLocal(Header header) { mInLocalHeaderSwitch = true; switchToHeader(header); @@ -190,7 +243,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { mCurrentHeader = parentHeader; switchToHeaderLocal(parentHeader); - highlightHeader(); + highlightHeader(mTopLevelHeaderId); mParentHeader = new Header(); mParentHeader.fragment @@ -213,9 +266,9 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { } } - private void highlightHeader() { - if (mTopLevelHeaderId != 0) { - Integer index = mHeaderIndexMap.get(mTopLevelHeaderId); + private void highlightHeader(int id) { + if (id != 0) { + Integer index = mHeaderIndexMap.get(id); if (index != null) { getListView().setItemChecked(index, true); getListView().smoothScrollToPosition(index); @@ -294,7 +347,13 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { if (DataUsageSummary.class.getName().equals(fragmentName) || PowerUsageSummary.class.getName().equals(fragmentName) || AccountSyncSettings.class.getName().equals(fragmentName) || - UserDictionarySettings.class.getName().equals(fragmentName)) { + UserDictionarySettings.class.getName().equals(fragmentName) || + Memory.class.getName().equals(fragmentName) || + ManageApplications.class.getName().equals(fragmentName) || + WirelessSettings.class.getName().equals(fragmentName) || + SoundSettings.class.getName().equals(fragmentName) || + PrivacySettings.class.getName().equals(fragmentName) || + ManageAccountsSettings.class.getName().equals(fragmentName)) { intent.putExtra(EXTRA_CLEAR_UI_OPTIONS, true); } @@ -335,6 +394,31 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) { target.remove(header); } + } else if (id == R.id.data_usage_settings) { + // Remove data usage when kernel module not enabled + final INetworkManagementService netManager = INetworkManagementService.Stub + .asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + try { + if (!netManager.isBandwidthControlEnabled()) { + target.remove(header); + } + } catch (RemoteException e) { + // ignored + } + } else if (id == R.id.account_settings) { + int headerIndex = i + 1; + i = insertAccountsHeaders(target, headerIndex); + } else if (id == R.id.user_settings) { + if (!mEnableUserManagement + || !UserId.MU_ENABLED || UserId.myUserId() != 0 + || !getResources().getBoolean(R.bool.enable_user_management) + || Utils.isMonkeyRunning()) { + target.remove(header); + } + } + if (UserId.MU_ENABLED && UserId.myUserId() != 0 + && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) { + target.remove(header); } // Increment if the current one wasn't removed by the Utils code. @@ -350,6 +434,63 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { } } + private int insertAccountsHeaders(List<Header> target, int headerIndex) { + String[] accountTypes = mAuthenticatorHelper.getEnabledAccountTypes(); + List<Header> accountHeaders = new ArrayList<Header>(accountTypes.length); + for (String accountType : accountTypes) { + CharSequence label = mAuthenticatorHelper.getLabelForType(this, accountType); + Account[] accounts = AccountManager.get(this).getAccountsByType(accountType); + boolean skipToAccount = accounts.length == 1 + && !mAuthenticatorHelper.hasAccountPreferences(accountType); + Header accHeader = new Header(); + accHeader.title = label; + if (accHeader.extras == null) { + accHeader.extras = new Bundle(); + } + if (skipToAccount) { + accHeader.breadCrumbTitleRes = R.string.account_sync_settings_title; + accHeader.breadCrumbShortTitleRes = R.string.account_sync_settings_title; + accHeader.fragment = AccountSyncSettings.class.getName(); + accHeader.fragmentArguments = new Bundle(); + // Need this for the icon + accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType); + accHeader.extras.putParcelable(AccountSyncSettings.ACCOUNT_KEY, accounts[0]); + accHeader.fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY, + accounts[0]); + } else { + accHeader.breadCrumbTitle = label; + accHeader.breadCrumbShortTitle = label; + accHeader.fragment = ManageAccountsSettings.class.getName(); + accHeader.fragmentArguments = new Bundle(); + accHeader.extras.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType); + accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, + accountType); + if (!isMultiPane()) { + accHeader.fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL, + label.toString()); + } + } + accountHeaders.add(accHeader); + } + + // Sort by label + Collections.sort(accountHeaders, new Comparator<Header>() { + @Override + public int compare(Header h1, Header h2) { + return h1.title.toString().compareTo(h2.title.toString()); + } + }); + + for (Header header : accountHeaders) { + target.add(headerIndex++, header); + } + if (!mListeningToAccountUpdates) { + AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true); + mListeningToAccountUpdates = true; + } + return headerIndex; + } + private boolean needsDockSettings() { return getResources().getBoolean(R.bool.has_dock_settings); } @@ -361,7 +502,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { if (ai == null || ai.metaData == null) return; mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID); mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); - + // Check if it has a parent specified and create a Header object final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE); String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS); @@ -395,6 +536,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { private final WifiEnabler mWifiEnabler; private final BluetoothEnabler mBluetoothEnabler; + private AuthenticatorHelper mAuthHelper; private static class HeaderViewHolder { ImageView icon; @@ -441,10 +583,13 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { return true; } - public HeaderAdapter(Context context, List<Header> objects) { + public HeaderAdapter(Context context, List<Header> objects, + AuthenticatorHelper authenticatorHelper) { super(context, 0, objects); + + mAuthHelper = authenticatorHelper; mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - + // Temp Switches provided as placeholder until the adapter replaces these with actual // Switches inflated from their layouts. Must be done before adapter is set in super mWifiEnabler = new WifiEnabler(context, new Switch(context)); @@ -512,7 +657,20 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { //$FALL-THROUGH$ case HEADER_TYPE_NORMAL: - holder.icon.setImageResource(header.iconRes); + if (header.extras != null + && header.extras.containsKey(ManageAccountsSettings.KEY_ACCOUNT_TYPE)) { + String accType = header.extras.getString( + ManageAccountsSettings.KEY_ACCOUNT_TYPE); + ViewGroup.LayoutParams lp = holder.icon.getLayoutParams(); + lp.width = getContext().getResources().getDimensionPixelSize( + R.dimen.header_icon_width); + lp.height = lp.width; + holder.icon.setLayoutParams(lp); + Drawable icon = mAuthHelper.getDrawableForType(getContext(), accType); + holder.icon.setImageDrawable(icon); + } else { + holder.icon.setImageResource(header.iconRes); + } holder.title.setText(header.getTitle(getContext().getResources())); CharSequence summary = header.getSummary(getContext().getResources()); if (!TextUtils.isEmpty(summary)) { @@ -539,16 +697,37 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { } @Override + public void onHeaderClick(Header header, int position) { + boolean revert = false; + if (header.id == R.id.account_add) { + revert = true; + } + + super.onHeaderClick(header, position); + + if (revert && mLastHeader != null) { + highlightHeader((int) mLastHeader.id); + } else { + mLastHeader = header; + } + } + + @Override public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) { // Override the fragment title for Wallpaper settings int titleRes = pref.getTitleRes(); if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) { titleRes = R.string.wallpaper_settings_fragment_title; } - startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, null, null, 0); + startPreferencePanel(pref.getFragment(), pref.getExtras(), titleRes, pref.getTitle(), + null, 0); return true; } + public boolean shouldUpRecreateTask(Intent targetIntent) { + return super.shouldUpRecreateTask(new Intent(this, Settings.class)); + } + @Override public void setListAdapter(ListAdapter adapter) { if (mHeaders == null) { @@ -561,7 +740,13 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { } // Ignore the adapter provided by PreferenceActivity and substitute ours instead - super.setListAdapter(new HeaderAdapter(this, mHeaders)); + super.setListAdapter(new HeaderAdapter(this, mHeaders, mAuthenticatorHelper)); + } + + @Override + public void onAccountsUpdated(Account[] accounts) { + mAuthenticatorHelper.onAccountsUpdated(this, accounts); + invalidateHeaders(); } /* @@ -576,6 +761,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler { public static class WifiSettingsActivity extends Settings { /* empty */ } public static class WifiP2pSettingsActivity extends Settings { /* empty */ } public static class InputMethodAndLanguageSettingsActivity extends Settings { /* empty */ } + public static class KeyboardLayoutPickerActivity extends Settings { /* empty */ } public static class InputMethodAndSubtypeEnablerActivity extends Settings { /* empty */ } public static class SpellCheckersSettingsActivity extends Settings { /* empty */ } public static class LocalePickerActivity extends Settings { /* empty */ } diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java index f7e3b02..368976a 100644 --- a/src/com/android/settings/SettingsPreferenceFragment.java +++ b/src/com/android/settings/SettingsPreferenceFragment.java @@ -21,11 +21,17 @@ import android.app.DialogFragment; import android.app.Fragment; import android.content.ContentResolver; import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; +import android.text.TextUtils; import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.widget.Button; /** @@ -35,11 +41,49 @@ public class SettingsPreferenceFragment extends PreferenceFragment implements Di private static final String TAG = "SettingsPreferenceFragment"; + private static final int MENU_HELP = Menu.FIRST + 100; + private SettingsDialogFragment mDialogFragment; + private String mHelpUrl; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + // Prepare help url and enable menu if necessary + int helpResource = getHelpResource(); + if (helpResource != 0) { + mHelpUrl = getResources().getString(helpResource); + } + } + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + if (!TextUtils.isEmpty(mHelpUrl)) { + setHasOptionsMenu(true); + } + } + + /** + * Override this if you want to show a help item in the menu, by returning the resource id. + * @return the resource id for the help url + */ + protected int getHelpResource() { + return 0; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (mHelpUrl != null) { + Intent helpIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(mHelpUrl)); + helpIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + MenuItem helpItem = menu.add(0, MENU_HELP, 0, R.string.help_label); + helpItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + helpItem.setIntent(helpIntent); + } } /* diff --git a/src/com/android/settings/SoundSettings.java b/src/com/android/settings/SoundSettings.java index cf7cd0a..67557b9 100644 --- a/src/com/android/settings/SoundSettings.java +++ b/src/com/android/settings/SoundSettings.java @@ -16,17 +16,14 @@ package com.android.settings; -import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.media.AudioManager; -import android.media.Ringtone; import android.media.RingtoneManager; import android.media.audiofx.AudioEffect; import android.net.Uri; @@ -41,8 +38,6 @@ import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.provider.MediaStore; import android.provider.Settings; -import android.provider.MediaStore.Images.Media; -import android.provider.Settings.SettingNotFoundException; import android.telephony.TelephonyManager; import android.util.Log; @@ -55,8 +50,7 @@ public class SoundSettings extends SettingsPreferenceFragment implements /** If there is no setting in the provider, use this. */ private static final int FALLBACK_EMERGENCY_TONE_VALUE = 0; - private static final String KEY_SILENT_MODE = "silent_mode"; - private static final String KEY_VIBRATE = "vibrate_on_ring"; + private static final String KEY_VIBRATE = "vibrate_when_ringing"; private static final String KEY_RING_VOLUME = "ring_volume"; private static final String KEY_MUSICFX = "musicfx"; private static final String KEY_DTMF_TONE = "dtmf_tone"; @@ -67,11 +61,7 @@ public class SoundSettings extends SettingsPreferenceFragment implements private static final String KEY_LOCK_SOUNDS = "lock_sounds"; private static final String KEY_RINGTONE = "ringtone"; private static final String KEY_NOTIFICATION_SOUND = "notification_sound"; - private static final String KEY_CATEGORY_CALLS = "category_calls"; - - private static final String SILENT_MODE_OFF = "off"; - private static final String SILENT_MODE_VIBRATE = "vibrate"; - private static final String SILENT_MODE_MUTE = "mute"; + private static final String KEY_CATEGORY_CALLS = "category_calls_and_notification"; private static final String[] NEED_VOICE_CAPABILITY = { KEY_RINGTONE, KEY_DTMF_TONE, KEY_CATEGORY_CALLS, @@ -81,8 +71,7 @@ public class SoundSettings extends SettingsPreferenceFragment implements private static final int MSG_UPDATE_RINGTONE_SUMMARY = 1; private static final int MSG_UPDATE_NOTIFICATION_SUMMARY = 2; - private CheckBoxPreference mVibrateOnRing; - private ListPreference mSilentMode; + private CheckBoxPreference mVibrateWhenRinging; private CheckBoxPreference mDtmfTone; private CheckBoxPreference mSoundEffects; private CheckBoxPreference mHapticFeedback; @@ -95,15 +84,6 @@ public class SoundSettings extends SettingsPreferenceFragment implements private AudioManager mAudioManager; - private BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { - updateState(false); - } - } - }; - private Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { @@ -134,16 +114,14 @@ public class SoundSettings extends SettingsPreferenceFragment implements getPreferenceScreen().removePreference(findPreference(KEY_EMERGENCY_TONE)); } - mSilentMode = (ListPreference) findPreference(KEY_SILENT_MODE); if (!getResources().getBoolean(R.bool.has_silent_mode)) { - getPreferenceScreen().removePreference(mSilentMode); findPreference(KEY_RING_VOLUME).setDependency(null); - } else { - mSilentMode.setOnPreferenceChangeListener(this); } - mVibrateOnRing = (CheckBoxPreference) findPreference(KEY_VIBRATE); - mVibrateOnRing.setOnPreferenceChangeListener(this); + mVibrateWhenRinging = (CheckBoxPreference) findPreference(KEY_VIBRATE); + mVibrateWhenRinging.setPersistent(false); + mVibrateWhenRinging.setChecked(Settings.System.getInt(resolver, + Settings.System.VIBRATE_WHEN_RINGING, 0) != 0); mDtmfTone = (CheckBoxPreference) findPreference(KEY_DTMF_TONE); mDtmfTone.setPersistent(false); @@ -165,8 +143,9 @@ public class SoundSettings extends SettingsPreferenceFragment implements mRingtonePreference = findPreference(KEY_RINGTONE); mNotificationPreference = findPreference(KEY_NOTIFICATION_SOUND); - if (!((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).hasVibrator()) { - getPreferenceScreen().removePreference(mVibrateOnRing); + Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); + if (vibrator == null || !vibrator.hasVibrator()) { + getPreferenceScreen().removePreference(mVibrateWhenRinging); getPreferenceScreen().removePreference(mHapticFeedback); } @@ -220,65 +199,7 @@ public class SoundSettings extends SettingsPreferenceFragment implements public void onResume() { super.onResume(); - updateState(true); lookupRingtoneNames(); - - IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); - getActivity().registerReceiver(mReceiver, filter); - } - - @Override - public void onPause() { - super.onPause(); - - getActivity().unregisterReceiver(mReceiver); - } - - /** - * Put the audio system into the correct vibrate setting - */ - private void setPhoneVibrateSettingValue(boolean vibeOnRing) { - // If vibrate-on-ring is checked, use VIBRATE_SETTING_ON - // Otherwise vibrate is off when ringer is silent - int vibrateMode = vibeOnRing ? AudioManager.VIBRATE_SETTING_ON - : AudioManager.VIBRATE_SETTING_ONLY_SILENT; - mAudioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER, vibrateMode); - mAudioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION, vibrateMode); - } - - private void setPhoneSilentSettingValue(String value) { - int ringerMode = AudioManager.RINGER_MODE_NORMAL; - if (value.equals(SILENT_MODE_MUTE)) { - ringerMode = AudioManager.RINGER_MODE_SILENT; - } else if (value.equals(SILENT_MODE_VIBRATE)) { - ringerMode = AudioManager.RINGER_MODE_VIBRATE; - } - mAudioManager.setRingerMode(ringerMode); - } - - private String getPhoneSilentModeSettingValue() { - switch (mAudioManager.getRingerMode()) { - case AudioManager.RINGER_MODE_NORMAL: - return SILENT_MODE_OFF; - case AudioManager.RINGER_MODE_VIBRATE: - return SILENT_MODE_VIBRATE; - case AudioManager.RINGER_MODE_SILENT: - return SILENT_MODE_MUTE; - } - // Shouldn't happen - return SILENT_MODE_OFF; - } - - // updateState in fact updates the UI to reflect the system state - private void updateState(boolean force) { - if (getActivity() == null) return; - - final int vibrateMode = mAudioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER); - - mVibrateOnRing.setChecked(vibrateMode == AudioManager.VIBRATE_SETTING_ON); - mSilentMode.setValue(getPhoneSilentModeSettingValue()); - - mSilentMode.setSummary(mSilentMode.getEntry()); } private void updateRingtoneName(int type, Preference preference, int msg) { @@ -314,7 +235,10 @@ public class SoundSettings extends SettingsPreferenceFragment implements @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - if (preference == mDtmfTone) { + if (preference == mVibrateWhenRinging) { + Settings.System.putInt(getContentResolver(), Settings.System.VIBRATE_WHEN_RINGING, + mVibrateWhenRinging.isChecked() ? 1 : 0); + } else if (preference == mDtmfTone) { Settings.System.putInt(getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, mDtmfTone.isChecked() ? 1 : 0); @@ -353,12 +277,13 @@ public class SoundSettings extends SettingsPreferenceFragment implements } catch (NumberFormatException e) { Log.e(TAG, "could not persist emergency tone setting", e); } - } else if (preference == mVibrateOnRing) { - setPhoneVibrateSettingValue((Boolean) objValue); - } else if (preference == mSilentMode) { - setPhoneSilentSettingValue(objValue.toString()); } return true; } + + @Override + protected int getHelpResource() { + return R.string.help_url_sound; + } } diff --git a/src/com/android/settings/SubSettings.java b/src/com/android/settings/SubSettings.java index 9cd3c31..eb275ad 100644 --- a/src/com/android/settings/SubSettings.java +++ b/src/com/android/settings/SubSettings.java @@ -21,4 +21,10 @@ package com.android.settings; * since for our app it is a special singleTask class. */ public class SubSettings extends Settings { + + @Override + public boolean onNavigateUp() { + finish(); + return true; + } } diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java index 867f733..77a72a7 100644 --- a/src/com/android/settings/TetherSettings.java +++ b/src/com/android/settings/TetherSettings.java @@ -37,6 +37,7 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Environment; +import android.os.SystemProperties; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceScreen; @@ -58,14 +59,8 @@ public class TetherSettings extends SettingsPreferenceFragment private static final String USB_TETHER_SETTINGS = "usb_tether_settings"; private static final String ENABLE_WIFI_AP = "enable_wifi_ap"; private static final String ENABLE_BLUETOOTH_TETHERING = "enable_bluetooth_tethering"; - private static final String TETHERING_HELP = "tethering_help"; - private static final String USB_HELP_MODIFIER = "usb_"; - private static final String WIFI_HELP_MODIFIER = "wifi_"; - private static final String HELP_URL = "file:///android_asset/html/%y%z/tethering_%xhelp.html"; - private static final String HELP_PATH = "html/%y%z/tethering_help.html"; - private static final int DIALOG_TETHER_HELP = 1; - private static final int DIALOG_AP_SETTINGS = 2; + private static final int DIALOG_AP_SETTINGS = 1; private WebView mView; private CheckBoxPreference mUsbTether; @@ -75,8 +70,6 @@ public class TetherSettings extends SettingsPreferenceFragment private CheckBoxPreference mBluetoothTether; - private PreferenceScreen mTetherHelp; - private BroadcastReceiver mTetherChangeReceiver; private String[] mUsbRegexs; @@ -130,7 +123,6 @@ public class TetherSettings extends SettingsPreferenceFragment Preference wifiApSettings = findPreference(WIFI_AP_SSID_AND_SECURITY); mUsbTether = (CheckBoxPreference) findPreference(USB_TETHER_SETTINGS); mBluetoothTether = (CheckBoxPreference) findPreference(ENABLE_BLUETOOTH_TETHERING); - mTetherHelp = (PreferenceScreen) findPreference(TETHERING_HELP); ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); @@ -204,50 +196,7 @@ public class TetherSettings extends SettingsPreferenceFragment @Override public Dialog onCreateDialog(int id) { - if (id == DIALOG_TETHER_HELP) { - Locale locale = Locale.getDefault(); - - // check for the full language + country resource, if not there, try just language - final AssetManager am = getActivity().getAssets(); - String path = HELP_PATH.replace("%y", locale.getLanguage().toLowerCase()); - path = path.replace("%z", '_'+locale.getCountry().toLowerCase()); - boolean useCountry = true; - InputStream is = null; - try { - is = am.open(path); - } catch (Exception ignored) { - useCountry = false; - } finally { - if (is != null) { - try { - is.close(); - } catch (Exception ignored) {} - } - } - String url = HELP_URL.replace("%y", locale.getLanguage().toLowerCase()); - url = url.replace("%z", useCountry ? '_'+locale.getCountry().toLowerCase() : ""); - if ((mUsbRegexs.length != 0) && (mWifiRegexs.length == 0)) { - url = url.replace("%x", USB_HELP_MODIFIER); - } else if ((mWifiRegexs.length != 0) && (mUsbRegexs.length == 0)) { - url = url.replace("%x", WIFI_HELP_MODIFIER); - } else { - // could assert that both wifi and usb have regexs, but the default - // is to use this anyway so no check is needed - url = url.replace("%x", ""); - } - - mView.loadUrl(url); - // Detach from old parent first - ViewParent parent = mView.getParent(); - if (parent != null && parent instanceof ViewGroup) { - ((ViewGroup) parent).removeView(mView); - } - return new AlertDialog.Builder(getActivity()) - .setCancelable(true) - .setTitle(R.string.tethering_help_button_text) - .setView(mView) - .create(); - } else if (id == DIALOG_AP_SETTINGS) { + if (id == DIALOG_AP_SETTINGS) { final Activity activity = getActivity(); mDialog = new WifiApDialog(activity, this, mWifiConfig); return mDialog; @@ -476,6 +425,9 @@ public class TetherSettings extends SettingsPreferenceFragment } boolean isProvisioningNeeded() { + if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)) { + return false; + } return mProvisionApp.length == 2; } @@ -584,9 +536,6 @@ public class TetherSettings extends SettingsPreferenceFragment mBluetoothTether.setSummary(R.string.bluetooth_tethering_off_subtext); } } - } else if (preference == mTetherHelp) { - showDialog(DIALOG_TETHER_HELP); - return true; } else if (preference == mCreateNetwork) { showDialog(DIALOG_AP_SETTINGS); } @@ -627,4 +576,9 @@ public class TetherSettings extends SettingsPreferenceFragment } } } + + @Override + public int getHelpResource() { + return R.string.help_url_tether; + } } diff --git a/src/com/android/settings/UserDictionarySettings.java b/src/com/android/settings/UserDictionarySettings.java index 496947b..b561ed4 100644 --- a/src/com/android/settings/UserDictionarySettings.java +++ b/src/com/android/settings/UserDictionarySettings.java @@ -20,6 +20,7 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.ListFragment; +import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -27,6 +28,7 @@ import android.database.Cursor; import android.os.Bundle; import android.provider.UserDictionary; import android.text.InputType; +import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -44,22 +46,19 @@ import android.widget.SectionIndexer; import android.widget.SimpleCursorAdapter; import android.widget.TextView; -import com.android.settings.SettingsPreferenceFragment.SettingsDialogFragment; +import com.android.settings.inputmethod.UserDictionaryAddWordContents; import java.util.Locale; -public class UserDictionarySettings extends ListFragment implements DialogCreatable { +public class UserDictionarySettings extends ListFragment { private static final String TAG = "UserDictionarySettings"; - private static final String INSTANCE_KEY_DIALOG_EDITING_WORD = "DIALOG_EDITING_WORD"; - private static final String INSTANCE_KEY_ADDED_WORD = "DIALOG_ADDED_WORD"; - private static final String[] QUERY_PROJECTION = { - UserDictionary.Words._ID, UserDictionary.Words.WORD + UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT }; - private static final int INDEX_ID = 0; - private static final int INDEX_WORD = 1; + // The index of the shortcut in the above array. + private static final int INDEX_SHORTCUT = 2; // Either the locale is empty (means the word is applicable to all locales) // or the word equals our current locale @@ -68,28 +67,18 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata private static final String QUERY_SELECTION_ALL_LOCALES = UserDictionary.Words.LOCALE + " is null"; - private static final String DELETE_SELECTION = UserDictionary.Words.WORD + "=?"; - - private static final String EXTRA_WORD = "word"; + private static final String DELETE_SELECTION_WITH_SHORTCUT = UserDictionary.Words.WORD + + "=? AND " + UserDictionary.Words.SHORTCUT + "=?"; + private static final String DELETE_SELECTION_WITHOUT_SHORTCUT = UserDictionary.Words.WORD + + "=? AND " + UserDictionary.Words.SHORTCUT + " is null OR " + + UserDictionary.Words.SHORTCUT + "=''"; private static final int OPTIONS_MENU_ADD = Menu.FIRST; - private static final int DIALOG_ADD_OR_EDIT = 0; - - private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250; - - /** The word being edited in the dialog (null means the user is adding a word). */ - private String mDialogEditingWord; - private Cursor mCursor; protected String mLocale; - private boolean mAddedWordAlready; - private boolean mAutoReturn; - - private SettingsDialogFragment mDialogFragment; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -135,31 +124,6 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata setHasOptionsMenu(true); - if (savedInstanceState != null) { - mDialogEditingWord = savedInstanceState.getString(INSTANCE_KEY_DIALOG_EDITING_WORD); - mAddedWordAlready = savedInstanceState.getBoolean(INSTANCE_KEY_ADDED_WORD, false); - } - } - - @Override - public void onResume() { - super.onResume(); - final Intent intent = getActivity().getIntent(); - if (!mAddedWordAlready - && intent.getAction().equals("com.android.settings.USER_DICTIONARY_INSERT")) { - final String word = intent.getStringExtra(EXTRA_WORD); - mAutoReturn = true; - if (word != null) { - showAddOrEditDialog(word); - } - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(INSTANCE_KEY_DIALOG_EDITING_WORD, mDialogEditingWord); - outState.putBoolean(INSTANCE_KEY_ADDED_WORD, mAddedWordAlready); } private Cursor createCursor(final String locale) { @@ -189,15 +153,16 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata private ListAdapter createAdapter() { return new MyAdapter(getActivity(), R.layout.user_dictionary_item, mCursor, - new String[] { UserDictionary.Words.WORD, UserDictionary.Words._ID }, - new int[] { android.R.id.text1, R.id.delete_button }, this); + new String[] { UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT }, + new int[] { android.R.id.text1, android.R.id.text2 }, this); } @Override public void onListItemClick(ListView l, View v, int position, long id) { - String word = getWord(position); + final String word = getWord(position); + final String shortcut = getShortcut(position); if (word != null) { - showAddOrEditDialog(word); + showAddOrEditDialog(word, shortcut); } } @@ -212,16 +177,34 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata @Override public boolean onOptionsItemSelected(MenuItem item) { - showAddOrEditDialog(null); - return true; + if (item.getItemId() == OPTIONS_MENU_ADD) { + showAddOrEditDialog(null, null); + return true; + } + return false; } - private void showAddOrEditDialog(String editingWord) { - mDialogEditingWord = editingWord; - showDialog(DIALOG_ADD_OR_EDIT); + /** + * Add or edit a word. If editingWord is null, it's an add; otherwise, it's an edit. + * @param editingWord the word to edit, or null if it's an add. + * @param editingShortcut the shortcut for this entry, or null if none. + */ + private void showAddOrEditDialog(final String editingWord, final String editingShortcut) { + final Bundle args = new Bundle(); + args.putInt(UserDictionaryAddWordContents.EXTRA_MODE, null == editingWord + ? UserDictionaryAddWordContents.MODE_INSERT + : UserDictionaryAddWordContents.MODE_EDIT); + args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord); + args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut); + args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale); + android.preference.PreferenceActivity pa = + (android.preference.PreferenceActivity)getActivity(); + pa.startPreferencePanel( + com.android.settings.inputmethod.UserDictionaryAddWordFragment.class.getName(), + args, R.string.user_dict_settings_add_dialog_title, null, null, 0); } - private String getWord(int position) { + private String getWord(final int position) { if (null == mCursor) return null; mCursor.moveToPosition(position); // Handle a possible race-condition @@ -231,96 +214,45 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD)); } - @Override - public Dialog onCreateDialog(int id) { - final Activity activity = getActivity(); - final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity); - final LayoutInflater inflater = LayoutInflater.from(dialogBuilder.getContext()); - final View content = inflater.inflate(R.layout.dialog_edittext, null); - final EditText editText = (EditText) content.findViewById(R.id.edittext); - editText.setText(mDialogEditingWord); - // No prediction in soft keyboard mode. TODO: Create a better way to disable prediction - editText.setInputType(InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); - - AlertDialog dialog = dialogBuilder - .setTitle(mDialogEditingWord != null - ? R.string.user_dict_settings_edit_dialog_title - : R.string.user_dict_settings_add_dialog_title) - .setView(content) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - onAddOrEditFinished(editText.getText().toString()); - if (mAutoReturn) activity.onBackPressed(); - }}) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (mAutoReturn) activity.onBackPressed(); - }}) - .create(); - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN | - WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - return dialog; - } + private String getShortcut(final int position) { + if (null == mCursor) return null; + mCursor.moveToPosition(position); + // Handle a possible race-condition + if (mCursor.isAfterLast()) return null; - private void showDialog(int dialogId) { - if (mDialogFragment != null) { - Log.e(TAG, "Old dialog fragment not null!"); - } - mDialogFragment = new SettingsDialogFragment(this, dialogId); - mDialogFragment.show(getActivity().getFragmentManager(), Integer.toString(dialogId)); + return mCursor.getString( + mCursor.getColumnIndexOrThrow(UserDictionary.Words.SHORTCUT)); } - private void onAddOrEditFinished(String word) { - if (mDialogEditingWord != null) { - // The user was editing a word, so do a delete/add - deleteWord(mDialogEditingWord); - } - - // Disallow duplicates - deleteWord(word); - - // TODO: present UI for picking whether to add word to all locales, or current. - if (null == mLocale) { - // Null means insert with the default system locale. - UserDictionary.Words.addWord(getActivity(), word.toString(), - FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_CURRENT); - } else if ("".equals(mLocale)) { - // Empty string means insert for all languages. - UserDictionary.Words.addWord(getActivity(), word.toString(), - FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_ALL); + public static void deleteWord(final String word, final String shortcut, + final ContentResolver resolver) { + if (TextUtils.isEmpty(shortcut)) { + resolver.delete( + UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT, + new String[] { word }); } else { - // TODO: fix the framework so that it can accept a locale when we add a word - // to the user dictionary instead of querying the system locale. - final Locale prevLocale = Locale.getDefault(); - Locale.setDefault(Utils.createLocaleFromString(mLocale)); - UserDictionary.Words.addWord(getActivity(), word.toString(), - FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_CURRENT); - Locale.setDefault(prevLocale); - } - if (null != mCursor && !mCursor.requery()) { - throw new IllegalStateException("can't requery on already-closed cursor."); + resolver.delete( + UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT, + new String[] { word, shortcut }); } - mAddedWordAlready = true; } - private void deleteWord(String word) { - getActivity().getContentResolver().delete( - UserDictionary.Words.CONTENT_URI, DELETE_SELECTION, new String[] { word }); - } - - private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer, - View.OnClickListener { + private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer { private AlphabetIndexer mIndexer; - private UserDictionarySettings mSettings; private ViewBinder mViewBinder = new ViewBinder() { public boolean setViewValue(View v, Cursor c, int columnIndex) { - if (v instanceof ImageView && columnIndex == INDEX_ID) { - v.setOnClickListener(MyAdapter.this); - v.setTag(c.getString(INDEX_WORD)); + if (columnIndex == INDEX_SHORTCUT) { + final String shortcut = c.getString(INDEX_SHORTCUT); + if (TextUtils.isEmpty(shortcut)) { + v.setVisibility(View.GONE); + } else { + ((TextView)v).setText(shortcut); + v.setVisibility(View.VISIBLE); + } + v.invalidate(); return true; } @@ -332,7 +264,6 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata UserDictionarySettings settings) { super(context, layout, c, from, to); - mSettings = settings; if (null != c) { final String alphabet = context.getString( com.android.internal.R.string.fast_scroll_alphabet); @@ -353,9 +284,5 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata public Object[] getSections() { return null == mIndexer ? null : mIndexer.getSections(); } - - public void onClick(View v) { - mSettings.deleteWord((String) v.getTag()); - } } } diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 582891d..a029342 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -407,7 +407,7 @@ public class Utils { final int paddingBottom = res.getDimensionPixelSize( com.android.internal.R.dimen.preference_fragment_padding_bottom); - final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingBottom; + final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide; list.setPadding(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom); } } diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java index 13f6902..3b67ec3 100644 --- a/src/com/android/settings/WirelessSettings.java +++ b/src/com/android/settings/WirelessSettings.java @@ -37,7 +37,7 @@ import android.widget.Switch; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyProperties; import com.android.settings.nfc.NfcEnabler; -import com.android.settings.wifi.p2p.WifiP2pEnabler; +import com.android.settings.NsdEnabler; public class WirelessSettings extends SettingsPreferenceFragment { @@ -46,11 +46,10 @@ public class WirelessSettings extends SettingsPreferenceFragment { private static final String KEY_WIMAX_SETTINGS = "wimax_settings"; private static final String KEY_ANDROID_BEAM_SETTINGS = "android_beam_settings"; private static final String KEY_VPN_SETTINGS = "vpn_settings"; - private static final String KEY_TOGGLE_WIFI_P2P = "toggle_wifi_p2p"; - private static final String KEY_WIFI_P2P_SETTINGS = "wifi_p2p_settings"; private static final String KEY_TETHER_SETTINGS = "tether_settings"; private static final String KEY_PROXY_SETTINGS = "proxy_settings"; private static final String KEY_MOBILE_NETWORK_SETTINGS = "mobile_network_settings"; + private static final String KEY_TOGGLE_NSD = "toggle_nsd"; //network service discovery public static final String EXIT_ECM_RESULT = "exit_ecm_result"; public static final int REQUEST_CODE_EXIT_ECM = 1; @@ -59,8 +58,7 @@ public class WirelessSettings extends SettingsPreferenceFragment { private CheckBoxPreference mAirplaneModePreference; private NfcEnabler mNfcEnabler; private NfcAdapter mNfcAdapter; - - private WifiP2pEnabler mWifiP2pEnabler; + private NsdEnabler mNsdEnabler; /** * Invoked on each preference click in this hierarchy, overrides @@ -101,12 +99,15 @@ public class WirelessSettings extends SettingsPreferenceFragment { mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE); CheckBoxPreference nfc = (CheckBoxPreference) findPreference(KEY_TOGGLE_NFC); PreferenceScreen androidBeam = (PreferenceScreen) findPreference(KEY_ANDROID_BEAM_SETTINGS); - - CheckBoxPreference wifiP2p = (CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI_P2P); + CheckBoxPreference nsd = (CheckBoxPreference) findPreference(KEY_TOGGLE_NSD); mAirplaneModeEnabler = new AirplaneModeEnabler(activity, mAirplaneModePreference); mNfcEnabler = new NfcEnabler(activity, nfc, androidBeam); + // Remove NSD checkbox by default + getPreferenceScreen().removePreference(nsd); + //mNsdEnabler = new NsdEnabler(activity, nsd); + String toggleable = Settings.System.getString(activity.getContentResolver(), Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS); @@ -153,15 +154,6 @@ public class WirelessSettings extends SettingsPreferenceFragment { getPreferenceScreen().removePreference(findPreference(KEY_MOBILE_NETWORK_SETTINGS)); } - WifiP2pManager p2p = (WifiP2pManager) activity.getSystemService(Context.WIFI_P2P_SERVICE); - - if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) { - getPreferenceScreen().removePreference(wifiP2p); - } else { - mWifiP2pEnabler = new WifiP2pEnabler(activity, wifiP2p); - } - getPreferenceScreen().removePreference(findPreference(KEY_WIFI_P2P_SETTINGS)); - // Enable Proxy selector settings if allowed. Preference mGlobalProxy = findPreference(KEY_PROXY_SETTINGS); DevicePolicyManager mDPM = (DevicePolicyManager) @@ -189,9 +181,8 @@ public class WirelessSettings extends SettingsPreferenceFragment { if (mNfcEnabler != null) { mNfcEnabler.resume(); } - - if (mWifiP2pEnabler != null) { - mWifiP2pEnabler.resume(); + if (mNsdEnabler != null) { + mNsdEnabler.resume(); } } @@ -203,9 +194,8 @@ public class WirelessSettings extends SettingsPreferenceFragment { if (mNfcEnabler != null) { mNfcEnabler.pause(); } - - if (mWifiP2pEnabler != null) { - mWifiP2pEnabler.pause(); + if (mNsdEnabler != null) { + mNsdEnabler.pause(); } } @@ -218,4 +208,9 @@ public class WirelessSettings extends SettingsPreferenceFragment { mAirplaneModePreference.isChecked()); } } + + @Override + protected int getHelpResource() { + return R.string.help_url_more_networks; + } } diff --git a/src/com/android/settings/accounts/AccountPreferenceBase.java b/src/com/android/settings/accounts/AccountPreferenceBase.java index a0d6a7f..2759a8f 100644 --- a/src/com/android/settings/accounts/AccountPreferenceBase.java +++ b/src/com/android/settings/accounts/AccountPreferenceBase.java @@ -17,6 +17,7 @@ package com.android.settings.accounts; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -27,6 +28,7 @@ import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AuthenticatorDescription; import android.accounts.OnAccountsUpdateListener; +import android.app.Activity; import android.content.ContentResolver; import android.content.Context; import android.content.SyncAdapterType; @@ -38,6 +40,7 @@ import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; +import android.text.format.DateFormat; import android.util.Log; class AccountPreferenceBase extends SettingsPreferenceFragment @@ -46,12 +49,12 @@ class AccountPreferenceBase extends SettingsPreferenceFragment protected static final String TAG = "AccountSettings"; public static final String AUTHORITIES_FILTER_KEY = "authorities"; public static final String ACCOUNT_TYPES_FILTER_KEY = "account_types"; - private Map<String, AuthenticatorDescription> mTypeToAuthDescription - = new HashMap<String, AuthenticatorDescription>(); - protected AuthenticatorDescription[] mAuthDescs; private final Handler mHandler = new Handler(); private Object mStatusChangeListenerHandle; private HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = null; + private AuthenticatorHelper mAuthenticatorHelper = new AuthenticatorHelper(); + private java.text.DateFormat mDateFormat; + private java.text.DateFormat mTimeFormat; /** * Overload to handle account updates. @@ -75,6 +78,16 @@ class AccountPreferenceBase extends SettingsPreferenceFragment } @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Activity activity = getActivity(); + + mDateFormat = DateFormat.getDateFormat(activity); + mTimeFormat = DateFormat.getTimeFormat(activity); + } + + @Override public void onResume() { super.onResume(); mStatusChangeListenerHandle = ContentResolver.addStatusChangeListener( @@ -91,7 +104,6 @@ class AccountPreferenceBase extends SettingsPreferenceFragment ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle); } - private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() { public void onStatusChanged(int which) { mHandler.post(new Runnable() { @@ -124,64 +136,21 @@ class AccountPreferenceBase extends SettingsPreferenceFragment } /** - * Gets an icon associated with a particular account type. If none found, return null. - * @param accountType the type of account - * @return a drawable for the icon or null if one cannot be found. - */ - protected Drawable getDrawableForType(final String accountType) { - Drawable icon = null; - if (mTypeToAuthDescription.containsKey(accountType)) { - try { - AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); - Context authContext = getActivity().createPackageContext(desc.packageName, 0); - icon = authContext.getResources().getDrawable(desc.iconId); - } catch (PackageManager.NameNotFoundException e) { - // TODO: place holder icon for missing account icons? - Log.w(TAG, "No icon name for account type " + accountType); - } catch (Resources.NotFoundException e) { - // TODO: place holder icon for missing account icons? - Log.w(TAG, "No icon resource for account type " + accountType); - } - } - return icon; - } - - /** - * Gets the label associated with a particular account type. If none found, return null. - * @param accountType the type of account - * @return a CharSequence for the label or null if one cannot be found. - */ - protected CharSequence getLabelForType(final String accountType) { - CharSequence label = null; - if (mTypeToAuthDescription.containsKey(accountType)) { - try { - AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); - Context authContext = getActivity().createPackageContext(desc.packageName, 0); - label = authContext.getResources().getText(desc.labelId); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "No label name for account type " + accountType); - } catch (Resources.NotFoundException e) { - Log.w(TAG, "No label icon for account type " + accountType); - } - } - return label; - } - - /** * Gets the preferences.xml file associated with a particular account type. * @param accountType the type of account * @return a PreferenceScreen inflated from accountPreferenceId. */ - protected PreferenceScreen addPreferencesForType(final String accountType) { + public PreferenceScreen addPreferencesForType(final String accountType, + PreferenceScreen parent) { PreferenceScreen prefs = null; - if (mTypeToAuthDescription.containsKey(accountType)) { + if (mAuthenticatorHelper.containsAccountType(accountType)) { AuthenticatorDescription desc = null; try { - desc = mTypeToAuthDescription.get(accountType); + desc = mAuthenticatorHelper.getAccountTypeDescription(accountType); if (desc != null && desc.accountPreferencesId != 0) { Context authContext = getActivity().createPackageContext(desc.packageName, 0); prefs = getPreferenceManager().inflateFromResource(authContext, - desc.accountPreferencesId, getPreferenceScreen()); + desc.accountPreferencesId, parent); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName); @@ -192,15 +161,21 @@ class AccountPreferenceBase extends SettingsPreferenceFragment return prefs; } - /** - * Updates provider icons. Subclasses should call this in onCreate() - * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated(). - */ - protected void updateAuthDescriptions() { - mAuthDescs = AccountManager.get(getActivity()).getAuthenticatorTypes(); - for (int i = 0; i < mAuthDescs.length; i++) { - mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]); - } + public void updateAuthDescriptions() { + mAuthenticatorHelper.updateAuthDescriptions(getActivity()); onAuthDescriptionsUpdated(); } + + protected Drawable getDrawableForType(final String accountType) { + return mAuthenticatorHelper.getDrawableForType(getActivity(), accountType); + } + + protected CharSequence getLabelForType(final String accountType) { + return mAuthenticatorHelper.getLabelForType(getActivity(), accountType); + } + + protected String formatSyncDate(Date date) { + // TODO: Switch to using DateUtils.formatDateTime + return mDateFormat.format(date) + " " + mTimeFormat.format(date); + } } diff --git a/src/com/android/settings/accounts/AccountSyncSettings.java b/src/com/android/settings/accounts/AccountSyncSettings.java index 6847607..196908e 100644 --- a/src/com/android/settings/accounts/AccountSyncSettings.java +++ b/src/com/android/settings/accounts/AccountSyncSettings.java @@ -63,9 +63,9 @@ import java.util.List; public class AccountSyncSettings extends AccountPreferenceBase { public static final String ACCOUNT_KEY = "account"; - protected static final int MENU_REMOVE_ACCOUNT_ID = Menu.FIRST; - private static final int MENU_SYNC_NOW_ID = Menu.FIRST + 1; - private static final int MENU_SYNC_CANCEL_ID = Menu.FIRST + 2; + private static final int MENU_SYNC_NOW_ID = Menu.FIRST; + private static final int MENU_SYNC_CANCEL_ID = Menu.FIRST + 1; + private static final int MENU_REMOVE_ACCOUNT_ID = Menu.FIRST + 2; private static final int REALLY_REMOVE_DIALOG = 100; private static final int FAILED_REMOVAL_DIALOG = 101; private static final int CANT_DO_ONETIME_SYNC_DIALOG = 102; @@ -73,8 +73,6 @@ public class AccountSyncSettings extends AccountPreferenceBase { private TextView mProviderId; private ImageView mProviderIcon; private TextView mErrorInfoView; - private java.text.DateFormat mDateFormat; - private java.text.DateFormat mTimeFormat; private Account mAccount; // List of all accounts, updated when accounts are added/removed // We need to re-scan the accounts on sync events, in case sync state changes. @@ -98,6 +96,10 @@ public class AccountSyncSettings extends AccountPreferenceBase { .removeAccount(mAccount, new AccountManagerCallback<Boolean>() { public void run(AccountManagerFuture<Boolean> future) { + // If already out of this screen, don't proceed. + if (!AccountSyncSettings.this.isResumed()) { + return; + } boolean failed = true; try { if (future.getResult() == true) { @@ -168,11 +170,6 @@ public class AccountSyncSettings extends AccountPreferenceBase { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - final Activity activity = getActivity(); - - mDateFormat = DateFormat.getDateFormat(activity); - mTimeFormat = DateFormat.getTimeFormat(activity); - Bundle arguments = getArguments(); if (arguments == null) { Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed."); @@ -208,11 +205,13 @@ public class AccountSyncSettings extends AccountPreferenceBase { new SyncStateCheckBoxPreference(getActivity(), account, authority); item.setPersistent(false); final ProviderInfo providerInfo = getPackageManager().resolveContentProvider(authority, 0); - CharSequence providerLabel = providerInfo != null - ? providerInfo.loadLabel(getPackageManager()) : null; + if (providerInfo == null) { + return; + } + CharSequence providerLabel = providerInfo.loadLabel(getPackageManager()); if (TextUtils.isEmpty(providerLabel)) { Log.e(TAG, "Provider needs a label for authority '" + authority + "'"); - providerLabel = authority; + return; } String title = getString(R.string.sync_item_title, providerLabel); item.setTitle(title); @@ -222,17 +221,16 @@ public class AccountSyncSettings extends AccountPreferenceBase { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - MenuItem removeAccount = menu.add(0, MENU_REMOVE_ACCOUNT_ID, 0, - getString(R.string.remove_account_label)) - .setIcon(R.drawable.ic_menu_delete_holo_dark); MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0, - getString(R.string.sync_menu_sync_now)) + getString(R.string.sync_menu_sync_now)) .setIcon(R.drawable.ic_menu_refresh_holo_dark); MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0, - getString(R.string.sync_menu_sync_cancel)) + getString(R.string.sync_menu_sync_cancel)) .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel); + MenuItem removeAccount = menu.add(0, MENU_REMOVE_ACCOUNT_ID, 0, + getString(R.string.remove_account_label)) + .setIcon(R.drawable.ic_menu_delete_holo_dark); removeAccount.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); @@ -240,6 +238,8 @@ public class AccountSyncSettings extends AccountPreferenceBase { MenuItem.SHOW_AS_ACTION_WITH_TEXT); syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + super.onCreateOptionsMenu(menu, inflater); } @Override @@ -394,11 +394,14 @@ public class AccountSyncSettings extends AccountPreferenceBase { } final long successEndTime = (status == null) ? 0 : status.lastSuccessTime; - if (successEndTime != 0) { + if (!syncEnabled) { + syncPref.setSummary(R.string.sync_disabled); + } else if (activelySyncing) { + syncPref.setSummary(R.string.sync_in_progress); + } else if (successEndTime != 0) { date.setTime(successEndTime); - final String timeString = mDateFormat.format(date) + " " - + mTimeFormat.format(date); - syncPref.setSummary(timeString); + final String timeString = formatSyncDate(date); + syncPref.setSummary(getResources().getString(R.string.last_synced, timeString)); } else { syncPref.setSummary(""); } @@ -498,25 +501,12 @@ public class AccountSyncSettings extends AccountPreferenceBase { if (mAccount != null) { mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type)); mProviderId.setText(getLabelForType(mAccount.type)); - PreferenceScreen prefs = addPreferencesForType(mAccount.type); - if (prefs != null) { - updatePreferenceIntents(prefs); - } } addPreferencesFromResource(R.xml.account_sync_settings); } - private void updatePreferenceIntents(PreferenceScreen prefs) { - for (int i = 0; i < prefs.getPreferenceCount(); i++) { - Intent intent = prefs.getPreference(i).getIntent(); - if (intent != null) { - intent.putExtra(ACCOUNT_KEY, mAccount); - // This is somewhat of a hack. Since the preference screen we're accessing comes - // from another package, we need to modify the intent to launch it with - // FLAG_ACTIVITY_NEW_TASK. - // TODO: Do something smarter if we ever have PreferenceScreens of our own. - intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - } - } + @Override + protected int getHelpResource() { + return R.string.help_url_accounts; } } diff --git a/src/com/android/settings/accounts/AuthenticatorHelper.java b/src/com/android/settings/accounts/AuthenticatorHelper.java new file mode 100644 index 0000000..ab2fe74 --- /dev/null +++ b/src/com/android/settings/accounts/AuthenticatorHelper.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2012 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.accounts; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorDescription; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.ScaleDrawable; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class AuthenticatorHelper { + + private static final String TAG = "AccountTypesHelper"; + private Map<String, AuthenticatorDescription> mTypeToAuthDescription + = new HashMap<String, AuthenticatorDescription>(); + private AuthenticatorDescription[] mAuthDescs; + private ArrayList<String> mEnabledAccountTypes = new ArrayList<String>(); + private Map<String, Drawable> mAccTypeIconCache = new HashMap<String, Drawable>(); + + public AuthenticatorHelper() { + } + + public String[] getEnabledAccountTypes() { + return mEnabledAccountTypes.toArray(new String[mEnabledAccountTypes.size()]); + } + + /** + * Gets an icon associated with a particular account type. If none found, return null. + * @param accountType the type of account + * @return a drawable for the icon or null if one cannot be found. + */ + public Drawable getDrawableForType(Context context, final String accountType) { + Drawable icon = null; + if (mAccTypeIconCache.containsKey(accountType)) { + return mAccTypeIconCache.get(accountType); + } + if (mTypeToAuthDescription.containsKey(accountType)) { + try { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + Context authContext = context.createPackageContext(desc.packageName, 0); + icon = authContext.getResources().getDrawable(desc.iconId); + mAccTypeIconCache.put(accountType, icon); + } catch (PackageManager.NameNotFoundException e) { + } catch (Resources.NotFoundException e) { + } + } + if (icon == null) { + icon = context.getPackageManager().getDefaultActivityIcon(); + } + return icon; + } + + /** + * Gets the label associated with a particular account type. If none found, return null. + * @param accountType the type of account + * @return a CharSequence for the label or null if one cannot be found. + */ + public CharSequence getLabelForType(Context context, final String accountType) { + CharSequence label = null; + if (mTypeToAuthDescription.containsKey(accountType)) { + try { + AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType); + Context authContext = context.createPackageContext(desc.packageName, 0); + label = authContext.getResources().getText(desc.labelId); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "No label name for account type " + accountType); + } catch (Resources.NotFoundException e) { + Log.w(TAG, "No label icon for account type " + accountType); + } + } + return label; + } + + /** + * Updates provider icons. Subclasses should call this in onCreate() + * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated(). + */ + public void updateAuthDescriptions(Context context) { + mAuthDescs = AccountManager.get(context).getAuthenticatorTypes(); + for (int i = 0; i < mAuthDescs.length; i++) { + mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]); + } + } + + public void onAccountsUpdated(Context context, Account[] accounts) { + if (accounts == null) { + accounts = AccountManager.get(context).getAccounts(); + } + mEnabledAccountTypes.clear(); + mAccTypeIconCache.clear(); + for (Account account: accounts) { + if (!mEnabledAccountTypes.contains(account.type)) { + mEnabledAccountTypes.add(account.type); + } + } + } + + public boolean containsAccountType(String accountType) { + return mTypeToAuthDescription.containsKey(accountType); + } + + public AuthenticatorDescription getAccountTypeDescription(String accountType) { + return mTypeToAuthDescription.get(accountType); + } + + public boolean hasAccountPreferences(final String accountType) { + if (containsAccountType(accountType)) { + AuthenticatorDescription desc = getAccountTypeDescription(accountType); + if (desc != null && desc.accountPreferencesId != 0) { + return true; + } + } + return false; + } +} diff --git a/src/com/android/settings/accounts/ManageAccountsSettings.java b/src/com/android/settings/accounts/ManageAccountsSettings.java index 0177491..bb1ebdd 100644 --- a/src/com/android/settings/accounts/ManageAccountsSettings.java +++ b/src/com/android/settings/accounts/ManageAccountsSettings.java @@ -21,16 +21,22 @@ import android.accounts.AccountManager; import android.accounts.OnAccountsUpdateListener; import android.app.ActionBar; import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityManagerNative; import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; import android.content.SyncAdapterType; import android.content.SyncInfo; import android.content.SyncStatusInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; +import android.text.format.DateFormat; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -44,16 +50,22 @@ import android.widget.Switch; import android.widget.TextView; import com.android.settings.AccountPreference; -import com.android.settings.DialogCreatable; import com.android.settings.R; +import com.android.settings.Settings; import java.util.ArrayList; +import java.util.Date; import java.util.HashSet; public class ManageAccountsSettings extends AccountPreferenceBase - implements OnAccountsUpdateListener, DialogCreatable { + implements OnAccountsUpdateListener { - private static final int MENU_ADD_ACCOUNT = Menu.FIRST; + private static final String ACCOUNT_KEY = "account"; // to pass to auth settings + public static final String KEY_ACCOUNT_TYPE = "account_type"; + public static final String KEY_ACCOUNT_LABEL = "account_label"; + + private static final int MENU_SYNC_NOW_ID = Menu.FIRST; + private static final int MENU_SYNC_CANCEL_ID = Menu.FIRST + 1; private static final int REQUEST_SHOW_SYNC_SETTINGS = 1; @@ -61,12 +73,19 @@ public class ManageAccountsSettings extends AccountPreferenceBase private TextView mErrorInfoView; private SettingsDialogFragment mDialogFragment; - private Switch mAutoSyncSwitch; + // If an account type is set, then show only accounts of that type + private String mAccountType; + // Temporary hack, to deal with backward compatibility + private Account mFirstAccount; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); + Bundle args = getArguments(); + if (args != null && args.containsKey(KEY_ACCOUNT_TYPE)) { + mAccountType = args.getString(KEY_ACCOUNT_TYPE); + } addPreferencesFromResource(R.xml.manage_accounts_settings); setHasOptionsMenu(true); } @@ -76,12 +95,6 @@ public class ManageAccountsSettings extends AccountPreferenceBase super.onStart(); Activity activity = getActivity(); AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, true); - activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, - ActionBar.DISPLAY_SHOW_CUSTOM); - activity.getActionBar().setCustomView(mAutoSyncSwitch, new ActionBar.LayoutParams( - ActionBar.LayoutParams.WRAP_CONTENT, - ActionBar.LayoutParams.WRAP_CONTENT, - Gravity.CENTER_VERTICAL | Gravity.RIGHT)); } @Override @@ -101,23 +114,12 @@ public class ManageAccountsSettings extends AccountPreferenceBase mErrorInfoView = (TextView)view.findViewById(R.id.sync_settings_error_info); mErrorInfoView.setVisibility(View.GONE); - mAutoSyncSwitch = new Switch(activity); - - // TODO Where to put the switch in tablet multipane layout? - final int padding = activity.getResources().getDimensionPixelSize( - R.dimen.action_bar_switch_padding); - mAutoSyncSwitch.setPadding(0, 0, padding, 0); - mAutoSyncSwitch.setChecked(ContentResolver.getMasterSyncAutomatically()); - mAutoSyncSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - ContentResolver.setMasterSyncAutomatically(isChecked); - onSyncStateUpdated(); - } - }); - mAuthorities = activity.getIntent().getStringArrayExtra(AUTHORITIES_FILTER_KEY); + Bundle args = getArguments(); + if (args != null && args.containsKey(KEY_ACCOUNT_LABEL)) { + getActivity().setTitle(args.getString(KEY_ACCOUNT_LABEL)); + } updateAuthDescriptions(); } @@ -150,29 +152,60 @@ public class ManageAccountsSettings extends AccountPreferenceBase } @Override - public void showDialog(int dialogId) { - if (mDialogFragment != null) { - Log.e(TAG, "Old dialog fragment not null!"); - } - mDialogFragment = new SettingsDialogFragment(this, dialogId); - mDialogFragment.show(getActivity().getFragmentManager(), Integer.toString(dialogId)); + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0, + getString(R.string.sync_menu_sync_now)) + .setIcon(R.drawable.ic_menu_refresh_holo_dark); + MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0, + getString(R.string.sync_menu_sync_cancel)) + .setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel); + super.onCreateOptionsMenu(menu, inflater); } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - MenuItem addAccountItem = menu.add(0, MENU_ADD_ACCOUNT, 0, R.string.add_account_label); - addAccountItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM - | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + boolean syncActive = ContentResolver.getCurrentSync() != null; + menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive && mFirstAccount != null); + menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive && mFirstAccount != null); } @Override public boolean onOptionsItemSelected(MenuItem item) { - final int itemId = item.getItemId(); - if (itemId == MENU_ADD_ACCOUNT) { - onAddAccountClicked(); + switch (item.getItemId()) { + case MENU_SYNC_NOW_ID: + requestOrCancelSyncForAccounts(true); + return true; + case MENU_SYNC_CANCEL_ID: + requestOrCancelSyncForAccounts(false); return true; - } else { - return super.onOptionsItemSelected(item); + } + return super.onOptionsItemSelected(item); + } + + private void requestOrCancelSyncForAccounts(boolean sync) { + SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes(); + Bundle extras = new Bundle(); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + int count = getPreferenceScreen().getPreferenceCount(); + // For each account + for (int i = 0; i < count; i++) { + Preference pref = getPreferenceScreen().getPreference(i); + if (pref instanceof AccountPreference) { + Account account = ((AccountPreference) pref).getAccount(); + // For all available sync authorities, sync those that are enabled for the account + for (int j = 0; j < syncAdapters.length; j++) { + SyncAdapterType sa = syncAdapters[j]; + if (syncAdapters[j].accountType.equals(mAccountType) + && ContentResolver.getSyncAutomatically(account, sa.authority)) { + if (sync) { + ContentResolver.requestSync(account, sa.authority, extras); + } else { + ContentResolver.cancelSync(account, sa.authority); + } + } + } + } } } @@ -180,15 +213,12 @@ public class ManageAccountsSettings extends AccountPreferenceBase protected void onSyncStateUpdated() { // Catch any delayed delivery of update messages if (getActivity() == null) return; - // Set background connection state - if (mAutoSyncSwitch != null) { - mAutoSyncSwitch.setChecked(ContentResolver.getMasterSyncAutomatically()); - } // iterate over all the preferences, setting the state properly for each SyncInfo currentSync = ContentResolver.getCurrentSync(); boolean anySyncFailed = false; // true if sync on any account failed + Date date = new Date(); // only track userfacing sync adapters when deciding if account is synced or not final SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes(); @@ -208,8 +238,10 @@ public class ManageAccountsSettings extends AccountPreferenceBase AccountPreference accountPref = (AccountPreference) pref; Account account = accountPref.getAccount(); int syncCount = 0; + long lastSuccessTime = 0; boolean syncIsFailing = false; final ArrayList<String> authorities = accountPref.getAuthorities(); + boolean syncingNow = false; if (authorities != null) { for (String authority : authorities) { SyncStatusInfo status = ContentResolver.getSyncStatus(account, authority); @@ -230,6 +262,10 @@ public class ManageAccountsSettings extends AccountPreferenceBase syncIsFailing = true; anySyncFailed = true; } + syncingNow |= activelySyncing; + if (status != null && lastSuccessTime < status.lastSuccessTime) { + lastSuccessTime = status.lastSuccessTime; + } syncCount += syncEnabled && userFacing.contains(authority) ? 1 : 0; } } else { @@ -237,15 +273,26 @@ public class ManageAccountsSettings extends AccountPreferenceBase Log.v(TAG, "no syncadapters found for " + account); } } - int syncStatus = AccountPreference.SYNC_DISABLED; if (syncIsFailing) { - syncStatus = AccountPreference.SYNC_ERROR; + accountPref.setSyncStatus(AccountPreference.SYNC_ERROR, true); } else if (syncCount == 0) { - syncStatus = AccountPreference.SYNC_DISABLED; + accountPref.setSyncStatus(AccountPreference.SYNC_DISABLED, true); } else if (syncCount > 0) { - syncStatus = AccountPreference.SYNC_ENABLED; + if (syncingNow) { + accountPref.setSyncStatus(AccountPreference.SYNC_IN_PROGRESS, true); + } else { + accountPref.setSyncStatus(AccountPreference.SYNC_ENABLED, true); + if (lastSuccessTime > 0) { + accountPref.setSyncStatus(AccountPreference.SYNC_ENABLED, false); + date.setTime(lastSuccessTime); + final String timeString = formatSyncDate(date); + accountPref.setSummary(getResources().getString( + R.string.last_synced, timeString)); + } + } + } else { + accountPref.setSyncStatus(AccountPreference.SYNC_DISABLED, true); } - accountPref.setSyncStatus(syncStatus); } mErrorInfoView.setVisibility(anySyncFailed ? View.VISIBLE : View.GONE); @@ -255,8 +302,12 @@ public class ManageAccountsSettings extends AccountPreferenceBase public void onAccountsUpdated(Account[] accounts) { if (getActivity() == null) return; getPreferenceScreen().removeAll(); + mFirstAccount = null; + addPreferencesFromResource(R.xml.manage_accounts_settings); for (int i = 0, n = accounts.length; i < n; i++) { final Account account = accounts[i]; + // If an account type is specified for this screen, skip other types + if (mAccountType != null && !account.type.equals(mAccountType)) continue; final ArrayList<String> auths = getAuthoritiesForAccountType(account.type); boolean showAccount = true; @@ -273,26 +324,53 @@ public class ManageAccountsSettings extends AccountPreferenceBase if (showAccount) { final Drawable icon = getDrawableForType(account.type); final AccountPreference preference = - new AccountPreference(getActivity(), account, icon, auths); + new AccountPreference(getActivity(), account, icon, auths, false); getPreferenceScreen().addPreference(preference); + if (mFirstAccount == null) { + mFirstAccount = account; + } } } + if (mAccountType != null && mFirstAccount != null) { + addAuthenticatorSettings(); + } onSyncStateUpdated(); } + private void addAuthenticatorSettings() { + PreferenceScreen prefs = addPreferencesForType(mAccountType, getPreferenceScreen()); + if (prefs != null) { + updatePreferenceIntents(prefs); + } + } + + private void updatePreferenceIntents(PreferenceScreen prefs) { + PackageManager pm = getActivity().getPackageManager(); + for (int i = 0; i < prefs.getPreferenceCount();) { + Intent intent = prefs.getPreference(i).getIntent(); + if (intent != null) { + ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + if (ri == null) { + prefs.removePreference(prefs.getPreference(i)); + continue; + } else { + intent.putExtra(ACCOUNT_KEY, mFirstAccount); + intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); + } + } + i++; + } + } + @Override protected void onAuthDescriptionsUpdated() { // Update account icons for all account preference items for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) { - AccountPreference pref = (AccountPreference) getPreferenceScreen().getPreference(i); - pref.setProviderIcon(getDrawableForType(pref.getAccount().type)); - pref.setSummary(getLabelForType(pref.getAccount().type)); + Preference pref = getPreferenceScreen().getPreference(i); + if (pref instanceof AccountPreference) { + AccountPreference accPref = (AccountPreference) pref; + accPref.setSummary(getLabelForType(accPref.getAccount().type)); + } } } - - public void onAddAccountClicked() { - Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); - intent.putExtra(AUTHORITIES_FILTER_KEY, mAuthorities); - startActivity(intent); - } } diff --git a/src/com/android/settings/accounts/SyncSettings.java b/src/com/android/settings/accounts/SyncSettings.java new file mode 100644 index 0000000..20c296a --- /dev/null +++ b/src/com/android/settings/accounts/SyncSettings.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2008 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.accounts; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.OnAccountsUpdateListener; +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.PreferenceScreen; +import android.util.Log; + +import com.android.settings.AccountPreference; +import com.android.settings.DialogCreatable; +import com.android.settings.R; + +import java.util.ArrayList; + +public class SyncSettings extends AccountPreferenceBase + implements OnAccountsUpdateListener, DialogCreatable { + + private static final String KEY_SYNC_SWITCH = "sync_switch"; + + private String[] mAuthorities; + + private SettingsDialogFragment mDialogFragment; + private CheckBoxPreference mAutoSyncPreference; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + addPreferencesFromResource(R.xml.sync_settings); + mAutoSyncPreference = + (CheckBoxPreference) getPreferenceScreen().findPreference(KEY_SYNC_SWITCH); + mAutoSyncPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + ContentResolver.setMasterSyncAutomatically((Boolean) newValue); + return true; + } + }); + + setHasOptionsMenu(true); + } + + @Override + public void onStart() { + super.onStart(); + Activity activity = getActivity(); + AccountManager.get(activity).addOnAccountsUpdatedListener(this, null, true); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Activity activity = getActivity(); + mAutoSyncPreference.setChecked(ContentResolver.getMasterSyncAutomatically()); + mAuthorities = activity.getIntent().getStringArrayExtra(AUTHORITIES_FILTER_KEY); + + updateAuthDescriptions(); + } + + @Override + public void onStop() { + super.onStop(); + final Activity activity = getActivity(); + AccountManager.get(activity).removeOnAccountsUpdatedListener(this); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) { + if (preference instanceof AccountPreference) { + startAccountSettings((AccountPreference) preference); + } else { + return false; + } + return true; + } + + private void startAccountSettings(AccountPreference acctPref) { + Intent intent = new Intent("android.settings.ACCOUNT_SYNC_SETTINGS"); + intent.putExtra(AccountSyncSettings.ACCOUNT_KEY, acctPref.getAccount()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finish(); + } + + @Override + public void showDialog(int dialogId) { + if (mDialogFragment != null) { + Log.e(TAG, "Old dialog fragment not null!"); + } + mDialogFragment = new SettingsDialogFragment(this, dialogId); + mDialogFragment.show(getActivity().getFragmentManager(), Integer.toString(dialogId)); + } + + private void removeAccountPreferences() { + PreferenceScreen parent = getPreferenceScreen(); + for (int i = 0; i < parent.getPreferenceCount(); ) { + if (parent.getPreference(i) instanceof AccountPreference) { + parent.removePreference(parent.getPreference(i)); + } else { + i++; + } + } + } + + @Override + public void onAccountsUpdated(Account[] accounts) { + if (getActivity() == null) return; + + removeAccountPreferences(); + for (int i = 0, n = accounts.length; i < n; i++) { + final Account account = accounts[i]; + final ArrayList<String> auths = getAuthoritiesForAccountType(account.type); + + boolean showAccount = true; + if (mAuthorities != null && auths != null) { + showAccount = false; + for (String requestedAuthority : mAuthorities) { + if (auths.contains(requestedAuthority)) { + showAccount = true; + break; + } + } + } + + if (showAccount) { + final Drawable icon = getDrawableForType(account.type); + final AccountPreference preference = + new AccountPreference(getActivity(), account, icon, auths, true); + getPreferenceScreen().addPreference(preference); + preference.setSummary(getLabelForType(account.type)); + } + } + onSyncStateUpdated(); + } + + @Override + protected void onAuthDescriptionsUpdated() { + // Update account icons for all account preference items + for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) { + Preference pref = getPreferenceScreen().getPreference(i); + if (pref instanceof AccountPreference) { + AccountPreference accPref = (AccountPreference) + getPreferenceScreen().getPreference(i); + accPref.setIcon(getDrawableForType(accPref.getAccount().type)); + accPref.setSummary(getLabelForType(accPref.getAccount().type)); + } + } + } +} diff --git a/src/com/android/settings/accounts/SyncSettingsActivity.java b/src/com/android/settings/accounts/SyncSettingsActivity.java new file mode 100644 index 0000000..ed0089e --- /dev/null +++ b/src/com/android/settings/accounts/SyncSettingsActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2012 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.accounts; + +import android.content.Intent; +import android.preference.PreferenceActivity; + +/** + * Launcher activity for the SyncSettings fragment. + * + */ +public class SyncSettingsActivity extends PreferenceActivity { + @Override + public Intent getIntent() { + Intent modIntent = new Intent(super.getIntent()); + modIntent.putExtra(EXTRA_SHOW_FRAGMENT, SyncSettings.class.getName()); + modIntent.putExtra(EXTRA_NO_HEADERS, true); + return modIntent; + } +}
\ No newline at end of file diff --git a/src/com/android/settings/applications/AppViewHolder.java b/src/com/android/settings/applications/AppViewHolder.java new file mode 100644 index 0000000..d23f187 --- /dev/null +++ b/src/com/android/settings/applications/AppViewHolder.java @@ -0,0 +1,64 @@ +package com.android.settings.applications; + +import com.android.settings.R; + +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +// View Holder used when displaying views +public class AppViewHolder { + public ApplicationsState.AppEntry entry; + public View rootView; + public TextView appName; + public ImageView appIcon; + public TextView appSize; + public TextView disabled; + public CheckBox checkBox; + + static public AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView) { + if (convertView == null) { + convertView = inflater.inflate(R.layout.manage_applications_item, null); + + // Creates a ViewHolder and store references to the two children views + // we want to bind data to. + AppViewHolder holder = new AppViewHolder(); + holder.rootView = convertView; + holder.appName = (TextView) convertView.findViewById(R.id.app_name); + holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon); + holder.appSize = (TextView) convertView.findViewById(R.id.app_size); + holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled); + holder.checkBox = (CheckBox) convertView.findViewById(R.id.app_on_sdcard); + convertView.setTag(holder); + return holder; + } else { + // Get the ViewHolder back to get fast access to the TextView + // and the ImageView. + 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: + appSize.setText(entry.internalSizeStr); + break; + case ManageApplications.SIZE_EXTERNAL: + appSize.setText(entry.externalSizeStr); + break; + default: + appSize.setText(entry.sizeStr); + break; + } + } else if (entry.size == ApplicationsState.SIZE_INVALID) { + appSize.setText(invalidSizeStr); + } + } +}
\ No newline at end of file diff --git a/src/com/android/settings/applications/ApplicationsState.java b/src/com/android/settings/applications/ApplicationsState.java index 799b34a..0a5c26e 100644 --- a/src/com/android/settings/applications/ApplicationsState.java +++ b/src/com/android/settings/applications/ApplicationsState.java @@ -227,24 +227,21 @@ public class ApplicationsState { PackageIntentReceiver mPackageIntentReceiver; boolean mResumed; - Callbacks mCurCallbacks; - // Information about all applications. Synchronize on mAppEntries + // Information about all applications. Synchronize on mEntriesMap // to protect access to these. + final ArrayList<Session> mSessions = new ArrayList<Session>(); + final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>(); final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); final HashMap<String, AppEntry> mEntriesMap = new HashMap<String, AppEntry>(); final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>(); List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>(); long mCurId = 1; String mCurComputingSizePkg; + boolean mSessionsChanged; - // Rebuilding of app list. Synchronized on mRebuildSync. - final Object mRebuildSync = new Object(); - boolean mRebuildRequested; - boolean mRebuildAsync; - AppFilter mRebuildFilter; - Comparator<AppEntry> mRebuildComparator; - ArrayList<AppEntry> mRebuildResult; + // Temporary for dispatching session callbacks. Only touched by main thread. + final ArrayList<Session> mActiveSessions = new ArrayList<Session>(); /** * Receives notifications when applications are added/removed. @@ -262,6 +259,9 @@ public class ApplicationsState { sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); mContext.registerReceiver(this, sdFilter); } + void unregisterReceiver() { + mContext.unregisterReceiver(this); + } @Override public void onReceive(Context context, Intent intent) { String actionStr = intent.getAction(); @@ -300,6 +300,21 @@ public class ApplicationsState { } } + void rebuildActiveSessions() { + synchronized (mEntriesMap) { + if (!mSessionsChanged) { + return; + } + mActiveSessions.clear(); + for (int i=0; i<mSessions.size(); i++) { + Session s = mSessions.get(i); + if (s.mResumed) { + mActiveSessions.add(s); + } + } + } + } + class MainHandler extends Handler { static final int MSG_REBUILD_COMPLETE = 1; static final int MSG_PACKAGE_LIST_CHANGED = 2; @@ -310,35 +325,39 @@ public class ApplicationsState { @Override public void handleMessage(Message msg) { + rebuildActiveSessions(); switch (msg.what) { case MSG_REBUILD_COMPLETE: { - if (mCurCallbacks != null) { - mCurCallbacks.onRebuildComplete((ArrayList<AppEntry>)msg.obj); + Session s = (Session)msg.obj; + if (mActiveSessions.contains(s)) { + s.mCallbacks.onRebuildComplete(s.mLastAppList); } } break; case MSG_PACKAGE_LIST_CHANGED: { - if (mCurCallbacks != null) { - mCurCallbacks.onPackageListChanged(); + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onPackageListChanged(); } } break; case MSG_PACKAGE_ICON_CHANGED: { - if (mCurCallbacks != null) { - mCurCallbacks.onPackageIconChanged(); + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onPackageIconChanged(); } } break; case MSG_PACKAGE_SIZE_CHANGED: { - if (mCurCallbacks != null) { - mCurCallbacks.onPackageSizeChanged((String)msg.obj); + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onPackageSizeChanged( + (String)msg.obj); } } break; case MSG_ALL_SIZES_COMPUTED: { - if (mCurCallbacks != null) { - mCurCallbacks.onAllSizesComputed(); + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onAllSizesComputed(); } } break; case MSG_RUNNING_STATE_CHANGED: { - if (mCurCallbacks != null) { - mCurCallbacks.onRunningStateChanged(msg.arg1 != 0); + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onRunningStateChanged( + msg.arg1 != 0); } } break; } @@ -391,157 +410,226 @@ public class ApplicationsState { } } - void resume(Callbacks callbacks) { - if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); - synchronized (mEntriesMap) { - mCurCallbacks = callbacks; - mResumed = true; - if (mPackageIntentReceiver == null) { - mPackageIntentReceiver = new PackageIntentReceiver(); - mPackageIntentReceiver.registerReceiver(); - } - mApplications = mPm.getInstalledApplications( - PackageManager.GET_UNINSTALLED_PACKAGES | - PackageManager.GET_DISABLED_COMPONENTS); - if (mApplications == null) { - mApplications = new ArrayList<ApplicationInfo>(); - } - - if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { - // If an interesting part of the configuration has changed, we - // should completely reload the app entries. - mEntriesMap.clear(); - mAppEntries.clear(); - } else { - for (int i=0; i<mAppEntries.size(); i++) { - mAppEntries.get(i).sizeStale = true; - } - } + public class Session { + final Callbacks mCallbacks; + boolean mResumed; - for (int i=0; i<mApplications.size(); i++) { - final ApplicationInfo info = mApplications.get(i); - // Need to trim out any applications that are disabled by - // something different than the user. - if (!info.enabled && info.enabledSetting - != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { - mApplications.remove(i); - i--; - continue; - } - final AppEntry entry = mEntriesMap.get(info.packageName); - if (entry != null) { - entry.info = info; + // Rebuilding of app list. Synchronized on mRebuildSync. + final Object mRebuildSync = new Object(); + boolean mRebuildRequested; + boolean mRebuildAsync; + AppFilter mRebuildFilter; + Comparator<AppEntry> mRebuildComparator; + ArrayList<AppEntry> mRebuildResult; + ArrayList<AppEntry> mLastAppList; + + Session(Callbacks callbacks) { + mCallbacks = callbacks; + } + + public void resume() { + if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); + synchronized (mEntriesMap) { + if (!mResumed) { + mResumed = true; + mSessionsChanged = true; + doResumeIfNeededLocked(); } } - mCurComputingSizePkg = null; - if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { - mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); - } if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock"); } - } - void pause() { - if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock..."); - synchronized (mEntriesMap) { - mCurCallbacks = null; - mResumed = false; - if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock"); + public void pause() { + if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock..."); + synchronized (mEntriesMap) { + if (mResumed) { + mResumed = false; + mSessionsChanged = true; + mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this); + doPauseIfNeededLocked(); + } + if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock"); + } } - } - // Creates a new list of app entries with the given filter and comparator. - ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) { - synchronized (mRebuildSync) { - mRebuildRequested = true; - mRebuildAsync = false; - mRebuildFilter = filter; - mRebuildComparator = comparator; - mRebuildResult = null; - if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) { - mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_REBUILD_LIST); - } - - // We will wait for .25s for the list to be built. - long waitend = SystemClock.uptimeMillis()+250; - - while (mRebuildResult == null) { - long now = SystemClock.uptimeMillis(); - if (now >= waitend) { - break; + // Creates a new list of app entries with the given filter and comparator. + ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) { + synchronized (mRebuildSync) { + synchronized (mEntriesMap) { + mRebuildingSessions.add(this); + mRebuildRequested = true; + mRebuildAsync = false; + mRebuildFilter = filter; + mRebuildComparator = comparator; + mRebuildResult = null; + if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) { + Message msg = mBackgroundHandler.obtainMessage( + BackgroundHandler.MSG_REBUILD_LIST); + mBackgroundHandler.sendMessage(msg); + } } - try { - mRebuildSync.wait(waitend - now); - } catch (InterruptedException e) { + + // We will wait for .25s for the list to be built. + long waitend = SystemClock.uptimeMillis()+250; + + while (mRebuildResult == null) { + long now = SystemClock.uptimeMillis(); + if (now >= waitend) { + break; + } + try { + mRebuildSync.wait(waitend - now); + } catch (InterruptedException e) { + } } - } - mRebuildAsync = true; + mRebuildAsync = true; - return mRebuildResult; + return mRebuildResult; + } } - } - void handleRebuildList() { - AppFilter filter; - Comparator<AppEntry> comparator; - synchronized (mRebuildSync) { - if (!mRebuildRequested) { - return; + void handleRebuildList() { + AppFilter filter; + Comparator<AppEntry> comparator; + synchronized (mRebuildSync) { + if (!mRebuildRequested) { + return; + } + + filter = mRebuildFilter; + comparator = mRebuildComparator; + mRebuildRequested = false; + mRebuildFilter = null; + mRebuildComparator = null; } - filter = mRebuildFilter; - comparator = mRebuildComparator; - mRebuildRequested = false; - mRebuildFilter = null; - mRebuildComparator = null; - } + Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); - Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); + if (filter != null) { + filter.init(); + } + + List<ApplicationInfo> apps; + synchronized (mEntriesMap) { + apps = new ArrayList<ApplicationInfo>(mApplications); + } - if (filter != null) { - filter.init(); + ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>(); + if (DEBUG) Log.i(TAG, "Rebuilding..."); + for (int i=0; i<apps.size(); i++) { + ApplicationInfo info = apps.get(i); + if (filter == null || filter.filterApp(info)) { + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock"); + AppEntry entry = getEntryLocked(info); + entry.ensureLabel(mContext); + if (DEBUG) Log.i(TAG, "Using " + info.packageName + ": " + entry); + filteredApps.add(entry); + if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock"); + } + } + } + + Collections.sort(filteredApps, comparator); + + synchronized (mRebuildSync) { + if (!mRebuildRequested) { + mLastAppList = filteredApps; + if (!mRebuildAsync) { + mRebuildResult = filteredApps; + mRebuildSync.notifyAll(); + } else { + if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) { + Message msg = mMainHandler.obtainMessage( + MainHandler.MSG_REBUILD_COMPLETE, this); + mMainHandler.sendMessage(msg); + } + } + } + } + + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } - - List<ApplicationInfo> apps; + + public void release() { + pause(); + synchronized (mEntriesMap) { + mSessions.remove(this); + } + } + } + + public Session newSession(Callbacks callbacks) { + Session s = new Session(callbacks); synchronized (mEntriesMap) { - apps = new ArrayList<ApplicationInfo>(mApplications); + mSessions.add(s); } + return s; + } - ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>(); - if (DEBUG) Log.i(TAG, "Rebuilding..."); - for (int i=0; i<apps.size(); i++) { - ApplicationInfo info = apps.get(i); - if (filter == null || filter.filterApp(info)) { - synchronized (mEntriesMap) { - if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock"); - AppEntry entry = getEntryLocked(info); - entry.ensureLabel(mContext); - if (DEBUG) Log.i(TAG, "Using " + info.packageName + ": " + entry); - filteredApps.add(entry); - if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock"); - } + void doResumeIfNeededLocked() { + if (mResumed) { + return; + } + mResumed = true; + if (mPackageIntentReceiver == null) { + mPackageIntentReceiver = new PackageIntentReceiver(); + mPackageIntentReceiver.registerReceiver(); + } + mApplications = mPm.getInstalledApplications( + PackageManager.GET_UNINSTALLED_PACKAGES | + PackageManager.GET_DISABLED_COMPONENTS); + if (mApplications == null) { + mApplications = new ArrayList<ApplicationInfo>(); + } + + if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { + // If an interesting part of the configuration has changed, we + // should completely reload the app entries. + mEntriesMap.clear(); + mAppEntries.clear(); + } else { + for (int i=0; i<mAppEntries.size(); i++) { + mAppEntries.get(i).sizeStale = true; } } - Collections.sort(filteredApps, comparator); - - synchronized (mRebuildSync) { - if (!mRebuildRequested) { - if (!mRebuildAsync) { - mRebuildResult = filteredApps; - mRebuildSync.notifyAll(); - } else { - if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE)) { - Message msg = mMainHandler.obtainMessage( - MainHandler.MSG_REBUILD_COMPLETE, filteredApps); - mMainHandler.sendMessage(msg); - } - } + for (int i=0; i<mApplications.size(); i++) { + final ApplicationInfo info = mApplications.get(i); + // Need to trim out any applications that are disabled by + // something different than the user. + if (!info.enabled && info.enabledSetting + != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { + mApplications.remove(i); + i--; + continue; + } + final AppEntry entry = mEntriesMap.get(info.packageName); + if (entry != null) { + entry.info = info; } } + mCurComputingSizePkg = null; + if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { + mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); + } + } - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + void doPauseIfNeededLocked() { + if (!mResumed) { + return; + } + for (int i=0; i<mSessions.size(); i++) { + if (mSessions.get(i).mResumed) { + return; + } + } + mResumed = false; + if (mPackageIntentReceiver != null) { + mPackageIntentReceiver.unregisterReceiver(); + mPackageIntentReceiver = null; + } } AppEntry getEntry(String packageName) { @@ -772,7 +860,18 @@ public class ApplicationsState { @Override public void handleMessage(Message msg) { // Always try rebuilding list first thing, if needed. - handleRebuildList(); + ArrayList<Session> rebuildingSessions = null; + synchronized (mEntriesMap) { + if (mRebuildingSessions.size() > 0) { + rebuildingSessions = new ArrayList<Session>(mRebuildingSessions); + mRebuildingSessions.clear(); + } + } + if (rebuildingSessions != null) { + for (int i=0; i<rebuildingSessions.size(); i++) { + rebuildingSessions.get(i).handleRebuildList(); + } + } switch (msg.what) { case MSG_REBUILD_LIST: { diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index faa531a..c2e358c 100644 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -17,6 +17,7 @@ package com.android.settings.applications; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settings.applications.ApplicationsState.AppEntry; import android.app.Activity; @@ -25,8 +26,12 @@ import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; +import android.app.INotificationManager; +import android.app.NotificationManager; import android.app.admin.DevicePolicyManager; +import android.appwidget.AppWidgetManager; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -49,13 +54,15 @@ import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.preference.PreferenceActivity; +import android.text.SpannableString; +import android.text.TextUtils; import android.text.format.Formatter; +import android.text.style.BulletSpan; import android.util.Log; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; -import android.content.ComponentName; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -65,6 +72,7 @@ import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.Switch; import android.widget.TextView; /** @@ -87,8 +95,10 @@ public class InstalledAppDetails extends Fragment private PackageManager mPm; private IUsbManager mUsbManager; + private AppWidgetManager mAppWidgetManager; private DevicePolicyManager mDpm; private ApplicationsState mState; + private ApplicationsState.Session mSession; private ApplicationsState.AppEntry mAppEntry; private PackageInfo mPackageInfo; private CanBeOnSdCardChecker mCanBeOnSdCardChecker; @@ -115,7 +125,8 @@ public class InstalledAppDetails extends Fragment private Button mForceStopButton; private Button mClearDataButton; private Button mMoveAppButton; - + private CompoundButton mNotificationSwitch; + private PackageMoveObserver mPackageMoveObserver; private boolean mHaveSizes = false; @@ -150,6 +161,7 @@ public class InstalledAppDetails extends Fragment private static final int DLG_FORCE_STOP = DLG_BASE + 5; private static final int DLG_MOVE_FAILED = DLG_BASE + 6; private static final int DLG_DISABLE = DLG_BASE + 7; + private static final int DLG_DISABLE_NOTIFICATIONS = DLG_BASE + 8; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { @@ -268,6 +280,16 @@ public class InstalledAppDetails extends Fragment } } + private boolean isThisASystemPackage() { + try { + PackageInfo sys = mPm.getPackageInfo("android", PackageManager.GET_SIGNATURES); + return (mPackageInfo != null && mPackageInfo.signatures != null && + sys.signatures[0].equals(mPackageInfo.signatures[0])); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + private void initUninstallButtons() { mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; boolean enabled = true; @@ -287,9 +309,7 @@ public class InstalledAppDetails extends Fragment intent.addCategory(Intent.CATEGORY_HOME); intent.setPackage(mAppEntry.info.packageName); List<ResolveInfo> homes = mPm.queryIntentActivities(intent, 0); - if ((homes != null && homes.size() > 0) || - (mPackageInfo != null && mPackageInfo.signatures != null && - sys.signatures[0].equals(mPackageInfo.signatures[0]))) { + if ((homes != null && homes.size() > 0) || isThisASystemPackage()) { // Disable button for core system applications. mUninstallButton.setText(R.string.disable_text); } else if (mAppEntry.info.enabled) { @@ -319,15 +339,35 @@ public class InstalledAppDetails extends Fragment } } + private void initNotificationButton() { + INotificationManager nm = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + boolean enabled = true; // default on + try { + enabled = nm.areNotificationsEnabledForPackage(mAppEntry.info.packageName); + } catch (android.os.RemoteException ex) { + // this does not bode well + } + mNotificationSwitch.setChecked(enabled); + if (isThisASystemPackage()) { + mNotificationSwitch.setEnabled(false); + } else { + mNotificationSwitch.setEnabled(true); + mNotificationSwitch.setOnCheckedChangeListener(this); + } + } + /** Called when the activity is first created. */ @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); mState = ApplicationsState.getInstance(getActivity().getApplication()); + mSession = mState.newSession(this); mPm = getActivity().getPackageManager(); IBinder b = ServiceManager.getService(Context.USB_SERVICE); mUsbManager = IUsbManager.Stub.asInterface(b); + mAppWidgetManager = AppWidgetManager.getInstance(getActivity()); mDpm = (DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE); mCanBeOnSdCardChecker = new CanBeOnSdCardChecker(); @@ -368,6 +408,8 @@ public class InstalledAppDetails extends Fragment mScreenCompatSection = view.findViewById(R.id.screen_compatibility_section); mAskCompatibilityCB = (CheckBox)view.findViewById(R.id.ask_compatibility_cb); mEnableCompatibilityCB = (CheckBox)view.findViewById(R.id.enable_compatibility_cb); + + mNotificationSwitch = (CompoundButton) view.findViewById(R.id.notification_switch); return view; } @@ -397,7 +439,7 @@ public class InstalledAppDetails extends Fragment public void onResume() { super.onResume(); - mState.resume(this); + mSession.resume(); if (!refreshUi()) { setIntentAndFinish(true, true); } @@ -406,7 +448,7 @@ public class InstalledAppDetails extends Fragment @Override public void onPause() { super.onPause(); - mState.pause(); + mSession.pause(); } @Override @@ -473,20 +515,55 @@ public class InstalledAppDetails extends Fragment // Intent list cannot be null. so pass empty list List<IntentFilter> intentList = new ArrayList<IntentFilter>(); mPm.getPreferredActivities(intentList, prefActList, packageName); - if(localLOGV) Log.i(TAG, "Have "+prefActList.size()+" number of activities in prefered list"); + if (localLOGV) + Log.i(TAG, "Have " + prefActList.size() + " number of activities in preferred list"); boolean hasUsbDefaults = false; try { hasUsbDefaults = mUsbManager.hasDefaults(packageName); } catch (RemoteException e) { Log.e(TAG, "mUsbManager.hasDefaults", e); } - TextView autoLaunchView = (TextView)mRootView.findViewById(R.id.auto_launch); - if (prefActList.size() <= 0 && !hasUsbDefaults) { - // Disable clear activities button - autoLaunchView.setText(R.string.auto_launch_disable_text); - mActivitiesButton.setEnabled(false); + boolean hasBindAppWidgetPermission = + mAppWidgetManager.hasBindAppWidgetPermission(mAppEntry.info.packageName); + + TextView autoLaunchTitleView = (TextView) mRootView.findViewById(R.id.auto_launch_title); + TextView autoLaunchView = (TextView) mRootView.findViewById(R.id.auto_launch); + boolean autoLaunchEnabled = prefActList.size() > 0 || hasUsbDefaults; + if (!autoLaunchEnabled && !hasBindAppWidgetPermission) { + resetLaunchDefaultsUi(autoLaunchTitleView, autoLaunchView); } else { - autoLaunchView.setText(R.string.auto_launch_enable_text); + boolean useBullets = hasBindAppWidgetPermission && autoLaunchEnabled; + + if (hasBindAppWidgetPermission) { + autoLaunchTitleView.setText(R.string.auto_launch_label_generic); + } else { + autoLaunchTitleView.setText(R.string.auto_launch_label); + } + + CharSequence text = null; + int bulletIndent = getResources() + .getDimensionPixelSize(R.dimen.installed_app_details_bullet_offset); + if (autoLaunchEnabled) { + CharSequence autoLaunchEnableText = getText(R.string.auto_launch_enable_text); + SpannableString s = new SpannableString(autoLaunchEnableText); + if (useBullets) { + s.setSpan(new BulletSpan(bulletIndent), 0, autoLaunchEnableText.length(), 0); + } + text = (text == null) ? + TextUtils.concat(s, "\n") : TextUtils.concat(text, "\n", s, "\n"); + } + if (hasBindAppWidgetPermission) { + CharSequence alwaysAllowBindAppWidgetsText = + getText(R.string.always_allow_bind_appwidgets_text); + SpannableString s = new SpannableString(alwaysAllowBindAppWidgetsText); + if (useBullets) { + s.setSpan(new BulletSpan(bulletIndent), + 0, alwaysAllowBindAppWidgetsText.length(), 0); + } + text = (text == null) ? + TextUtils.concat(s, "\n") : TextUtils.concat(text, "\n", s, "\n"); + } + autoLaunchView.setText(text); mActivitiesButton.setEnabled(true); mActivitiesButton.setOnClickListener(this); } @@ -529,6 +606,13 @@ public class InstalledAppDetails extends Fragment return true; } + private void resetLaunchDefaultsUi(TextView title, TextView autoLaunchView) { + title.setText(R.string.auto_launch_label); + autoLaunchView.setText(R.string.auto_launch_disable_text); + // Disable clear activities button + mActivitiesButton.setEnabled(false); + } + private void setIntentAndFinish(boolean finish, boolean appChanged) { if(localLOGV) Log.i(TAG, "appChanged="+appChanged); Intent intent = new Intent(); @@ -614,6 +698,7 @@ public class InstalledAppDetails extends Fragment initUninstallButtons(); initDataButtons(); initMoveButton(); + initNotificationButton(); } else { mMoveAppButton.setText(R.string.moving); mMoveAppButton.setEnabled(false); @@ -780,6 +865,26 @@ public class InstalledAppDetails extends Fragment }) .setNegativeButton(R.string.dlg_cancel, null) .create(); + case DLG_DISABLE_NOTIFICATIONS: + return new AlertDialog.Builder(getActivity()) + .setTitle(getActivity().getText(R.string.app_disable_notifications_dlg_title)) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(getActivity().getText(R.string.app_disable_notifications_dlg_text)) + .setPositiveButton(R.string.dlg_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Disable the package's notifications + getOwner().setNotificationsEnabled(false); + } + }) + .setNegativeButton(R.string.dlg_cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Re-enable the checkbox + getOwner().mNotificationSwitch.setChecked(true); + } + }) + .create(); } throw new IllegalArgumentException("unknown id " + id); } @@ -834,7 +939,7 @@ public class InstalledAppDetails extends Fragment Activity.RESULT_CANCELED, null, null); } } - + static class DisableChanger extends AsyncTask<Object, Object, Object> { final PackageManager mPm; final WeakReference<InstalledAppDetails> mActivity; @@ -855,6 +960,18 @@ public class InstalledAppDetails extends Fragment } } + private void setNotificationsEnabled(boolean enabled) { + String packageName = mAppEntry.info.packageName; + INotificationManager nm = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + try { + final boolean enable = mNotificationSwitch.isChecked(); + nm.setNotificationsEnabledForPackage(packageName, enabled); + } catch (android.os.RemoteException ex) { + mNotificationSwitch.setChecked(!enabled); // revert + } + } + /* * Method implementing functionality of buttons clicked * @see android.view.View.OnClickListener#onClick(android.view.View) @@ -884,13 +1001,19 @@ public class InstalledAppDetails extends Fragment } catch (RemoteException e) { Log.e(TAG, "mUsbManager.clearDefaults", e); } - mActivitiesButton.setEnabled(false); + mAppWidgetManager.setBindAppWidgetPermission(packageName, false); + TextView autoLaunchTitleView = + (TextView) mRootView.findViewById(R.id.auto_launch_title); + TextView autoLaunchView = (TextView) mRootView.findViewById(R.id.auto_launch); + resetLaunchDefaultsUi(autoLaunchTitleView, autoLaunchView); } else if(v == mClearDataButton) { if (mAppEntry.info.manageSpaceActivityName != null) { - Intent intent = new Intent(Intent.ACTION_DEFAULT); - intent.setClassName(mAppEntry.info.packageName, - mAppEntry.info.manageSpaceActivityName); - startActivityForResult(intent, -1); + if (!Utils.isMonkeyRunning()) { + Intent intent = new Intent(Intent.ACTION_DEFAULT); + intent.setClassName(mAppEntry.info.packageName, + mAppEntry.info.manageSpaceActivityName); + startActivityForResult(intent, -1); + } } else { showDialogInner(DLG_CLEAR_DATA, 0); } @@ -925,6 +1048,12 @@ public class InstalledAppDetails extends Fragment } else if (buttonView == mEnableCompatibilityCB) { am.setPackageScreenCompatMode(packageName, isChecked ? ActivityManager.COMPAT_MODE_ENABLED : ActivityManager.COMPAT_MODE_DISABLED); + } else if (buttonView == mNotificationSwitch) { + if (!isChecked) { + showDialogInner(DLG_DISABLE_NOTIFICATIONS, 0); + } else { + setNotificationsEnabled(true); + } } } } diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java index 948ddb0..1cc9dcc 100644 --- a/src/com/android/settings/applications/ManageApplications.java +++ b/src/com/android/settings/applications/ManageApplications.java @@ -16,22 +16,37 @@ package com.android.settings.applications; -import static com.android.settings.Utils.prepareCustomPreferencesList; +import static android.net.NetworkPolicyManager.POLICY_NONE; +import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; import android.app.Activity; +import android.app.AlertDialog; import android.app.Fragment; +import android.app.INotificationManager; +import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.NetworkPolicyManager; +import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; +import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.StatFs; import android.preference.PreferenceActivity; +import android.preference.PreferenceFrameLayout; import android.provider.Settings; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.PagerTabStrip; +import android.support.v4.view.ViewPager; import android.text.format.Formatter; import android.util.Log; import android.view.LayoutInflater; @@ -41,27 +56,27 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; -import android.view.inputmethod.InputMethodManager; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; -import android.widget.CheckBox; import android.widget.Filter; import android.widget.Filterable; -import android.widget.ImageView; import android.widget.ListView; -import android.widget.TabHost; import android.widget.TextView; +import com.android.internal.app.IMediaContainerService; import com.android.internal.content.PackageHelper; import com.android.settings.R; import com.android.settings.Settings.RunningServicesActivity; import com.android.settings.Settings.StorageUseActivity; import com.android.settings.applications.ApplicationsState.AppEntry; +import com.android.settings.deviceinfo.StorageMeasurement; +import com.android.settings.Utils; import java.util.ArrayList; import java.util.Comparator; +import java.util.List; final class CanBeOnSdCardChecker { final IPackageManager mPm; @@ -86,8 +101,7 @@ final class CanBeOnSdCardChecker { if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { canBe = true; } else { - if ((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0 && - (info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { if (info.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL || info.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { canBe = true; @@ -105,6 +119,11 @@ final class CanBeOnSdCardChecker { } } +interface AppClickListener { + void onItemClick(ManageApplications.TabInfo tab, AdapterView<?> parent, + View view, int position, long id); +} + /** * Activity to pick an application that will be used to display installation information and * options to uninstall/delete user data for system applications. This activity @@ -112,14 +131,20 @@ final class CanBeOnSdCardChecker { * intent. */ public class ManageApplications extends Fragment implements - OnItemClickListener, - TabHost.TabContentFactory, TabHost.OnTabChangeListener { + AppClickListener, DialogInterface.OnClickListener, + DialogInterface.OnDismissListener { + static final String TAG = "ManageApplications"; static final boolean DEBUG = false; - + + private static final String EXTRA_SORT_ORDER = "sortOrder"; + private static final String EXTRA_SHOW_BACKGROUND = "showBackground"; + private static final String EXTRA_DEFAULT_LIST_TYPE = "defaultListType"; + private static final String EXTRA_RESET_DIALOG = "resetDialog"; + // attributes used as keys when passing values to InstalledAppDetails activity public static final String APP_CHG = "chg"; - + // constant value that can be used to check return code from sub activity. private static final int INSTALLED_APP_DETAILS = 1; @@ -139,16 +164,270 @@ public class ManageApplications extends Fragment implements public static final int SORT_ORDER_SIZE = MENU_OPTIONS_BASE + 5; public static final int SHOW_RUNNING_SERVICES = MENU_OPTIONS_BASE + 6; public static final int SHOW_BACKGROUND_PROCESSES = MENU_OPTIONS_BASE + 7; + public static final int RESET_APP_PREFERENCES = MENU_OPTIONS_BASE + 8; // sort order private int mSortOrder = SORT_ORDER_ALPHA; - // Filter value - private int mFilterApps = FILTER_APPS_THIRD_PARTY; private ApplicationsState mApplicationsState; - private ApplicationsAdapter mApplicationsAdapter; - + + public static class TabInfo implements OnItemClickListener { + public final ManageApplications mOwner; + public final ApplicationsState mApplicationsState; + public final CharSequence mLabel; + public final int mListType; + public final int mFilter; + public final AppClickListener mClickListener; + public final CharSequence mInvalidSizeStr; + public final CharSequence mComputingSizeStr; + private final Bundle mSavedInstanceState; + + public ApplicationsAdapter mApplications; + public LayoutInflater mInflater; + public View mRootView; + + private IMediaContainerService mContainerService; + + private View mLoadingContainer; + + private View mListContainer; + + // ListView used to display list + private ListView mListView; + // Custom view used to display running processes + private RunningProcessesView mRunningProcessesView; + + private LinearColorBar mColorBar; + private TextView mStorageChartLabel; + private TextView mUsedStorageText; + private TextView mFreeStorageText; + private long mFreeStorage = 0, mAppStorage = 0, mTotalStorage = 0; + private long mLastUsedStorage, mLastAppStorage, mLastFreeStorage; + + final Runnable mRunningProcessesAvail = new Runnable() { + public void run() { + handleRunningProcessesAvail(); + } + }; + + public TabInfo(ManageApplications owner, ApplicationsState apps, + CharSequence label, int listType, AppClickListener clickListener, + Bundle savedInstanceState) { + mOwner = owner; + mApplicationsState = apps; + mLabel = label; + mListType = listType; + switch (listType) { + case LIST_TYPE_DOWNLOADED: mFilter = FILTER_APPS_THIRD_PARTY; break; + case LIST_TYPE_SDCARD: mFilter = FILTER_APPS_SDCARD; break; + default: mFilter = FILTER_APPS_ALL; break; + } + mClickListener = clickListener; + mInvalidSizeStr = owner.getActivity().getText(R.string.invalid_size_value); + mComputingSizeStr = owner.getActivity().getText(R.string.computing_size); + mSavedInstanceState = savedInstanceState; + } + + public void setContainerService(IMediaContainerService containerService) { + mContainerService = containerService; + updateStorageUsage(); + } + + public View build(LayoutInflater inflater, ViewGroup contentParent, View contentChild) { + if (mRootView != null) { + return mRootView; + } + + mInflater = inflater; + mRootView = inflater.inflate(mListType == LIST_TYPE_RUNNING + ? R.layout.manage_applications_running + : R.layout.manage_applications_apps, null); + mLoadingContainer = mRootView.findViewById(R.id.loading_container); + mLoadingContainer.setVisibility(View.VISIBLE); + 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 = (ListView) 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); + mListView.setAdapter(mApplications); + mListView.setRecyclerListener(mApplications); + mColorBar = (LinearColorBar)mListContainer.findViewById(R.id.storage_color_bar); + mStorageChartLabel = (TextView)mListContainer.findViewById(R.id.storageChartLabel); + mUsedStorageText = (TextView)mListContainer.findViewById(R.id.usedStorageText); + mFreeStorageText = (TextView)mListContainer.findViewById(R.id.freeStorageText); + Utils.prepareCustomPreferencesList(contentParent, contentChild, mListView, false); + if (mFilter == FILTER_APPS_SDCARD) { + mStorageChartLabel.setText(mOwner.getActivity().getText( + R.string.sd_card_storage)); + } else { + mStorageChartLabel.setText(mOwner.getActivity().getText( + R.string.internal_storage)); + } + applyCurrentStorage(); + } + mRunningProcessesView = (RunningProcessesView)mRootView.findViewById( + R.id.running_processes); + if (mRunningProcessesView != null) { + mRunningProcessesView.doCreate(mSavedInstanceState); + } + + return mRootView; + } + + public void detachView() { + if (mRootView != null) { + ViewGroup group = (ViewGroup)mRootView.getParent(); + if (group != null) { + group.removeView(mRootView); + } + } + } + + public void resume(int sortOrder) { + if (mApplications != null) { + mApplications.resume(sortOrder); + } + if (mRunningProcessesView != null) { + boolean haveData = mRunningProcessesView.doResume(mOwner, mRunningProcessesAvail); + if (haveData) { + mRunningProcessesView.setVisibility(View.VISIBLE); + mLoadingContainer.setVisibility(View.INVISIBLE); + } else { + mLoadingContainer.setVisibility(View.VISIBLE); + } + } + } + + public void pause() { + if (mApplications != null) { + mApplications.pause(); + } + if (mRunningProcessesView != null) { + mRunningProcessesView.doPause(); + } + } + + void updateStorageUsage() { + // Make sure a callback didn't come at an inopportune time. + if (mOwner.getActivity() == null) return; + // Doesn't make sense for stuff that is not an app list. + if (mApplications == null) return; + + mFreeStorage = 0; + mAppStorage = 0; + mTotalStorage = 0; + + if (mFilter == FILTER_APPS_SDCARD) { + if (mContainerService != null) { + try { + final long[] stats = mContainerService.getFileSystemStats( + Environment.getExternalStorageDirectory().getPath()); + mTotalStorage = stats[0]; + mFreeStorage = stats[1]; + } catch (RemoteException e) { + Log.w(TAG, "Problem in container service", e); + } + } + + if (mApplications != null) { + final int N = mApplications.getCount(); + for (int i=0; i<N; i++) { + ApplicationsState.AppEntry ae = mApplications.getAppEntry(i); + mAppStorage += ae.externalCodeSize + ae.externalDataSize; + } + } + } else { + if (mContainerService != null) { + try { + final long[] stats = mContainerService.getFileSystemStats( + Environment.getDataDirectory().getPath()); + mTotalStorage = stats[0]; + mFreeStorage = stats[1]; + } catch (RemoteException e) { + Log.w(TAG, "Problem in container service", e); + } + } + + final boolean emulatedStorage = Environment.isExternalStorageEmulated(); + if (mApplications != null) { + final int N = mApplications.getCount(); + for (int i=0; i<N; i++) { + ApplicationsState.AppEntry ae = mApplications.getAppEntry(i); + mAppStorage += ae.codeSize + ae.dataSize; + if (emulatedStorage) { + mAppStorage += ae.externalCodeSize + ae.externalDataSize; + } + } + } + mFreeStorage += mApplicationsState.sumCacheSizes(); + } + + applyCurrentStorage(); + } + + void applyCurrentStorage() { + // If view hierarchy is not yet created, no views to update. + if (mRootView == null) { + return; + } + if (mTotalStorage > 0) { + mColorBar.setRatios((mTotalStorage-mFreeStorage-mAppStorage)/(float)mTotalStorage, + mAppStorage/(float)mTotalStorage, mFreeStorage/(float)mTotalStorage); + long usedStorage = mTotalStorage - mFreeStorage; + if (mLastUsedStorage != usedStorage) { + mLastUsedStorage = usedStorage; + String sizeStr = Formatter.formatShortFileSize( + mOwner.getActivity(), usedStorage); + mUsedStorageText.setText(mOwner.getActivity().getResources().getString( + R.string.service_foreground_processes, sizeStr)); + } + if (mLastFreeStorage != mFreeStorage) { + mLastFreeStorage = mFreeStorage; + String sizeStr = Formatter.formatShortFileSize( + mOwner.getActivity(), mFreeStorage); + mFreeStorageText.setText(mOwner.getActivity().getResources().getString( + R.string.service_background_processes, sizeStr)); + } + } else { + mColorBar.setRatios(0, 0, 0); + if (mLastUsedStorage != -1) { + mLastUsedStorage = -1; + mUsedStorageText.setText(""); + } + if (mLastFreeStorage != -1) { + mLastFreeStorage = -1; + mFreeStorageText.setText(""); + } + } + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + mClickListener.onItemClick(this, parent, view, position, id); + } + + void handleRunningProcessesAvail() { + mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( + mOwner.getActivity(), android.R.anim.fade_out)); + mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation( + mOwner.getActivity(), android.R.anim.fade_in)); + mRunningProcessesView.setVisibility(View.VISIBLE); + mLoadingContainer.setVisibility(View.GONE); + } + } + private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); + TabInfo mCurTab = null; + // Size resource used for packages whose size computation failed for some reason - private CharSequence mInvalidSizeStr; + CharSequence mInvalidSizeStr; private CharSequence mComputingSizeStr; // layout inflater object used to inflate views @@ -156,85 +435,75 @@ public class ManageApplications extends Fragment implements private String mCurrentPkgName; - private View mLoadingContainer; - - private View mListContainer; - - // ListView used to display list - private ListView mListView; - // Custom view used to display running processes - private RunningProcessesView mRunningProcessesView; - - LinearColorBar mColorBar; - TextView mStorageChartLabel; - TextView mUsedStorageText; - TextView mFreeStorageText; - private Menu mOptionsMenu; - - // These are for keeping track of activity and tab switch state. - private int mCurView; - private boolean mCreatedRunning; - private boolean mResumedRunning; + // These are for keeping track of activity and spinner switch state. private boolean mActivityResumed; - private StatFs mDataFileStats; - private StatFs mSDCardFileStats; - private boolean mLastShowedInternalStorage = true; - private long mLastUsedStorage, mLastAppStorage, mLastFreeStorage; - - static final String TAB_DOWNLOADED = "Downloaded"; - static final String TAB_RUNNING = "Running"; - static final String TAB_ALL = "All"; - static final String TAB_SDCARD = "OnSdCard"; - private View mRootView; + static final int LIST_TYPE_DOWNLOADED = 0; + static final int LIST_TYPE_RUNNING = 1; + static final int LIST_TYPE_SDCARD = 2; + static final int LIST_TYPE_ALL = 3; private boolean mShowBackground = false; - // -------------- Copied from TabActivity -------------- + private int mDefaultListType = -1; - private TabHost mTabHost; - private String mDefaultTab = null; + private ViewGroup mContentContainer; + private View mRootView; + private ViewPager mViewPager; - // -------------- Copied from TabActivity -------------- + AlertDialog mResetDialog; - final Runnable mRunningProcessesAvail = new Runnable() { - public void run() { - handleRunningProcessesAvail(); - } - }; + class MyPagerAdapter extends PagerAdapter + implements ViewPager.OnPageChangeListener { + int mCurPos = 0; - // View Holder used when displaying views - static class AppViewHolder { - ApplicationsState.AppEntry entry; - TextView appName; - ImageView appIcon; - TextView appSize; - TextView disabled; - CheckBox checkBox; + @Override + public int getCount() { + return mTabs.size(); + } - void updateSizeText(ManageApplications ma, int whichSize) { - if (DEBUG) Log.i(TAG, "updateSizeText of " + entry.label + " " + entry - + ": " + entry.sizeStr); - if (entry.sizeStr != null) { - switch (whichSize) { - case SIZE_INTERNAL: - appSize.setText(entry.internalSizeStr); - break; - case SIZE_EXTERNAL: - appSize.setText(entry.externalSizeStr); - break; - default: - appSize.setText(entry.sizeStr); - break; - } - } else if (entry.size == ApplicationsState.SIZE_INVALID) { - appSize.setText(ma.mInvalidSizeStr); + @Override + public Object instantiateItem(ViewGroup container, int position) { + TabInfo tab = mTabs.get(position); + View root = tab.build(mInflater, mContentContainer, mRootView); + container.addView(root); + return root; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView((View)object); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public CharSequence getPageTitle(int position) { + return mTabs.get(position).mLabel; + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int position) { + mCurPos = position; + } + + @Override + public void onPageScrollStateChanged(int state) { + if (state == ViewPager.SCROLL_STATE_IDLE) { + updateCurrentTab(mCurPos); } } } - + /* * Custom adapter implementation for the ListView * This adapter maintains a map for each displayed application and its properties @@ -244,14 +513,18 @@ public class ManageApplications extends Fragment implements * the getId methods via the package name into the internal maps and indices. * The order of applications in the list is mirrored in mAppLocalList */ - class ApplicationsAdapter extends BaseAdapter implements Filterable, + static class ApplicationsAdapter extends BaseAdapter implements Filterable, ApplicationsState.Callbacks, AbsListView.RecyclerListener { private final ApplicationsState mState; + private final ApplicationsState.Session mSession; + private final TabInfo mTab; + private final Context mContext; private final ArrayList<View> mActive = new ArrayList<View>(); + private final int mFilterMode; private ArrayList<ApplicationsState.AppEntry> mBaseEntries; private ArrayList<ApplicationsState.AppEntry> mEntries; private boolean mResumed; - private int mLastFilterMode=-1, mLastSortMode=-1; + private int mLastSortMode=-1; private boolean mWaitingForData; private int mWhichSize = SIZE_TOTAL; CharSequence mCurFilterPrefix; @@ -272,39 +545,41 @@ public class ManageApplications extends Fragment implements mCurFilterPrefix = constraint; mEntries = (ArrayList<ApplicationsState.AppEntry>)results.values; notifyDataSetChanged(); - updateStorageUsage(); + mTab.updateStorageUsage(); } }; - public ApplicationsAdapter(ApplicationsState state) { + public ApplicationsAdapter(ApplicationsState state, TabInfo tab, int filterMode) { mState = state; + mSession = state.newSession(this); + mTab = tab; + mContext = tab.mOwner.getActivity(); + mFilterMode = filterMode; } - public void resume(int filter, int sort) { + public void resume(int sort) { if (DEBUG) Log.i(TAG, "Resume! mResumed=" + mResumed); if (!mResumed) { mResumed = true; - mState.resume(this); - mLastFilterMode = filter; + mSession.resume(); mLastSortMode = sort; rebuild(true); } else { - rebuild(filter, sort); + rebuild(sort); } } public void pause() { if (mResumed) { mResumed = false; - mState.pause(); + mSession.pause(); } } - public void rebuild(int filter, int sort) { - if (filter == mLastFilterMode && sort == mLastSortMode) { + public void rebuild(int sort) { + if (sort == mLastSortMode) { return; } - mLastFilterMode = filter; mLastSortMode = sort; rebuild(true); } @@ -319,7 +594,7 @@ public class ManageApplications extends Fragment implements } else { mWhichSize = SIZE_INTERNAL; } - switch (mLastFilterMode) { + switch (mFilterMode) { case FILTER_APPS_THIRD_PARTY: filterObj = ApplicationsState.THIRD_PARTY_FILTER; break; @@ -352,7 +627,7 @@ public class ManageApplications extends Fragment implements break; } ArrayList<ApplicationsState.AppEntry> entries - = mState.rebuild(filterObj, comparatorObj); + = mSession.rebuild(filterObj, comparatorObj); if (entries == null && !eraseold) { // Don't have new list yet, but can continue using the old one. return; @@ -364,15 +639,15 @@ public class ManageApplications extends Fragment implements mEntries = null; } notifyDataSetChanged(); - updateStorageUsage(); + mTab.updateStorageUsage(); if (entries == null) { mWaitingForData = true; - mListContainer.setVisibility(View.INVISIBLE); - mLoadingContainer.setVisibility(View.VISIBLE); + mTab.mListContainer.setVisibility(View.INVISIBLE); + mTab.mLoadingContainer.setVisibility(View.VISIBLE); } else { - mListContainer.setVisibility(View.VISIBLE); - mLoadingContainer.setVisibility(View.GONE); + mTab.mListContainer.setVisibility(View.VISIBLE); + mTab.mLoadingContainer.setVisibility(View.GONE); } } @@ -398,24 +673,24 @@ public class ManageApplications extends Fragment implements @Override public void onRunningStateChanged(boolean running) { - getActivity().setProgressBarIndeterminateVisibility(running); + mTab.mOwner.getActivity().setProgressBarIndeterminateVisibility(running); } @Override public void onRebuildComplete(ArrayList<AppEntry> apps) { - if (mLoadingContainer.getVisibility() == View.VISIBLE) { - mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_out)); - mListContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_in)); + if (mTab.mLoadingContainer.getVisibility() == View.VISIBLE) { + mTab.mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( + mContext, android.R.anim.fade_out)); + mTab.mListContainer.startAnimation(AnimationUtils.loadAnimation( + mContext, android.R.anim.fade_in)); } - mListContainer.setVisibility(View.VISIBLE); - mLoadingContainer.setVisibility(View.GONE); + mTab.mListContainer.setVisibility(View.VISIBLE); + mTab.mLoadingContainer.setVisibility(View.GONE); mWaitingForData = false; mBaseEntries = apps; mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); notifyDataSetChanged(); - updateStorageUsage(); + mTab.updateStorageUsage(); } @Override @@ -435,9 +710,9 @@ public class ManageApplications extends Fragment implements AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag(); if (holder.entry.info.packageName.equals(packageName)) { synchronized (holder.entry) { - holder.updateSizeText(ManageApplications.this, mWhichSize); + holder.updateSizeText(mTab.mInvalidSizeStr, mWhichSize); } - if (holder.entry.info.packageName.equals(mCurrentPkgName) + if (holder.entry.info.packageName.equals(mTab.mOwner.mCurrentPkgName) && mLastSortMode == SORT_ORDER_SIZE) { // We got the size information for the last app the // user viewed, and are sorting by size... they may @@ -445,7 +720,7 @@ public class ManageApplications extends Fragment implements // the list with the new size to reflect it to the user. rebuild(false); } - updateStorageUsage(); + mTab.updateStorageUsage(); return; } } @@ -456,6 +731,7 @@ public class ManageApplications extends Fragment implements if (mLastSortMode == SORT_ORDER_SIZE) { rebuild(false); } + mTab.updateStorageUsage(); } public int getCount() { @@ -477,28 +753,8 @@ public class ManageApplications extends Fragment implements 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; - - // When convertView is not null, we can reuse it directly, there is no need - // to reinflate it. We only inflate a new View when the convertView supplied - // by ListView is null. - if (convertView == null) { - convertView = mInflater.inflate(R.layout.manage_applications_item, null); - - // Creates a ViewHolder and store references to the two children views - // we want to bind data to. - holder = new AppViewHolder(); - holder.appName = (TextView) convertView.findViewById(R.id.app_name); - holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon); - holder.appSize = (TextView) convertView.findViewById(R.id.app_size); - holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled); - holder.checkBox = (CheckBox) convertView.findViewById(R.id.app_on_sdcard); - convertView.setTag(holder); - } else { - // Get the ViewHolder back to get fast access to the TextView - // and the ImageView. - holder = (AppViewHolder) convertView.getTag(); - } + AppViewHolder holder = AppViewHolder.createOrRecycle(mTab.mInflater, convertView); + convertView = holder.rootView; // Bind the data efficiently with the holder ApplicationsState.AppEntry entry = mEntries.get(position); @@ -506,7 +762,7 @@ public class ManageApplications extends Fragment implements holder.entry = entry; if (entry.label != null) { holder.appName.setText(entry.label); - holder.appName.setTextColor(getActivity().getResources().getColorStateList( + holder.appName.setTextColor(mContext.getResources().getColorStateList( entry.info.enabled ? android.R.color.primary_text_dark : android.R.color.secondary_text_dark)); } @@ -514,13 +770,13 @@ public class ManageApplications extends Fragment implements if (entry.icon != null) { holder.appIcon.setImageDrawable(entry.icon); } - holder.updateSizeText(ManageApplications.this, mWhichSize); + holder.updateSizeText(mTab.mInvalidSizeStr, mWhichSize); if (InstalledAppDetails.SUPPORT_DISABLE_APPS) { holder.disabled.setVisibility(entry.info.enabled ? View.GONE : View.VISIBLE); } else { holder.disabled.setVisibility(View.GONE); } - if (mLastFilterMode == FILTER_APPS_SDCARD) { + if (mFilterMode == FILTER_APPS_SDCARD) { holder.checkBox.setVisibility(View.VISIBLE); holder.checkBox.setChecked((entry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0); @@ -551,10 +807,9 @@ public class ManageApplications extends Fragment implements setHasOptionsMenu(true); mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication()); - mApplicationsAdapter = new ApplicationsAdapter(mApplicationsState); Intent intent = getActivity().getIntent(); String action = intent.getAction(); - String defaultTabTag = TAB_DOWNLOADED; + int defaultListType = LIST_TYPE_DOWNLOADED; String className = getArguments() != null ? getArguments().getString("classname") : null; if (className == null) { @@ -562,33 +817,54 @@ public class ManageApplications extends Fragment implements } if (className.equals(RunningServicesActivity.class.getName()) || className.endsWith(".RunningServices")) { - defaultTabTag = TAB_RUNNING; + defaultListType = LIST_TYPE_RUNNING; } else if (className.equals(StorageUseActivity.class.getName()) || Intent.ACTION_MANAGE_PACKAGE_STORAGE.equals(action) || className.endsWith(".StorageUse")) { mSortOrder = SORT_ORDER_SIZE; - mFilterApps = FILTER_APPS_ALL; - defaultTabTag = TAB_ALL; + defaultListType = LIST_TYPE_ALL; } else if (Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS.equals(action)) { - // Select the all-apps tab, with the default sorting - defaultTabTag = TAB_ALL; + // Select the all-apps list, with the default sorting + defaultListType = LIST_TYPE_ALL; } - + if (savedInstanceState != null) { - mSortOrder = savedInstanceState.getInt("sortOrder", mSortOrder); - mFilterApps = savedInstanceState.getInt("filterApps", mFilterApps); - String tmp = savedInstanceState.getString("defaultTabTag"); - if (tmp != null) defaultTabTag = tmp; - mShowBackground = savedInstanceState.getBoolean("showBackground", false); + mSortOrder = savedInstanceState.getInt(EXTRA_SORT_ORDER, mSortOrder); + int tmp = savedInstanceState.getInt(EXTRA_DEFAULT_LIST_TYPE, -1); + if (tmp != -1) defaultListType = tmp; + mShowBackground = savedInstanceState.getBoolean(EXTRA_SHOW_BACKGROUND, false); } - - mDefaultTab = defaultTabTag; - - mDataFileStats = new StatFs("/data"); - mSDCardFileStats = new StatFs(Environment.getExternalStorageDirectory().toString()); + + mDefaultListType = defaultListType; + + final Intent containerIntent = new Intent().setComponent( + StorageMeasurement.DEFAULT_CONTAINER_COMPONENT); + getActivity().bindService(containerIntent, mContainerConnection, Context.BIND_AUTO_CREATE); mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); mComputingSizeStr = getActivity().getText(R.string.computing_size); + + TabInfo tab = new TabInfo(this, mApplicationsState, + getActivity().getString(R.string.filter_apps_third_party), + LIST_TYPE_DOWNLOADED, this, savedInstanceState); + mTabs.add(tab); + + if (!Environment.isExternalStorageEmulated()) { + tab = new TabInfo(this, mApplicationsState, + getActivity().getString(R.string.filter_apps_onsdcard), + LIST_TYPE_SDCARD, this, savedInstanceState); + mTabs.add(tab); + } + + tab = new TabInfo(this, mApplicationsState, + getActivity().getString(R.string.filter_apps_running), + LIST_TYPE_RUNNING, this, savedInstanceState); + mTabs.add(tab); + + tab = new TabInfo(this, mApplicationsState, + getActivity().getString(R.string.filter_apps_all), + LIST_TYPE_ALL, this, savedInstanceState); + mTabs.add(tab); } @@ -596,61 +872,41 @@ public class ManageApplications extends Fragment implements public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // initialize the inflater mInflater = inflater; - mRootView = inflater.inflate(R.layout.manage_applications, null); - mLoadingContainer = mRootView.findViewById(R.id.loading_container); - mListContainer = mRootView.findViewById(R.id.list_container); - // Create adapter and list view here - ListView lv = (ListView) mListContainer.findViewById(android.R.id.list); - View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty); - if (emptyView != null) { - lv.setEmptyView(emptyView); - } - lv.setOnItemClickListener(this); - lv.setSaveEnabled(true); - lv.setItemsCanFocus(true); - lv.setOnItemClickListener(this); - lv.setTextFilterEnabled(true); - mListView = lv; - lv.setRecyclerListener(mApplicationsAdapter); - mListView.setAdapter(mApplicationsAdapter); - mColorBar = (LinearColorBar)mListContainer.findViewById(R.id.storage_color_bar); - mStorageChartLabel = (TextView)mListContainer.findViewById(R.id.storageChartLabel); - mUsedStorageText = (TextView)mListContainer.findViewById(R.id.usedStorageText); - mFreeStorageText = (TextView)mListContainer.findViewById(R.id.freeStorageText); - mRunningProcessesView = (RunningProcessesView)mRootView.findViewById( - R.id.running_processes); - - mCreatedRunning = mResumedRunning = false; - mCurView = VIEW_NOTHING; - - mTabHost = (TabHost) mInflater.inflate(R.layout.manage_apps_tab_content, container, false); - mTabHost.setup(); - final TabHost tabHost = mTabHost; - tabHost.addTab(tabHost.newTabSpec(TAB_DOWNLOADED) - .setIndicator(getActivity().getString(R.string.filter_apps_third_party), - getActivity().getResources().getDrawable(R.drawable.ic_tab_download)) - .setContent(this)); - if (!Environment.isExternalStorageEmulated()) { - tabHost.addTab(tabHost.newTabSpec(TAB_SDCARD) - .setIndicator(getActivity().getString(R.string.filter_apps_onsdcard), - getActivity().getResources().getDrawable(R.drawable.ic_tab_sdcard)) - .setContent(this)); - } - tabHost.addTab(tabHost.newTabSpec(TAB_RUNNING) - .setIndicator(getActivity().getString(R.string.filter_apps_running), - getActivity().getResources().getDrawable(R.drawable.ic_tab_running)) - .setContent(this)); - tabHost.addTab(tabHost.newTabSpec(TAB_ALL) - .setIndicator(getActivity().getString(R.string.filter_apps_all), - getActivity().getResources().getDrawable(R.drawable.ic_tab_all)) - .setContent(this)); - tabHost.setCurrentTabByTag(mDefaultTab); - tabHost.setOnTabChangedListener(this); - - // adjust padding around tabwidget as needed - prepareCustomPreferencesList(container, mTabHost, mListView, false); - - return mTabHost; + + View rootView = mInflater.inflate(R.layout.manage_applications_content, + container, false); + mContentContainer = container; + mRootView = rootView; + + mViewPager = (ViewPager) rootView.findViewById(R.id.pager); + MyPagerAdapter adapter = new MyPagerAdapter(); + mViewPager.setAdapter(adapter); + mViewPager.setOnPageChangeListener(adapter); + PagerTabStrip tabs = (PagerTabStrip) rootView.findViewById(R.id.tabs); + tabs.setTabIndicatorColorResource(android.R.color.holo_blue_light); + + // We have to do this now because PreferenceFrameLayout looks at it + // only when the view is added. + if (container instanceof PreferenceFrameLayout) { + ((PreferenceFrameLayout.LayoutParams) rootView.getLayoutParams()).removeBorders = true; + } + + if (savedInstanceState != null && savedInstanceState.getBoolean(EXTRA_RESET_DIALOG)) { + buildResetDialog(); + } + + if (savedInstanceState == null) { + // First time init: make sure view pager is showing the correct tab. + for (int i = 0; i < mTabs.size(); i++) { + TabInfo tab = mTabs.get(i); + if (tab.mListType == mDefaultListType) { + mViewPager.setCurrentItem(i); + break; + } + } + } + + return rootView; } @Override @@ -662,32 +918,50 @@ public class ManageApplications extends Fragment implements public void onResume() { super.onResume(); mActivityResumed = true; - showCurrentTab(); + updateCurrentTab(mViewPager.getCurrentItem()); updateOptionsMenu(); - mTabHost.getTabWidget().setEnabled(true); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putInt("sortOrder", mSortOrder); - outState.putInt("filterApps", mFilterApps); - if (mDefaultTab != null) { - outState.putString("defautTabTag", mDefaultTab); + outState.putInt(EXTRA_SORT_ORDER, mSortOrder); + if (mDefaultListType != -1) { + outState.putInt(EXTRA_DEFAULT_LIST_TYPE, mDefaultListType); + } + outState.putBoolean(EXTRA_SHOW_BACKGROUND, mShowBackground); + if (mResetDialog != null) { + outState.putBoolean(EXTRA_RESET_DIALOG, true); } - outState.putBoolean("showBackground", mShowBackground); } @Override public void onPause() { super.onPause(); mActivityResumed = false; - mApplicationsAdapter.pause(); - if (mResumedRunning) { - mRunningProcessesView.doPause(); - mResumedRunning = false; + for (int i=0; i<mTabs.size(); i++) { + mTabs.get(i).pause(); + } + } + + @Override + public void onStop() { + super.onStop(); + if (mResetDialog != null) { + mResetDialog.dismiss(); + mResetDialog = null; + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + // We are going to keep the tab data structures around, but they + // are no longer attached to their view hierarchy. + for (int i=0; i<mTabs.size(); i++) { + mTabs.get(i).detachView(); } - mTabHost.getTabWidget().setEnabled(false); } @Override @@ -696,7 +970,17 @@ public class ManageApplications extends Fragment implements mApplicationsState.requestSize(mCurrentPkgName); } } - + + TabInfo tabForType(int type) { + for (int i = 0; i < mTabs.size(); i++) { + TabInfo tab = mTabs.get(i); + if (tab.mListType == type) { + return tab; + } + } + return null; + } + // utility method used to start sub activity private void startApplicationDetailsActivity() { // start new fragment to display extended information @@ -724,6 +1008,8 @@ public class ManageApplications extends Fragment implements .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); menu.add(0, SHOW_BACKGROUND_PROCESSES, 3, R.string.show_background_processes) .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + menu.add(0, RESET_APP_PREFERENCES, 4, R.string.reset_app_preferences) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); updateOptionsMenu(); } @@ -736,7 +1022,13 @@ public class ManageApplications extends Fragment implements public void onDestroyOptionsMenu() { mOptionsMenu = null; } - + + @Override + public void onDestroy() { + getActivity().unbindService(mContainerConnection); + super.onDestroy(); + } + void updateOptionsMenu() { if (mOptionsMenu == null) { return; @@ -746,18 +1038,112 @@ public class ManageApplications extends Fragment implements * The running processes screen doesn't use the mApplicationsAdapter * so bringing up this menu in that case doesn't make any sense. */ - if (mCurView == VIEW_RUNNING) { - boolean showingBackground = mRunningProcessesView != null - ? mRunningProcessesView.mAdapter.getShowBackground() : false; + if (mCurTab != null && mCurTab.mListType == LIST_TYPE_RUNNING) { + TabInfo tab = tabForType(LIST_TYPE_RUNNING); + boolean showingBackground = tab != null && tab.mRunningProcessesView != null + ? tab.mRunningProcessesView.mAdapter.getShowBackground() : false; mOptionsMenu.findItem(SORT_ORDER_ALPHA).setVisible(false); mOptionsMenu.findItem(SORT_ORDER_SIZE).setVisible(false); mOptionsMenu.findItem(SHOW_RUNNING_SERVICES).setVisible(showingBackground); mOptionsMenu.findItem(SHOW_BACKGROUND_PROCESSES).setVisible(!showingBackground); + mOptionsMenu.findItem(RESET_APP_PREFERENCES).setVisible(false); } else { mOptionsMenu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA); mOptionsMenu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE); mOptionsMenu.findItem(SHOW_RUNNING_SERVICES).setVisible(false); mOptionsMenu.findItem(SHOW_BACKGROUND_PROCESSES).setVisible(false); + mOptionsMenu.findItem(RESET_APP_PREFERENCES).setVisible(true); + } + } + + void buildResetDialog() { + if (mResetDialog == null) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.reset_app_preferences_title); + builder.setMessage(R.string.reset_app_preferences_desc); + builder.setPositiveButton(R.string.reset_app_preferences_button, this); + builder.setNegativeButton(R.string.cancel, null); + mResetDialog = builder.show(); + mResetDialog.setOnDismissListener(this); + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + if (mResetDialog == dialog) { + mResetDialog = null; + } + } + + + @Override + public void onClick(DialogInterface dialog, int which) { + if (mResetDialog == dialog) { + final PackageManager pm = getActivity().getPackageManager(); + final INotificationManager nm = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + final NetworkPolicyManager npm = NetworkPolicyManager.from(getActivity()); + final Handler handler = new Handler(getActivity().getMainLooper()); + (new AsyncTask<Void, Void, Void>() { + @Override protected Void doInBackground(Void... params) { + List<ApplicationInfo> apps = pm.getInstalledApplications( + PackageManager.GET_DISABLED_COMPONENTS); + for (int i=0; i<apps.size(); i++) { + ApplicationInfo app = apps.get(i); + try { + if (DEBUG) Log.v(TAG, "Enabling notifications: " + app.packageName); + nm.setNotificationsEnabledForPackage(app.packageName, true); + } catch (android.os.RemoteException ex) { + } + if (DEBUG) Log.v(TAG, "Clearing preferred: " + app.packageName); + pm.clearPackagePreferredActivities(app.packageName); + if (!app.enabled) { + if (DEBUG) Log.v(TAG, "Enabling app: " + app.packageName); + if (pm.getApplicationEnabledSetting(app.packageName) + == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { + pm.setApplicationEnabledSetting(app.packageName, + PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, + PackageManager.DONT_KILL_APP); + } + } + } + // We should have cleared all of the preferred apps above; + // just in case some may be lingering, retrieve whatever is + // still set and remove it. + ArrayList<IntentFilter> filters = new ArrayList<IntentFilter>(); + ArrayList<ComponentName> prefActivities = new ArrayList<ComponentName>(); + pm.getPreferredActivities(filters, prefActivities, null); + for (int i=0; i<prefActivities.size(); i++) { + if (DEBUG) Log.v(TAG, "Clearing preferred: " + + prefActivities.get(i).getPackageName()); + pm.clearPackagePreferredActivities(prefActivities.get(i).getPackageName()); + } + final int[] restrictedAppIds = npm.getAppsWithPolicy( + POLICY_REJECT_METERED_BACKGROUND); + for (int i : restrictedAppIds) { + if (DEBUG) Log.v(TAG, "Clearing data policy: " + i); + npm.setAppPolicy(i, POLICY_NONE); + } + handler.post(new Runnable() { + @Override public void run() { + if (DEBUG) Log.v(TAG, "Done clearing"); + if (getActivity() != null && mActivityResumed) { + if (DEBUG) Log.v(TAG, "Updating UI!"); + for (int i=0; i<mTabs.size(); i++) { + TabInfo tab = mTabs.get(i); + if (tab.mApplications != null) { + tab.mApplications.pause(); + } + } + if (mCurTab != null) { + mCurTab.resume(mSortOrder); + } + } + } + }); + return null; + } + }).execute(); } } @@ -766,200 +1152,78 @@ public class ManageApplications extends Fragment implements int menuId = item.getItemId(); if ((menuId == SORT_ORDER_ALPHA) || (menuId == SORT_ORDER_SIZE)) { mSortOrder = menuId; - if (mCurView != VIEW_RUNNING) { - mApplicationsAdapter.rebuild(mFilterApps, mSortOrder); + if (mCurTab != null && mCurTab.mApplications != null) { + mCurTab.mApplications.rebuild(mSortOrder); } } else if (menuId == SHOW_RUNNING_SERVICES) { mShowBackground = false; - mRunningProcessesView.mAdapter.setShowBackground(false); + if (mCurTab != null && mCurTab.mRunningProcessesView != null) { + mCurTab.mRunningProcessesView.mAdapter.setShowBackground(false); + } } else if (menuId == SHOW_BACKGROUND_PROCESSES) { mShowBackground = true; - mRunningProcessesView.mAdapter.setShowBackground(true); + if (mCurTab != null && mCurTab.mRunningProcessesView != null) { + mCurTab.mRunningProcessesView.mAdapter.setShowBackground(true); + } + } else if (menuId == RESET_APP_PREFERENCES) { + buildResetDialog(); + } else { + // Handle the home button + return false; } updateOptionsMenu(); return true; } - public void onItemClick(AdapterView<?> parent, View view, int position, + public void onItemClick(TabInfo tab, AdapterView<?> parent, View view, int position, long id) { - ApplicationsState.AppEntry entry = mApplicationsAdapter.getAppEntry(position); - mCurrentPkgName = entry.info.packageName; - startApplicationDetailsActivity(); - } - - public View createTabContent(String tag) { - return mRootView; - } - - static final int VIEW_NOTHING = 0; - static final int VIEW_LIST = 1; - static final int VIEW_RUNNING = 2; - - void updateStorageUsage() { - if (mCurView == VIEW_RUNNING) { - return; + if (tab.mApplications != null && tab.mApplications.getCount() > position) { + ApplicationsState.AppEntry entry = tab.mApplications.getAppEntry(position); + mCurrentPkgName = entry.info.packageName; + startApplicationDetailsActivity(); } + } - long freeStorage = 0; - long appStorage = 0; - long totalStorage = 0; - CharSequence newLabel = null; + public void updateCurrentTab(int position) { + TabInfo tab = mTabs.get(position); + mCurTab = tab; - if (mFilterApps == FILTER_APPS_SDCARD) { - if (mLastShowedInternalStorage) { - mLastShowedInternalStorage = false; - } - newLabel = getActivity().getText(R.string.sd_card_storage); - mSDCardFileStats.restat(Environment.getExternalStorageDirectory().toString()); - try { - totalStorage = (long)mSDCardFileStats.getBlockCount() * - mSDCardFileStats.getBlockSize(); - freeStorage = (long) mSDCardFileStats.getAvailableBlocks() * - mSDCardFileStats.getBlockSize(); - } catch (IllegalArgumentException e) { - // use the old value of mFreeMem - } - final int N = mApplicationsAdapter.getCount(); - for (int i=0; i<N; i++) { - ApplicationsState.AppEntry ae = mApplicationsAdapter.getAppEntry(i); - appStorage += ae.externalCodeSize + ae.externalDataSize; - } + // Put things in the correct paused/resumed state. + if (mActivityResumed) { + mCurTab.build(mInflater, mContentContainer, mRootView); + mCurTab.resume(mSortOrder); } else { - if (!mLastShowedInternalStorage) { - mLastShowedInternalStorage = true; - } - newLabel = getActivity().getText(R.string.internal_storage); - mDataFileStats.restat("/data"); - try { - totalStorage = (long)mDataFileStats.getBlockCount() * - mDataFileStats.getBlockSize(); - freeStorage = (long) mDataFileStats.getAvailableBlocks() * - mDataFileStats.getBlockSize(); - } catch (IllegalArgumentException e) { - } - final boolean emulatedStorage = Environment.isExternalStorageEmulated(); - final int N = mApplicationsAdapter.getCount(); - for (int i=0; i<N; i++) { - ApplicationsState.AppEntry ae = mApplicationsAdapter.getAppEntry(i); - appStorage += ae.codeSize + ae.dataSize; - if (emulatedStorage) { - appStorage += ae.externalCodeSize + ae.externalDataSize; - } - } - freeStorage += mApplicationsState.sumCacheSizes(); - } - if (newLabel != null) { - mStorageChartLabel.setText(newLabel); + mCurTab.pause(); } - if (totalStorage > 0) { - mColorBar.setRatios((totalStorage-freeStorage-appStorage)/(float)totalStorage, - appStorage/(float)totalStorage, freeStorage/(float)totalStorage); - long usedStorage = totalStorage - freeStorage; - if (mLastUsedStorage != usedStorage) { - mLastUsedStorage = usedStorage; - String sizeStr = Formatter.formatShortFileSize(getActivity(), usedStorage); - mUsedStorageText.setText(getActivity().getResources().getString( - R.string.service_foreground_processes, sizeStr)); - } - if (mLastFreeStorage != freeStorage) { - mLastFreeStorage = freeStorage; - String sizeStr = Formatter.formatShortFileSize(getActivity(), freeStorage); - mFreeStorageText.setText(getActivity().getResources().getString( - R.string.service_background_processes, sizeStr)); - } - } else { - mColorBar.setRatios(0, 0, 0); - if (mLastUsedStorage != -1) { - mLastUsedStorage = -1; - mUsedStorageText.setText(""); - } - if (mLastFreeStorage != -1) { - mLastFreeStorage = -1; - mFreeStorageText.setText(""); + for (int i=0; i<mTabs.size(); i++) { + TabInfo t = mTabs.get(i); + if (t != mCurTab) { + t.pause(); } } - } - private void selectView(int which) { - if (which == VIEW_LIST) { - if (mResumedRunning) { - mRunningProcessesView.doPause(); - mResumedRunning = false; - } - if (mCurView != which) { - mRunningProcessesView.setVisibility(View.GONE); - mListContainer.setVisibility(View.VISIBLE); - mLoadingContainer.setVisibility(View.GONE); - } - if (mActivityResumed) { - mApplicationsAdapter.resume(mFilterApps, mSortOrder); - } - } else if (which == VIEW_RUNNING) { - if (!mCreatedRunning) { - mRunningProcessesView.doCreate(null); - mRunningProcessesView.mAdapter.setShowBackground(mShowBackground); - mCreatedRunning = true; - } - boolean haveData = true; - if (mActivityResumed && !mResumedRunning) { - haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail); - mResumedRunning = true; - } - mApplicationsAdapter.pause(); - if (mCurView != which) { - if (haveData) { - mRunningProcessesView.setVisibility(View.VISIBLE); - } else { - mLoadingContainer.setVisibility(View.VISIBLE); - } - mListContainer.setVisibility(View.GONE); - } - } - mCurView = which; + mCurTab.updateStorageUsage(); + updateOptionsMenu(); final Activity host = getActivity(); if (host != null) { host.invalidateOptionsMenu(); } } - void handleRunningProcessesAvail() { - if (mCurView == VIEW_RUNNING) { - mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_out)); - mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_in)); - mRunningProcessesView.setVisibility(View.VISIBLE); - mLoadingContainer.setVisibility(View.GONE); - } - } + private volatile IMediaContainerService mContainerService; - public void showCurrentTab() { - String tabId = mDefaultTab = mTabHost.getCurrentTabTag(); - int newOption; - if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) { - newOption = FILTER_APPS_THIRD_PARTY; - } else if (TAB_ALL.equalsIgnoreCase(tabId)) { - newOption = FILTER_APPS_ALL; - } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) { - newOption = FILTER_APPS_SDCARD; - } else if (TAB_RUNNING.equalsIgnoreCase(tabId)) { - ((InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)) - .hideSoftInputFromWindow( - getActivity().getWindow().getDecorView().getWindowToken(), 0); - selectView(VIEW_RUNNING); - return; - } else { - // Invalid option. Do nothing - return; + private final ServiceConnection mContainerConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mContainerService = IMediaContainerService.Stub.asInterface(service); + for (int i=0; i<mTabs.size(); i++) { + mTabs.get(i).setContainerService(mContainerService); + } } - - mFilterApps = newOption; - selectView(VIEW_LIST); - updateStorageUsage(); - updateOptionsMenu(); - } - public void onTabChanged(String tabId) { - showCurrentTab(); - } + @Override + public void onServiceDisconnected(ComponentName name) { + mContainerService = null; + } + }; } diff --git a/src/com/android/settings/applications/RunningServiceDetails.java b/src/com/android/settings/applications/RunningServiceDetails.java index 08cb0e0..f91abd6 100644 --- a/src/com/android/settings/applications/RunningServiceDetails.java +++ b/src/com/android/settings/applications/RunningServiceDetails.java @@ -553,6 +553,7 @@ public class RunningServiceDetails extends Fragment @Override public void onRefreshUi(int what) { + if (getActivity() == null) return; switch (what) { case REFRESH_TIME: updateTimes(); diff --git a/src/com/android/settings/bluetooth/BluetoothEventManager.java b/src/com/android/settings/bluetooth/BluetoothEventManager.java index 9140b25..a6d9bcf 100644 --- a/src/com/android/settings/bluetooth/BluetoothEventManager.java +++ b/src/com/android/settings/bluetooth/BluetoothEventManager.java @@ -139,8 +139,6 @@ final class BluetoothEventManager { private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - Log.v(TAG, "Received " + intent.getAction()); - String action = intent.getAction(); BluetoothDevice device = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); diff --git a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java index 4996858..b80e42a 100644 --- a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java @@ -30,10 +30,14 @@ import android.text.Editable; import android.text.InputFilter; import android.text.TextWatcher; import android.util.Log; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.EditText; +import android.widget.TextView; import com.android.settings.R; @@ -94,19 +98,23 @@ public final class BluetoothNameDialogFragment extends DialogFragment implements .setPositiveButton(R.string.bluetooth_rename_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - if (mLocalAdapter != null) { - String deviceName = mDeviceNameView.getText().toString(); - Log.d(TAG, "Setting device name to " + deviceName); - mLocalAdapter.setName(deviceName); - } + String deviceName = mDeviceNameView.getText().toString(); + setDeviceName(deviceName); } }) .setNegativeButton(android.R.string.cancel, null) .create(); + mAlertDialog.getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); return mAlertDialog; } + private void setDeviceName(String deviceName) { + Log.d(TAG, "Setting device name to " + deviceName); + mLocalAdapter.setName(deviceName); + } + @Override public void onSaveInstanceState(Bundle outState) { outState.putString(KEY_NAME, mDeviceNameView.getText().toString()); @@ -123,6 +131,18 @@ public final class BluetoothNameDialogFragment extends DialogFragment implements }); mDeviceNameView.setText(deviceName); // set initial value before adding listener mDeviceNameView.addTextChangedListener(this); + mDeviceNameView.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + setDeviceName(v.getText().toString()); + mAlertDialog.dismiss(); + return true; // action handled + } else { + return false; // not handled + } + } + }); return view; } diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java index 7c8cb6e..d30e428 100755 --- a/src/com/android/settings/bluetooth/BluetoothSettings.java +++ b/src/com/android/settings/bluetooth/BluetoothSettings.java @@ -182,6 +182,7 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment { .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + super.onCreateOptionsMenu(menu, inflater); } @Override @@ -378,4 +379,9 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment { preference.setOnSettingsClickListener(mDeviceProfilesListener); } } + + @Override + protected int getHelpResource() { + return R.string.help_url_bluetooth; + } } diff --git a/src/com/android/settings/bluetooth/RequestPermissionActivity.java b/src/com/android/settings/bluetooth/RequestPermissionActivity.java index 07a7316..529312d 100644 --- a/src/com/android/settings/bluetooth/RequestPermissionActivity.java +++ b/src/com/android/settings/bluetooth/RequestPermissionActivity.java @@ -173,6 +173,11 @@ public class RequestPermissionActivity extends Activity implements mDialog = builder.create(); mDialog.show(); + + if (getResources().getBoolean(R.bool.auto_confirm_bluetooth_activation_dialog) == true) { + // dismiss dialog immediately if settings say so + onClick(null, DialogInterface.BUTTON_POSITIVE); + } } @Override diff --git a/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java b/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java index 9b5946b..5c4b828 100644 --- a/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java +++ b/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java @@ -62,6 +62,12 @@ public class RequestPermissionHelperActivity extends AlertActivity implements } createDialog(); + + if (getResources().getBoolean(R.bool.auto_confirm_bluetooth_activation_dialog) == true) { + // dismiss dialog immediately if settings say so + onClick(null, BUTTON_POSITIVE); + dismiss(); + } } void createDialog() { diff --git a/src/com/android/settings/deviceinfo/Memory.java b/src/com/android/settings/deviceinfo/Memory.java index 728e558..cb344bf 100644 --- a/src/com/android/settings/deviceinfo/Memory.java +++ b/src/com/android/settings/deviceinfo/Memory.java @@ -44,6 +44,7 @@ import android.widget.Toast; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; public class Memory extends SettingsPreferenceFragment { private static final String TAG = "MemorySettings"; @@ -51,8 +52,6 @@ public class Memory extends SettingsPreferenceFragment { private static final int DLG_CONFIRM_UNMOUNT = 1; private static final int DLG_ERROR_UNMOUNT = 2; - private static final int MENU_ID_USB = Menu.FIRST; - private Resources mResources; // The mountToggle Preference that has last been clicked. @@ -92,9 +91,6 @@ public class Memory extends SettingsPreferenceFragment { } StorageVolume[] storageVolumes = mStorageManager.getVolumeList(); - // mass storage is enabled if primary volume supports it - boolean massStorageEnabled = (storageVolumes.length > 0 - && storageVolumes[0].allowMassStorage()); int length = storageVolumes.length; mStorageVolumePreferenceCategories = new StorageVolumePreferenceCategory[length]; for (int i = 0; i < length; i++) { @@ -106,8 +102,13 @@ public class Memory extends SettingsPreferenceFragment { mStorageVolumePreferenceCategories[i].init(); } - // only show options menu if we are not using the legacy USB mass storage support - setHasOptionsMenu(!massStorageEnabled); + setHasOptionsMenu(true); + } + + private boolean isMassStorageEnabled() { + // mass storage is enabled if primary volume supports it + final StorageVolume[] storageVolumes = mStorageManager.getVolumeList(); + return (storageVolumes.length > 0 && storageVolumes[0].allowMassStorage()); } @Override @@ -163,15 +164,19 @@ public class Memory extends SettingsPreferenceFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(Menu.NONE, MENU_ID_USB, 0, R.string.storage_menu_usb) - //.setIcon(com.android.internal.R.drawable.stat_sys_data_usb) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + inflater.inflate(R.menu.storage, menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + final MenuItem usb = menu.findItem(R.id.storage_usb); + usb.setVisible(!isMassStorageEnabled()); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case MENU_ID_USB: + case R.id.storage_usb: if (getActivity() instanceof PreferenceActivity) { ((PreferenceActivity) getActivity()).startPreferencePanel( UsbSettings.class.getCanonicalName(), @@ -204,7 +209,10 @@ public class Memory extends SettingsPreferenceFragment { StorageVolumePreferenceCategory svpc = mStorageVolumePreferenceCategories[i]; Intent intent = svpc.intentForClick(preference); if (intent != null) { - startActivity(intent); + // Don't go across app boundary if monkey is running + if (!Utils.isMonkeyRunning()) { + startActivity(intent); + } return true; } diff --git a/src/com/android/settings/deviceinfo/Status.java b/src/com/android/settings/deviceinfo/Status.java index 434dfe7..d4fea37 100644 --- a/src/com/android/settings/deviceinfo/Status.java +++ b/src/com/android/settings/deviceinfo/Status.java @@ -329,7 +329,8 @@ public class Status extends PreferenceActivity { private void updateNetworkType() { // Whether EDGE, UMTS, etc... - setSummary(KEY_NETWORK_TYPE, TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE, sUnknown); + setSummaryText(KEY_NETWORK_TYPE, mTelephonyManager.getNetworkTypeName() + + ":" + mTelephonyManager.getNetworkType()); } private void updateDataState() { diff --git a/src/com/android/settings/deviceinfo/StorageMeasurement.java b/src/com/android/settings/deviceinfo/StorageMeasurement.java index b4004e9..2792d09 100644 --- a/src/com/android/settings/deviceinfo/StorageMeasurement.java +++ b/src/com/android/settings/deviceinfo/StorageMeasurement.java @@ -31,7 +31,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.StatFs; +import android.os.RemoteException; import android.os.storage.StorageVolume; import android.util.Log; @@ -53,7 +53,7 @@ import java.util.concurrent.ConcurrentHashMap; * know about by just keeping an array of measurement types of the following * properties: * - * Filesystem stats (using StatFs) + * Filesystem stats (using DefaultContainerService) * Directory measurements (using DefaultContainerService.measureDir) * Application measurements (using PackageManager) * @@ -81,7 +81,7 @@ public class StorageMeasurement { private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer"; - private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( + public static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService"); private final MeasurementHandler mHandler; @@ -258,8 +258,6 @@ public class StorageMeasurement { return; } - measureApproximateStorage(); - synchronized (mLock) { if (mBound) { removeMessages(MSG_DISCONNECT); @@ -274,6 +272,7 @@ public class StorageMeasurement { } case MSG_CONNECTED: { IMediaContainerService imcs = (IMediaContainerService) msg.obj; + measureApproximateStorage(imcs); measureExactStorage(imcs); break; } @@ -367,15 +366,16 @@ public class StorageMeasurement { sendEmptyMessage(MSG_COMPLETED); } - private void measureApproximateStorage() { - final StatFs stat = new StatFs(mStorageVolume != null - ? mStorageVolume.getPath() : Environment.getDataDirectory().getPath()); - final long blockSize = stat.getBlockSize(); - final long totalBlocks = stat.getBlockCount(); - final long availableBlocks = stat.getAvailableBlocks(); - - mTotalSize = totalBlocks * blockSize; - mAvailSize = availableBlocks * blockSize; + private void measureApproximateStorage(IMediaContainerService imcs) { + final String path = mStorageVolume != null ? mStorageVolume.getPath() + : Environment.getDataDirectory().getPath(); + try { + final long[] stats = imcs.getFileSystemStats(path); + mTotalSize = stats[0]; + mAvailSize = stats[1]; + } catch (RemoteException e) { + Log.w(TAG, "Problem in container service", e); + } sendInternalApproximateUpdate(); } diff --git a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java index 39a08d8..bbf1d01 100644 --- a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java +++ b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java @@ -162,7 +162,7 @@ public class StorageVolumePreferenceCategory extends PreferenceCategory implemen mResources = resources; mStorageVolume = storageVolume; mStorageManager = storageManager; - setTitle(storageVolume != null ? storageVolume.getDescription() + setTitle(storageVolume != null ? storageVolume.getDescription(context) : resources.getText(R.string.internal_storage)); mMeasurement = StorageMeasurement.getInstance(context, storageVolume, isPrimary); mMeasurement.setReceiver(this); diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index b813ec6..fa2b02d 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.SensorManager; +import android.net.Uri; import android.os.BatteryStats; import android.os.BatteryStats.Uid; import android.os.Bundle; @@ -37,6 +38,7 @@ import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.telephony.SignalStrength; +import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.Menu; @@ -72,6 +74,7 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { private static final int MENU_STATS_TYPE = Menu.FIRST; private static final int MENU_STATS_REFRESH = Menu.FIRST + 1; + private static final int MENU_HELP = Menu.FIRST + 2; private static BatteryStatsImpl sStatsXfer; @@ -318,6 +321,16 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { .setAlphabeticShortcut('r'); refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + String helpUrl; + if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_battery))) { + final MenuItem help = menu.add(0, MENU_HELP, 0, R.string.help_label); + Intent helpIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(helpUrl)); + helpIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + help.setIntent(helpIntent); + help.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } } @Override diff --git a/src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java b/src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java index 952146d..28b8616 100644 --- a/src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java +++ b/src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java @@ -34,8 +34,7 @@ public class CheckBoxAndSettingsPreference extends CheckBoxPreference { private SettingsPreferenceFragment mFragment; private TextView mTitleText; private TextView mSummaryText; - private View mCheckBox; - private ImageView mSetingsButton; + private ImageView mSettingsButton; private Intent mSettingsIntent; public CheckBoxAndSettingsPreference(Context context, AttributeSet attrs) { @@ -47,28 +46,28 @@ public class CheckBoxAndSettingsPreference extends CheckBoxPreference { @Override protected void onBindView(View view) { super.onBindView(view); - mCheckBox = view.findViewById(R.id.inputmethod_pref); - mCheckBox.setOnClickListener( + View textLayout = view.findViewById(R.id.inputmethod_pref); + textLayout.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { onCheckBoxClicked(); } }); - mSetingsButton = (ImageView)view.findViewById(R.id.inputmethod_settings); + + mSettingsButton = (ImageView) view.findViewById(R.id.inputmethod_settings); mTitleText = (TextView)view.findViewById(android.R.id.title); mSummaryText = (TextView)view.findViewById(android.R.id.summary); - mSetingsButton.setOnClickListener( + mSettingsButton.setOnClickListener( new OnClickListener() { @Override - public void onClick(View arg0) { - onSettingsButtonClicked(arg0); + public void onClick(View clickedView) { + onSettingsButtonClicked(); } }); enableSettingsButton(); } - @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); @@ -88,23 +87,23 @@ public class CheckBoxAndSettingsPreference extends CheckBoxPreference { } } - protected void onSettingsButtonClicked(View arg0) { + protected void onSettingsButtonClicked() { if (mFragment != null && mSettingsIntent != null) { mFragment.startActivity(mSettingsIntent); } } private void enableSettingsButton() { - if (mSetingsButton != null) { + if (mSettingsButton != null) { if (mSettingsIntent == null) { - mSetingsButton.setVisibility(View.GONE); + mSettingsButton.setVisibility(View.GONE); } else { final boolean checked = isChecked(); - mSetingsButton.setEnabled(checked); - mSetingsButton.setClickable(checked); - mSetingsButton.setFocusable(checked); + mSettingsButton.setEnabled(checked); + mSettingsButton.setClickable(checked); + mSettingsButton.setFocusable(checked); if (!checked) { - mSetingsButton.setAlpha(DISABLED_ALPHA); + mSettingsButton.setAlpha(DISABLED_ALPHA); } } } diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java index 4454389..bc8b458 100644 --- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java +++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java @@ -17,6 +17,7 @@ package com.android.settings.inputmethod; import com.android.settings.R; +import com.android.settings.Settings.KeyboardLayoutPickerActivity; import com.android.settings.Settings.SpellCheckersSettingsActivity; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; @@ -29,27 +30,30 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.ContentObserver; +import android.hardware.input.InputManager; +import android.hardware.input.KeyboardLayout; import android.os.Bundle; import android.os.Handler; import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceCategory; -import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.provider.Settings; import android.provider.Settings.System; import android.text.TextUtils; +import android.view.InputDevice; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Set; +import java.util.TreeSet; public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment - implements Preference.OnPreferenceChangeListener{ + implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener, + KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener { private static final String KEY_PHONE_LANGUAGE = "phone_language"; private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method"; @@ -68,17 +72,22 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment private int mDefaultInputMethodSelectorVisibility = 0; private ListPreference mShowInputMethodSelectorPref; + private PreferenceCategory mKeyboardSettingsCategory; + private PreferenceCategory mHardKeyboardCategory; + private PreferenceCategory mGameControllerCategory; private Preference mLanguagePref; - private ArrayList<InputMethodPreference> mInputMethodPreferenceList = + private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<InputMethodPreference>(); - private boolean mHaveHardKeyboard; - private PreferenceCategory mHardKeyboardCategory; + private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList = + new ArrayList<PreferenceScreen>(); + private InputManager mIm; private InputMethodManager mImm; private List<InputMethodInfo> mImis; private boolean mIsOnlyImeSettings; private Handler mHandler; @SuppressWarnings("unused") private SettingsObserver mSettingsObserver; + private Intent mIntentWaitingForResult; @Override public void onCreate(Bundle icicle) { @@ -108,18 +117,58 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment new VoiceInputOutputSettings(this).onCreate(); - // Hard keyboard - final Configuration config = getResources().getConfiguration(); - mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY); + // Get references to dynamically constructed categories. + mHardKeyboardCategory = (PreferenceCategory)findPreference("hard_keyboard"); + mKeyboardSettingsCategory = (PreferenceCategory)findPreference( + "keyboard_settings_category"); + mGameControllerCategory = (PreferenceCategory)findPreference( + "game_controller_settings_category"); - // IME + // Filter out irrelevant features if invoked from IME settings button. mIsOnlyImeSettings = Settings.ACTION_INPUT_METHOD_SETTINGS.equals( getActivity().getIntent().getAction()); getActivity().getIntent().setAction(null); + if (mIsOnlyImeSettings) { + getPreferenceScreen().removeAll(); + getPreferenceScreen().addPreference(mHardKeyboardCategory); + if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { + getPreferenceScreen().addPreference(mShowInputMethodSelectorPref); + } + getPreferenceScreen().addPreference(mKeyboardSettingsCategory); + } + + // Build IME preference category. mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); mImis = mImm.getInputMethodList(); - createImePreferenceHierarchy((PreferenceGroup)findPreference("keyboard_settings_category")); + mKeyboardSettingsCategory.removeAll(); + if (!mIsOnlyImeSettings) { + final PreferenceScreen currentIme = new PreferenceScreen(getActivity(), null); + currentIme.setKey(KEY_CURRENT_INPUT_METHOD); + currentIme.setTitle(getResources().getString(R.string.current_input_method)); + mKeyboardSettingsCategory.addPreference(currentIme); + } + + mInputMethodPreferenceList.clear(); + final int N = (mImis == null ? 0 : mImis.size()); + for (int i = 0; i < N; ++i) { + final InputMethodInfo imi = mImis.get(i); + final InputMethodPreference pref = getInputMethodPreference(imi, N); + mInputMethodPreferenceList.add(pref); + } + + if (!mInputMethodPreferenceList.isEmpty()) { + Collections.sort(mInputMethodPreferenceList); + for (int i = 0; i < N; ++i) { + mKeyboardSettingsCategory.addPreference(mInputMethodPreferenceList.get(i)); + } + } + + // Build hard keyboard and game controller preference categories. + mIm = (InputManager)getActivity().getSystemService(Context.INPUT_SERVICE); + updateInputDevices(); + + // Spell Checker final Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClass(getActivity(), SpellCheckersSettingsActivity.class); final SpellCheckersPreference scp = ((SpellCheckersPreference)findPreference( @@ -143,7 +192,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment private void updateUserDictionaryPreference(Preference userDictionaryPreference) { final Activity activity = getActivity(); - final Set<String> localeList = UserDictionaryList.getUserDictionaryLocalesList(activity); + final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity); if (null == localeList) { // The locale list is null if and only if the user dictionary service is // not present or disabled. In this case we need to remove the preference. @@ -153,12 +202,14 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment new Intent(UserDictionaryList.USER_DICTIONARY_SETTINGS_INTENT_ACTION); userDictionaryPreference.setTitle(R.string.user_dict_single_settings_title); userDictionaryPreference.setIntent(intent); + userDictionaryPreference.setFragment( + com.android.settings.UserDictionarySettings.class.getName()); // If the size of localeList is 0, we don't set the locale parameter in the // extras. This will be interpreted by the UserDictionarySettings class as // meaning "the current locale". - // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesList() + // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesSet() // the locale list always has at least one element, since it always includes the current - // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesList(). + // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesSet(). if (localeList.size() == 1) { final String locale = (String)localeList.toArray()[0]; userDictionaryPreference.getExtras().putString("locale", locale); @@ -172,6 +223,9 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment @Override public void onResume() { super.onResume(); + + mIm.registerInputDeviceListener(this, null); + if (!mIsOnlyImeSettings) { if (mLanguagePref != null) { Configuration conf = getResources().getConfiguration(); @@ -189,7 +243,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment } // Hard keyboard - if (mHaveHardKeyboard) { + if (!mHardKeyboardPreferenceList.isEmpty()) { for (int i = 0; i < sHardKeyboardKeys.length; ++i) { CheckBoxPreference chkPref = (CheckBoxPreference) mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i]); @@ -198,6 +252,8 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment } } + updateInputDevices(); + // IME InputMethodAndSubtypeUtil.loadInputMethodSubtypeList( this, getContentResolver(), mImis, null); @@ -207,11 +263,29 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment @Override public void onPause() { super.onPause(); + + mIm.unregisterInputDeviceListener(this); + if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null); } InputMethodAndSubtypeUtil.saveInputMethodSubtypeList( - this, getContentResolver(), mImis, mHaveHardKeyboard); + this, getContentResolver(), mImis, !mHardKeyboardPreferenceList.isEmpty()); + } + + @Override + public void onInputDeviceAdded(int deviceId) { + updateInputDevices(); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + updateInputDevices(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + updateInputDevices(); } @Override @@ -230,7 +304,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment } } else if (preference instanceof CheckBoxPreference) { final CheckBoxPreference chkPref = (CheckBoxPreference) preference; - if (mHaveHardKeyboard) { + if (!mHardKeyboardPreferenceList.isEmpty()) { for (int i = 0; i < sHardKeyboardKeys.length; ++i) { if (chkPref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) { System.putInt(getContentResolver(), sSystemSettingNames[i], @@ -239,6 +313,11 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment } } } + if (chkPref == mGameControllerCategory.findPreference("vibrate_input_devices")) { + System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES, + chkPref.isChecked() ? 1 : 0); + return true; + } } return super.onPreferenceTreeClick(preferenceScreen, preference); } @@ -315,47 +394,118 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment return pref; } - private void createImePreferenceHierarchy(PreferenceGroup root) { - final Preference hardKeyPref = findPreference("hard_keyboard"); - if (mIsOnlyImeSettings) { - getPreferenceScreen().removeAll(); - if (hardKeyPref != null && mHaveHardKeyboard) { - getPreferenceScreen().addPreference(hardKeyPref); - } - if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { - getPreferenceScreen().addPreference(mShowInputMethodSelectorPref); + private void updateInputDevices() { + updateHardKeyboards(); + updateGameControllers(); + } + + private void updateHardKeyboards() { + mHardKeyboardPreferenceList.clear(); + if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY) { + final int[] devices = InputDevice.getDeviceIds(); + for (int i = 0; i < devices.length; i++) { + InputDevice device = InputDevice.getDevice(devices[i]); + if (device != null + && !device.isVirtual() + && device.isFullKeyboard()) { + final String inputDeviceDescriptor = device.getDescriptor(); + final String keyboardLayoutDescriptor = + mIm.getCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor); + final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ? + mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null; + + final PreferenceScreen pref = new PreferenceScreen(getActivity(), null); + pref.setTitle(device.getName()); + if (keyboardLayout != null) { + pref.setSummary(keyboardLayout.toString()); + } else { + pref.setSummary(R.string.keyboard_layout_default_label); + } + pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + showKeyboardLayoutDialog(inputDeviceDescriptor); + return true; + } + }); + mHardKeyboardPreferenceList.add(pref); + } } - getPreferenceScreen().addPreference(root); } - if (hardKeyPref != null) { - if (mHaveHardKeyboard) { - mHardKeyboardCategory = (PreferenceCategory) hardKeyPref; - } else { - getPreferenceScreen().removePreference(hardKeyPref); + + if (!mHardKeyboardPreferenceList.isEmpty()) { + for (int i = mHardKeyboardCategory.getPreferenceCount(); i-- > 0; ) { + final Preference pref = mHardKeyboardCategory.getPreference(i); + if (pref.getOrder() < 1000) { + mHardKeyboardCategory.removePreference(pref); + } } + + Collections.sort(mHardKeyboardPreferenceList); + final int count = mHardKeyboardPreferenceList.size(); + for (int i = 0; i < count; i++) { + final Preference pref = mHardKeyboardPreferenceList.get(i); + pref.setOrder(i); + mHardKeyboardCategory.addPreference(pref); + } + + getPreferenceScreen().addPreference(mHardKeyboardCategory); + } else { + getPreferenceScreen().removePreference(mHardKeyboardCategory); } - root.removeAll(); - mInputMethodPreferenceList.clear(); + } - if (!mIsOnlyImeSettings) { - // Current IME selection - final PreferenceScreen currentIme = new PreferenceScreen(getActivity(), null); - currentIme.setKey(KEY_CURRENT_INPUT_METHOD); - currentIme.setTitle(getResources().getString(R.string.current_input_method)); - root.addPreference(currentIme); + private void showKeyboardLayoutDialog(String inputDeviceDescriptor) { + KeyboardLayoutDialogFragment fragment = + new KeyboardLayoutDialogFragment(inputDeviceDescriptor); + fragment.setTargetFragment(this, 0); + fragment.show(getActivity().getFragmentManager(), "keyboardLayout"); + } + + @Override + public void onSetupKeyboardLayouts(String inputDeviceDescriptor) { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setClass(getActivity(), KeyboardLayoutPickerActivity.class); + intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_DESCRIPTOR, + inputDeviceDescriptor); + mIntentWaitingForResult = intent; + startActivityForResult(intent, 0); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (mIntentWaitingForResult != null) { + String inputDeviceDescriptor = mIntentWaitingForResult.getStringExtra( + KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_DESCRIPTOR); + mIntentWaitingForResult = null; + showKeyboardLayoutDialog(inputDeviceDescriptor); } + } - final int N = (mImis == null ? 0 : mImis.size()); - for (int i = 0; i < N; ++i) { - final InputMethodInfo imi = mImis.get(i); - final InputMethodPreference pref = getInputMethodPreference(imi, N); - mInputMethodPreferenceList.add(pref); + private void updateGameControllers() { + if (haveInputDeviceWithVibrator()) { + getPreferenceScreen().addPreference(mGameControllerCategory); + + CheckBoxPreference chkPref = (CheckBoxPreference) + mGameControllerCategory.findPreference("vibrate_input_devices"); + chkPref.setChecked(System.getInt(getContentResolver(), + Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0); + } else { + getPreferenceScreen().removePreference(mGameControllerCategory); } + } - Collections.sort(mInputMethodPreferenceList); - for (int i = 0; i < N; ++i) { - root.addPreference(mInputMethodPreferenceList.get(i)); + private boolean haveInputDeviceWithVibrator() { + final int[] devices = InputDevice.getDeviceIds(); + for (int i = 0; i < devices.length; i++) { + InputDevice device = InputDevice.getDevice(devices[i]); + if (device != null && !device.isVirtual() && device.getVibrator().hasVibrator()) { + return true; + } } + return false; } private class SettingsObserver extends ContentObserver { diff --git a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java index cb4058f..c7d8c89 100644 --- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java +++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java @@ -22,6 +22,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceScreen; @@ -36,6 +37,7 @@ import android.view.inputmethod.InputMethodSubtype; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; public class InputMethodAndSubtypeUtil { @@ -46,6 +48,7 @@ public class InputMethodAndSubtypeUtil { private static final char INPUT_METHOD_SEPARATER = ':'; private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';'; private static final int NOT_A_SUBTYPE_ID = -1; + private static final Locale ENGLISH_LOCALE = new Locale("en"); private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER); @@ -183,7 +186,7 @@ public class InputMethodAndSubtypeUtil { getEnabledInputMethodsAndSubtypeList(resolver); HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver); - final boolean onlyOneIME = inputMethodInfos.size() == 1; + final int imiCount = inputMethodInfos.size(); boolean needsToResetSelectedSubtype = false; for (InputMethodInfo imi : inputMethodInfos) { final String imiId = imi.getId(); @@ -193,11 +196,11 @@ public class InputMethodAndSubtypeUtil { // pref is instance of CheckBoxPreference in the Configure input method screen. final boolean isImeChecked = (pref instanceof CheckBoxPreference) ? ((CheckBoxPreference) pref).isChecked() - : enabledIMEAndSubtypesMap.containsKey(imiId); + : enabledIMEAndSubtypesMap.containsKey(imiId); final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId); - final boolean auxIme = isAuxiliaryIme(imi); final boolean systemIme = isSystemIme(imi); - if (((onlyOneIME || (systemIme && !auxIme)) && !hasHardKeyboard) || isImeChecked) { + if ((!hasHardKeyboard && isAlwaysCheckedIme(imi, context.getActivity(), imiCount)) + || isImeChecked) { if (!enabledIMEAndSubtypesMap.containsKey(imiId)) { // imiId has just been enabled enabledIMEAndSubtypesMap.put(imiId, new HashSet<String>()); @@ -373,4 +376,47 @@ public class InputMethodAndSubtypeUtil { public static boolean isAuxiliaryIme(InputMethodInfo imi) { return imi.isAuxiliaryIme(); } + + public static boolean isAlwaysCheckedIme(InputMethodInfo imi, Context context, int imiCount) { + if (imiCount <= 1) { + return true; + } + if (!isSystemIme(imi)) { + return false; + } + if (isAuxiliaryIme(imi)) { + return false; + } + if (isValidDefaultIme(imi, context)) { + return true; + } + return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage()); + } + + private static boolean isValidDefaultIme(InputMethodInfo imi, Context context) { + if (imi.getIsDefaultResourceId() != 0) { + try { + Resources res = context.createPackageContext( + imi.getPackageName(), 0).getResources(); + if (res.getBoolean(imi.getIsDefaultResourceId()) + && containsSubtypeOf(imi, context.getResources().getConfiguration(). + locale.getLanguage())) { + return true; + } + } catch (PackageManager.NameNotFoundException ex) { + } catch (Resources.NotFoundException ex) { + } + } + return false; + } + + private static boolean containsSubtypeOf(InputMethodInfo imi, String language) { + final int N = imi.getSubtypeCount(); + for (int i = 0; i < N; ++i) { + if (imi.getSubtypeAt(i).getLocale().startsWith(language)) { + return true; + } + } + return false; + } } diff --git a/src/com/android/settings/inputmethod/InputMethodPreference.java b/src/com/android/settings/inputmethod/InputMethodPreference.java index 4ecdb8e..f555d21 100644 --- a/src/com/android/settings/inputmethod/InputMethodPreference.java +++ b/src/com/android/settings/inputmethod/InputMethodPreference.java @@ -39,6 +39,7 @@ import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import java.util.Comparator; import java.util.List; @@ -51,6 +52,7 @@ public class InputMethodPreference extends CheckBoxPreference private final InputMethodInfo mImi; private final InputMethodManager mImm; private final Intent mSettingsIntent; + private final boolean mAlwaysChecked; private final boolean mIsSystemIme; private AlertDialog mDialog = null; @@ -66,10 +68,10 @@ public class InputMethodPreference extends CheckBoxPreference return; } if (isChecked()) { - setChecked(false); + setChecked(false, true /* save */); } else { if (mIsSystemIme) { - setChecked(true); + setChecked(true, true /* save */); } else { showSecurityWarnDialog(mImi, InputMethodPreference.this); } @@ -87,9 +89,10 @@ public class InputMethodPreference extends CheckBoxPreference mImm = imm; mImi = imi; updateSummary(); + mAlwaysChecked = InputMethodAndSubtypeUtil.isAlwaysCheckedIme( + imi, fragment.getActivity(), imiCount); mIsSystemIme = InputMethodAndSubtypeUtil.isSystemIme(imi); - final boolean isAuxIme = InputMethodAndSubtypeUtil.isAuxiliaryIme(imi); - if (imiCount <= 1 || (mIsSystemIme && !isAuxIme)) { + if (mAlwaysChecked) { setEnabled(false); } } @@ -126,8 +129,12 @@ public class InputMethodPreference extends CheckBoxPreference mFragment.startActivity(mSettingsIntent); } catch (ActivityNotFoundException e) { Log.d(TAG, "IME's Settings Activity Not Found: " + e); - // If the IME's settings activity does not exist, we can just - // do nothing... + final String msg = mFragment.getString( + R.string.failed_to_open_app_settings_toast, + mImi.loadLabel( + mFragment.getActivity().getPackageManager())); + Toast.makeText( + mFragment.getActivity(), msg, Toast.LENGTH_LONG).show(); } } }); @@ -222,14 +229,25 @@ public class InputMethodPreference extends CheckBoxPreference setSummary(summary); } - @Override - public void setChecked(boolean checked) { + /** + * Sets the checkbox state and optionally saves the settings. + * @param checked whether to check the box + * @param save whether to save IME settings + */ + public void setChecked(boolean checked, boolean save) { super.setChecked(checked); - saveImeSettings(); + if (save) { + saveImeSettings(); + } updateSummary(); } - private void showSecurityWarnDialog(InputMethodInfo imi, final CheckBoxPreference chkPref) { + @Override + public void setChecked(boolean checked) { + setChecked(checked, false); + } + + private void showSecurityWarnDialog(InputMethodInfo imi, final InputMethodPreference chkPref) { if (mDialog != null && mDialog.isShowing()) { mDialog.dismiss(); } @@ -241,7 +259,7 @@ public class InputMethodPreference extends CheckBoxPreference new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - chkPref.setChecked(true); + chkPref.setChecked(true, true); } }) .setNegativeButton(android.R.string.cancel, diff --git a/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java b/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java new file mode 100644 index 0000000..a232a0f --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2012 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.inputmethod; + +import com.android.settings.R; +import com.android.settings.Settings.KeyboardLayoutPickerActivity; + +import android.app.AlertDialog; +import android.app.Activity; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.LoaderManager.LoaderCallbacks; +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.Loader; +import android.content.res.Resources; +import android.hardware.input.InputManager; +import android.hardware.input.KeyboardLayout; +import android.hardware.input.InputManager.InputDeviceListener; +import android.os.Bundle; +import android.view.InputDevice; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.CheckedTextView; +import android.widget.RadioButton; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; + +public class KeyboardLayoutDialogFragment extends DialogFragment + implements InputDeviceListener, LoaderCallbacks<KeyboardLayoutDialogFragment.Keyboards> { + private static final String KEY_INPUT_DEVICE_DESCRIPTOR = "inputDeviceDescriptor"; + + private String mInputDeviceDescriptor; + private int mInputDeviceId = -1; + private InputManager mIm; + private KeyboardLayoutAdapter mAdapter; + + public KeyboardLayoutDialogFragment() { + } + + public KeyboardLayoutDialogFragment(String inputDeviceDescriptor) { + mInputDeviceDescriptor = inputDeviceDescriptor; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + Context context = activity.getBaseContext(); + mIm = (InputManager)context.getSystemService(Context.INPUT_SERVICE); + mAdapter = new KeyboardLayoutAdapter(context); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + mInputDeviceDescriptor = savedInstanceState.getString(KEY_INPUT_DEVICE_DESCRIPTOR); + } + + getLoaderManager().initLoader(0, null, this); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(KEY_INPUT_DEVICE_DESCRIPTOR, mInputDeviceDescriptor); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Context context = getActivity(); + LayoutInflater inflater = LayoutInflater.from(context); + AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setTitle(R.string.keyboard_layout_dialog_title) + .setPositiveButton(R.string.keyboard_layout_dialog_setup_button, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + onSetupLayoutsButtonClicked(); + } + }) + .setSingleChoiceItems(mAdapter, -1, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + onKeyboardLayoutClicked(which); + } + }) + .setView(inflater.inflate(R.layout.keyboard_layout_dialog_switch_hint, null)); + updateSwitchHintVisibility(); + return builder.create(); + } + + @Override + public void onResume() { + super.onResume(); + + mIm.registerInputDeviceListener(this, null); + + InputDevice inputDevice = mIm.getInputDeviceByDescriptor(mInputDeviceDescriptor); + if (inputDevice == null) { + dismiss(); + return; + } + mInputDeviceId = inputDevice.getId(); + } + + @Override + public void onPause() { + mIm.unregisterInputDeviceListener(this); + mInputDeviceId = -1; + + super.onPause(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + dismiss(); + } + + private void onSetupLayoutsButtonClicked() { + ((OnSetupKeyboardLayoutsListener)getTargetFragment()).onSetupKeyboardLayouts( + mInputDeviceDescriptor); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + show(getActivity().getFragmentManager(), "layout"); + } + + private void onKeyboardLayoutClicked(int which) { + if (which >= 0 && which < mAdapter.getCount()) { + KeyboardLayout keyboardLayout = mAdapter.getItem(which); + if (keyboardLayout != null) { + mIm.setCurrentKeyboardLayoutForInputDevice(mInputDeviceDescriptor, + keyboardLayout.getDescriptor()); + } + dismiss(); + } + } + + @Override + public Loader<Keyboards> onCreateLoader(int id, Bundle args) { + return new KeyboardLayoutLoader(getActivity().getBaseContext(), mInputDeviceDescriptor); + } + + @Override + public void onLoadFinished(Loader<Keyboards> loader, Keyboards data) { + mAdapter.clear(); + mAdapter.addAll(data.keyboardLayouts); + mAdapter.setCheckedItem(data.current); + AlertDialog dialog = (AlertDialog)getDialog(); + if (dialog != null) { + dialog.getListView().setItemChecked(data.current, true); + } + updateSwitchHintVisibility(); + } + + @Override + public void onLoaderReset(Loader<Keyboards> loader) { + mAdapter.clear(); + updateSwitchHintVisibility(); + } + + @Override + public void onInputDeviceAdded(int deviceId) { + } + + @Override + public void onInputDeviceChanged(int deviceId) { + if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { + getLoaderManager().restartLoader(0, null, this); + } + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { + dismiss(); + } + } + + private void updateSwitchHintVisibility() { + AlertDialog dialog = (AlertDialog)getDialog(); + if (dialog != null) { + View customPanel = dialog.findViewById(com.android.internal.R.id.customPanel); + customPanel.setVisibility(mAdapter.getCount() > 1 ? View.VISIBLE : View.GONE); + } + } + + private static final class KeyboardLayoutAdapter extends ArrayAdapter<KeyboardLayout> { + private final LayoutInflater mInflater; + private int mCheckedItem = -1; + + public KeyboardLayoutAdapter(Context context) { + super(context, com.android.internal.R.layout.simple_list_item_2_single_choice); + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public void setCheckedItem(int position) { + mCheckedItem = position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + KeyboardLayout item = getItem(position); + String label, collection; + if (item != null) { + label = item.getLabel(); + collection = item.getCollection(); + } else { + label = getContext().getString(R.string.keyboard_layout_default_label); + collection = ""; + } + + boolean checked = (position == mCheckedItem); + if (collection.isEmpty()) { + return inflateOneLine(convertView, parent, label, checked); + } else { + return inflateTwoLine(convertView, parent, label, collection, checked); + } + } + + private View inflateOneLine(View convertView, ViewGroup parent, + String label, boolean checked) { + View view = convertView; + if (view == null || isTwoLine(view)) { + view = mInflater.inflate( + com.android.internal.R.layout.simple_list_item_single_choice, + parent, false); + setTwoLine(view, false); + } + CheckedTextView headline = (CheckedTextView) view.findViewById(android.R.id.text1); + headline.setText(label); + headline.setChecked(checked); + return view; + } + + private View inflateTwoLine(View convertView, ViewGroup parent, + String label, String collection, boolean checked) { + View view = convertView; + if (view == null || !isTwoLine(view)) { + view = mInflater.inflate( + com.android.internal.R.layout.simple_list_item_2_single_choice, + parent, false); + setTwoLine(view, true); + } + TextView headline = (TextView) view.findViewById(android.R.id.text1); + TextView subText = (TextView) view.findViewById(android.R.id.text2); + RadioButton radioButton = + (RadioButton)view.findViewById(com.android.internal.R.id.radio); + headline.setText(label); + subText.setText(collection); + radioButton.setChecked(checked); + return view; + } + + private static boolean isTwoLine(View view) { + return view.getTag() == Boolean.TRUE; + } + + private static void setTwoLine(View view, boolean twoLine) { + view.setTag(Boolean.valueOf(twoLine)); + } + } + + private static final class KeyboardLayoutLoader extends AsyncTaskLoader<Keyboards> { + private final String mInputDeviceDescriptor; + + public KeyboardLayoutLoader(Context context, String inputDeviceDescriptor) { + super(context); + mInputDeviceDescriptor = inputDeviceDescriptor; + } + + @Override + public Keyboards loadInBackground() { + Keyboards keyboards = new Keyboards(); + InputManager im = (InputManager)getContext().getSystemService(Context.INPUT_SERVICE); + String[] keyboardLayoutDescriptors = im.getKeyboardLayoutsForInputDevice( + mInputDeviceDescriptor); + for (String keyboardLayoutDescriptor : keyboardLayoutDescriptors) { + KeyboardLayout keyboardLayout = im.getKeyboardLayout(keyboardLayoutDescriptor); + if (keyboardLayout != null) { + keyboards.keyboardLayouts.add(keyboardLayout); + } + } + Collections.sort(keyboards.keyboardLayouts); + + String currentKeyboardLayoutDescriptor = + im.getCurrentKeyboardLayoutForInputDevice(mInputDeviceDescriptor); + if (currentKeyboardLayoutDescriptor != null) { + final int numKeyboardLayouts = keyboards.keyboardLayouts.size(); + for (int i = 0; i < numKeyboardLayouts; i++) { + if (keyboards.keyboardLayouts.get(i).getDescriptor().equals( + currentKeyboardLayoutDescriptor)) { + keyboards.current = i; + break; + } + } + } + + if (keyboards.keyboardLayouts.isEmpty()) { + keyboards.keyboardLayouts.add(null); // default layout + keyboards.current = 0; + } + return keyboards; + } + + @Override + protected void onStartLoading() { + super.onStartLoading(); + forceLoad(); + } + + @Override + protected void onStopLoading() { + super.onStopLoading(); + cancelLoad(); + } + } + + public static final class Keyboards { + public final ArrayList<KeyboardLayout> keyboardLayouts = new ArrayList<KeyboardLayout>(); + public int current = -1; + } + + public interface OnSetupKeyboardLayoutsListener { + public void onSetupKeyboardLayouts(String inputDeviceDescriptor); + } +}
\ No newline at end of file diff --git a/src/com/android/settings/inputmethod/KeyboardLayoutPickerFragment.java b/src/com/android/settings/inputmethod/KeyboardLayoutPickerFragment.java new file mode 100644 index 0000000..932dd10 --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardLayoutPickerFragment.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2012 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.inputmethod; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +import android.content.Context; +import android.hardware.input.InputManager; +import android.hardware.input.InputManager.InputDeviceListener; +import android.hardware.input.KeyboardLayout; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceScreen; +import android.view.InputDevice; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class KeyboardLayoutPickerFragment extends SettingsPreferenceFragment + implements InputDeviceListener { + private String mInputDeviceDescriptor; + private int mInputDeviceId = -1; + private InputManager mIm; + private KeyboardLayout[] mKeyboardLayouts; + private HashMap<CheckBoxPreference, KeyboardLayout> mPreferenceMap = + new HashMap<CheckBoxPreference, KeyboardLayout>(); + + /** + * Intent extra: The input device descriptor of the keyboard whose keyboard + * layout is to be changed. + */ + public static final String EXTRA_INPUT_DEVICE_DESCRIPTOR = "input_device_descriptor"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mInputDeviceDescriptor = getActivity().getIntent().getStringExtra( + EXTRA_INPUT_DEVICE_DESCRIPTOR); + if (mInputDeviceDescriptor == null) { + getActivity().finish(); + } + + mIm = (InputManager)getSystemService(Context.INPUT_SERVICE); + mKeyboardLayouts = mIm.getKeyboardLayouts(); + Arrays.sort(mKeyboardLayouts); + setPreferenceScreen(createPreferenceHierarchy()); + } + + @Override + public void onResume() { + super.onResume(); + + mIm.registerInputDeviceListener(this, null); + + InputDevice inputDevice = mIm.getInputDeviceByDescriptor(mInputDeviceDescriptor); + if (inputDevice == null) { + getActivity().finish(); + return; + } + mInputDeviceId = inputDevice.getId(); + + updateCheckedState(); + } + + @Override + public void onPause() { + mIm.unregisterInputDeviceListener(this); + mInputDeviceId = -1; + + super.onPause(); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { + if (preference instanceof CheckBoxPreference) { + CheckBoxPreference checkboxPref = (CheckBoxPreference)preference; + KeyboardLayout layout = mPreferenceMap.get(checkboxPref); + if (layout != null) { + boolean checked = checkboxPref.isChecked(); + if (checked) { + mIm.addKeyboardLayoutForInputDevice(mInputDeviceDescriptor, + layout.getDescriptor()); + } else { + mIm.removeKeyboardLayoutForInputDevice(mInputDeviceDescriptor, + layout.getDescriptor()); + } + return true; + } + } + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + + @Override + public void onInputDeviceAdded(int deviceId) { + } + + @Override + public void onInputDeviceChanged(int deviceId) { + if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { + updateCheckedState(); + } + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { + getActivity().finish(); + } + } + + private PreferenceScreen createPreferenceHierarchy() { + PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity()); + Context context = getActivity(); + + for (KeyboardLayout layout : mKeyboardLayouts) { + CheckBoxPreference pref = new CheckBoxPreference(context); + pref.setTitle(layout.getLabel()); + pref.setSummary(layout.getCollection()); + root.addPreference(pref); + mPreferenceMap.put(pref, layout); + } + return root; + } + + private void updateCheckedState() { + String[] enabledKeyboardLayouts = mIm.getKeyboardLayoutsForInputDevice( + mInputDeviceDescriptor); + Arrays.sort(enabledKeyboardLayouts); + + for (Map.Entry<CheckBoxPreference, KeyboardLayout> entry : mPreferenceMap.entrySet()) { + entry.getKey().setChecked(Arrays.binarySearch(enabledKeyboardLayouts, + entry.getValue().getDescriptor()) >= 0); + } + } +} diff --git a/src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java b/src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java index 2a62017..5b28142 100644 --- a/src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java +++ b/src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java @@ -19,6 +19,7 @@ package com.android.settings.inputmethod; import com.android.settings.R; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; @@ -33,6 +34,7 @@ import android.view.textservice.TextServicesManager; import android.widget.ImageView; import android.widget.RadioButton; import android.widget.TextView; +import android.widget.Toast; public class SingleSpellCheckerPreference extends Preference { private static final float DISABLED_ALPHA = 0.4f; @@ -177,7 +179,13 @@ public class SingleSpellCheckerPreference extends Preference { private void onSettingsButtonClicked(View arg0) { if (mFragment != null && mSettingsIntent != null) { - mFragment.startActivity(mSettingsIntent); + try { + mFragment.startActivity(mSettingsIntent); + } catch (ActivityNotFoundException e) { + final String msg = mFragment.getString(R.string.failed_to_open_app_settings_toast, + mSpellCheckerInfo.loadLabel(mFragment.getActivity().getPackageManager())); + Toast.makeText(mFragment.getActivity(), msg, Toast.LENGTH_LONG).show(); + } } } diff --git a/src/com/android/settings/inputmethod/UserDictionaryAddWordActivity.java b/src/com/android/settings/inputmethod/UserDictionaryAddWordActivity.java new file mode 100644 index 0000000..e52ab7a --- /dev/null +++ b/src/com/android/settings/inputmethod/UserDictionaryAddWordActivity.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2011 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.inputmethod; + +import com.android.settings.R; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +public class UserDictionaryAddWordActivity extends Activity { + + private static final String STATE_KEY_IS_OPEN = "isOpen"; + + public static final String MODE_EDIT_ACTION = "com.android.settings.USER_DICTIONARY_EDIT"; + public static final String MODE_INSERT_ACTION = "com.android.settings.USER_DICTIONARY_INSERT"; + + private UserDictionaryAddWordContents mContents; + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.user_dictionary_add_word); + final Intent intent = getIntent(); + final String action = intent.getAction(); + final int mode; + if (MODE_EDIT_ACTION.equals(action)) { + mode = UserDictionaryAddWordContents.MODE_EDIT; + } else if (MODE_INSERT_ACTION.equals(action)) { + mode = UserDictionaryAddWordContents.MODE_INSERT; + } else { + // Can never come here because we only support these two actions in the manifest + throw new RuntimeException("Unsupported action: " + action); + } + + // The following will get the EXTRA_WORD and EXTRA_LOCALE fields that are in the intent. + // We do need to add the action by hand, because UserDictionaryAddWordContents expects + // it to be in the bundle, in the EXTRA_MODE key. + final Bundle args = intent.getExtras(); + args.putInt(UserDictionaryAddWordContents.EXTRA_MODE, mode); + + if (null != savedInstanceState) { + // Override options if we have a saved state. + args.putAll(savedInstanceState); + } + + mContents = new UserDictionaryAddWordContents(getWindow().getDecorView(), args); + } + + @Override + public void onSaveInstanceState(final Bundle outState) { + mContents.saveStateIntoBundle(outState); + } + + public void onClickCancel(final View v) { + finish(); + } + + public void onClickConfirm(final View v) { + mContents.apply(this); + finish(); + } +} diff --git a/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java b/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java new file mode 100644 index 0000000..e46b19c --- /dev/null +++ b/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2012 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.inputmethod; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import android.provider.UserDictionary; +import android.text.TextUtils; +import android.view.View; +import android.widget.EditText; + +import com.android.settings.R; +import com.android.settings.UserDictionarySettings; +import com.android.settings.Utils; + +import java.util.ArrayList; +import java.util.Locale; +import java.util.TreeSet; + +/** + * A container class to factor common code to UserDictionaryAddWordFragment + * and UserDictionaryAddWordActivity. + */ +public class UserDictionaryAddWordContents { + public static final String EXTRA_MODE = "mode"; + public static final String EXTRA_WORD = "word"; + public static final String EXTRA_SHORTCUT = "shortcut"; + public static final String EXTRA_LOCALE = "locale"; + + public static final int MODE_EDIT = 0; + public static final int MODE_INSERT = 1; + + private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250; + + private final int mMode; // Either MODE_EDIT or MODE_INSERT + private final EditText mWordEditText; + private final EditText mShortcutEditText; + private String mLocale; + private final String mOldWord; + private final String mOldShortcut; + + /* package */ UserDictionaryAddWordContents(final View view, final Bundle args) { + mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text); + mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut); + final String word = args.getString(EXTRA_WORD); + if (null != word) { + mWordEditText.setText(word); + mWordEditText.setSelection(word.length()); + } + final String shortcut = args.getString(EXTRA_SHORTCUT); + if (null != shortcut && null != mShortcutEditText) { + mShortcutEditText.setText(shortcut); + } + mMode = args.getInt(EXTRA_MODE); // default return value for #getInt() is 0 = MODE_EDIT + mOldWord = args.getString(EXTRA_WORD); + mOldShortcut = args.getString(EXTRA_SHORTCUT); + updateLocale(args.getString(EXTRA_LOCALE)); + } + + // locale may be null, this means default locale + // It may also be the empty string, which means "all locales" + /* package */ void updateLocale(final String locale) { + mLocale = null == locale ? Locale.getDefault().toString() : locale; + } + + /* package */ void saveStateIntoBundle(final Bundle outState) { + outState.putString(EXTRA_WORD, mWordEditText.getText().toString()); + if (null != mShortcutEditText) { + outState.putString(EXTRA_SHORTCUT, mShortcutEditText.getText().toString()); + } + outState.putString(EXTRA_LOCALE, mLocale); + } + + /* package */ void delete(final Context context) { + if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) { + // Mode edit: remove the old entry. + final ContentResolver resolver = context.getContentResolver(); + UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver); + } + // If we are in add mode, nothing was added, so we don't need to do anything. + } + + /* package */ void apply(final Context context) { + final ContentResolver resolver = context.getContentResolver(); + if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) { + // Mode edit: remove the old entry. + UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver); + } + final String newWord = mWordEditText.getText().toString(); + final String newShortcut; + if (null == mShortcutEditText) { + newShortcut = null; + } else { + final String tmpShortcut = mShortcutEditText.getText().toString(); + if (TextUtils.isEmpty(tmpShortcut)) { + newShortcut = null; + } else { + newShortcut = tmpShortcut; + } + } + if (TextUtils.isEmpty(newWord)) { + // If the word is somehow empty, don't insert it. + return; + } + // If there is no shortcut, and the word already exists in the database, then we + // should not insert, because either A. the word exists with no shortcut, in which + // case the exact same thing we want to insert is already there, or B. the word + // exists with at least one shortcut, in which case it has priority on our word. + if (hasWord(newWord, context)) return; + + // Disallow duplicates. If the same word with no shortcut is defined, remove it; if + // the same word with the same shortcut is defined, remove it; but we don't mind if + // there is the same word with a different, non-empty shortcut. + UserDictionarySettings.deleteWord(newWord, null, resolver); + if (!TextUtils.isEmpty(newShortcut)) { + // If newShortcut is empty we just deleted this, no need to do it again + UserDictionarySettings.deleteWord(newWord, newShortcut, resolver); + } + + // In this class we use the empty string to represent 'all locales' and mLocale cannot + // be null. However the addWord method takes null to mean 'all locales'. + UserDictionary.Words.addWord(context, newWord.toString(), + FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut, + TextUtils.isEmpty(mLocale) ? null : Utils.createLocaleFromString(mLocale)); + } + + private static final String[] HAS_WORD_PROJECTION = { UserDictionary.Words.WORD }; + private static final String HAS_WORD_SELECTION_ONE_LOCALE = UserDictionary.Words.WORD + + "=? AND " + UserDictionary.Words.LOCALE + "=?"; + private static final String HAS_WORD_SELECTION_ALL_LOCALES = UserDictionary.Words.WORD + + "=? AND " + UserDictionary.Words.LOCALE + " is null"; + private boolean hasWord(final String word, final Context context) { + final Cursor cursor; + // mLocale == "" indicates this is an entry for all languages. Here, mLocale can't + // be null at all (it's ensured by the updateLocale method). + if ("".equals(mLocale)) { + cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, + HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ALL_LOCALES, + new String[] { word }, null /* sort order */); + } else { + cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, + HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ONE_LOCALE, + new String[] { word, mLocale }, null /* sort order */); + } + try { + if (null == cursor) return false; + return cursor.getCount() > 0; + } finally { + if (null != cursor) cursor.close(); + } + } + + public static class LocaleRenderer { + private final String mLocaleString; + private final String mDescription; + // LocaleString may NOT be null. + public LocaleRenderer(final Context context, final String localeString) { + mLocaleString = localeString; + if (null == localeString) { + mDescription = context.getString(R.string.user_dict_settings_more_languages); + } else if ("".equals(localeString)) { + mDescription = context.getString(R.string.user_dict_settings_all_languages); + } else { + mDescription = Utils.createLocaleFromString(localeString).getDisplayName(); + } + } + @Override + public String toString() { + return mDescription; + } + public String getLocaleString() { + return mLocaleString; + } + // "More languages..." is null ; "All languages" is the empty string. + public boolean isMoreLanguages() { + return null == mLocaleString; + } + } + + private static void addLocaleDisplayNameToList(final Context context, + final ArrayList<LocaleRenderer> list, final String locale) { + if (null != locale) { + list.add(new LocaleRenderer(context, locale)); + } + } + + // Helper method to get the list of locales to display for this word + public ArrayList<LocaleRenderer> getLocalesList(final Activity activity) { + final TreeSet<String> locales = UserDictionaryList.getUserDictionaryLocalesSet(activity); + // Remove our locale if it's in, because we're always gonna put it at the top + locales.remove(mLocale); // mLocale may not be null + final String systemLocale = Locale.getDefault().toString(); + // The system locale should be inside. We want it at the 2nd spot. + locales.remove(systemLocale); // system locale may not be null + locales.remove(""); // Remove the empty string if it's there + final ArrayList<LocaleRenderer> localesList = new ArrayList<LocaleRenderer>(); + // Add the passed locale, then the system locale at the top of the list. Add an + // "all languages" entry at the bottom of the list. + addLocaleDisplayNameToList(activity, localesList, mLocale); + if (!systemLocale.equals(mLocale)) { + addLocaleDisplayNameToList(activity, localesList, systemLocale); + } + for (final String l : locales) { + // TODO: sort in unicode order + addLocaleDisplayNameToList(activity, localesList, l); + } + if (!"".equals(mLocale)) { + // If mLocale is "", then we already inserted the "all languages" item, so don't do it + addLocaleDisplayNameToList(activity, localesList, ""); // meaning: all languages + } + localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale + return localesList; + } +} diff --git a/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java b/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java new file mode 100644 index 0000000..97ffa19 --- /dev/null +++ b/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2012 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.inputmethod; + +import android.app.Fragment; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Spinner; + +import com.android.settings.R; +import com.android.settings.inputmethod.UserDictionaryAddWordContents.LocaleRenderer; + +import java.util.ArrayList; +import java.util.Locale; + +/** + * Fragment to add a word/shortcut to the user dictionary. + * + * As opposed to the UserDictionaryActivity, this is only invoked within Settings + * from the UserDictionarySettings. + */ +public class UserDictionaryAddWordFragment extends Fragment + implements AdapterView.OnItemSelectedListener, + com.android.internal.app.LocalePicker.LocaleSelectionListener { + + private static final int OPTIONS_MENU_DELETE = Menu.FIRST; + + private UserDictionaryAddWordContents mContents; + private View mRootView; + private boolean mIsDeleting = false; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { + mRootView = inflater.inflate(R.layout.user_dictionary_add_word_fullscreen, null); + mIsDeleting = false; + if (null == mContents) { + mContents = new UserDictionaryAddWordContents(mRootView, getArguments()); + } + return mRootView; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + MenuItem actionItem = menu.add(0, OPTIONS_MENU_DELETE, 0, R.string.delete) + .setIcon(android.R.drawable.ic_menu_delete); + actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + /** + * Callback for the framework when a menu option is pressed. + * + * This class only supports the delete menu item. + * @param MenuItem the item that was pressed + * @return false to allow normal menu processing to proceed, true to consume it here + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == OPTIONS_MENU_DELETE) { + mContents.delete(getActivity()); + mIsDeleting = true; + getActivity().onBackPressed(); + return true; + } + return false; + } + + @Override + public void onResume() { + super.onResume(); + // We are being shown: display the word + updateSpinner(); + } + + private void updateSpinner() { + final ArrayList<LocaleRenderer> localesList = mContents.getLocalesList(getActivity()); + + final Spinner localeSpinner = + (Spinner)mRootView.findViewById(R.id.user_dictionary_add_locale); + final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<LocaleRenderer>(getActivity(), + android.R.layout.simple_spinner_item, localesList); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + localeSpinner.setAdapter(adapter); + localeSpinner.setOnItemSelectedListener(this); + } + + @Override + public void onPause() { + super.onPause(); + // We are being hidden: commit changes to the user dictionary, unless we were deleting it + if (!mIsDeleting) { + mContents.apply(getActivity()); + } + } + + @Override + public void onItemSelected(final AdapterView<?> parent, final View view, final int pos, + final long id) { + final LocaleRenderer locale = (LocaleRenderer)parent.getItemAtPosition(pos); + if (locale.isMoreLanguages()) { + PreferenceActivity preferenceActivity = (PreferenceActivity)getActivity(); + preferenceActivity.startPreferenceFragment(new UserDictionaryLocalePicker(this), true); + } else { + mContents.updateLocale(locale.getLocaleString()); + } + } + + @Override + public void onNothingSelected(final AdapterView<?> parent) { + // I'm not sure we can come here, but if we do, that's the right thing to do. + final Bundle args = getArguments(); + mContents.updateLocale(args.getString(UserDictionaryAddWordContents.EXTRA_LOCALE)); + } + + // Called by the locale picker + @Override + public void onLocaleSelected(final Locale locale) { + mContents.updateLocale(locale.toString()); + getActivity().onBackPressed(); + } +} diff --git a/src/com/android/settings/inputmethod/UserDictionaryList.java b/src/com/android/settings/inputmethod/UserDictionaryList.java index 6b1ca7b..5390be6 100644 --- a/src/com/android/settings/inputmethod/UserDictionaryList.java +++ b/src/com/android/settings/inputmethod/UserDictionaryList.java @@ -29,7 +29,6 @@ import android.preference.PreferenceGroup; import android.provider.UserDictionary; import java.util.Locale; -import java.util.Set; import java.util.TreeSet; public class UserDictionaryList extends SettingsPreferenceFragment { @@ -43,12 +42,12 @@ public class UserDictionaryList extends SettingsPreferenceFragment { setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity())); } - static Set<String> getUserDictionaryLocalesList(Activity activity) { + static TreeSet<String> getUserDictionaryLocalesSet(Activity activity) { @SuppressWarnings("deprecation") final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI, new String[] { UserDictionary.Words.LOCALE }, null, null, null); - final Set<String> localeList = new TreeSet<String>(); + final TreeSet<String> localeList = new TreeSet<String>(); if (null == cursor) { // The user dictionary service is not present or disabled. Return null. return null; @@ -70,7 +69,8 @@ public class UserDictionaryList extends SettingsPreferenceFragment { protected void createUserDictSettings(PreferenceGroup userDictGroup) { final Activity activity = getActivity(); userDictGroup.removeAll(); - final Set<String> localeList = UserDictionaryList.getUserDictionaryLocalesList(activity); + final TreeSet<String> localeList = + UserDictionaryList.getUserDictionaryLocalesSet(activity); if (localeList.isEmpty()) { userDictGroup.addPreference(createUserDictionaryPreference(null, activity)); @@ -100,6 +100,7 @@ public class UserDictionaryList extends SettingsPreferenceFragment { newPref.getExtras().putString("locale", locale); } newPref.setIntent(intent); + newPref.setFragment(com.android.settings.UserDictionarySettings.class.getName()); return newPref; } diff --git a/src/com/android/settings/inputmethod/UserDictionaryLocalePicker.java b/src/com/android/settings/inputmethod/UserDictionaryLocalePicker.java new file mode 100644 index 0000000..b9ccdfd --- /dev/null +++ b/src/com/android/settings/inputmethod/UserDictionaryLocalePicker.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012 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.inputmethod; + +import java.util.Locale; + +public class UserDictionaryLocalePicker extends com.android.internal.app.LocalePicker { + public UserDictionaryLocalePicker(final UserDictionaryAddWordFragment parent) { + super(); + setLocaleSelectionListener(parent); + } +} diff --git a/src/com/android/settings/net/ChartDataLoader.java b/src/com/android/settings/net/ChartDataLoader.java index 09e6e3b..e0336b7 100644 --- a/src/com/android/settings/net/ChartDataLoader.java +++ b/src/com/android/settings/net/ChartDataLoader.java @@ -21,41 +21,44 @@ import static android.net.NetworkStats.SET_FOREGROUND; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStatsHistory.FIELD_RX_BYTES; import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; +import static android.text.format.DateUtils.HOUR_IN_MILLIS; import android.content.AsyncTaskLoader; import android.content.Context; -import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.os.Bundle; import android.os.RemoteException; +import com.android.settings.DataUsageSummary.AppItem; + /** * Loader for historical chart data for both network and UID details. */ public class ChartDataLoader extends AsyncTaskLoader<ChartData> { private static final String KEY_TEMPLATE = "template"; - private static final String KEY_UIDS = "uids"; + private static final String KEY_APP = "app"; private static final String KEY_FIELDS = "fields"; - private final INetworkStatsService mStatsService; + private final INetworkStatsSession mSession; private final Bundle mArgs; - public static Bundle buildArgs(NetworkTemplate template, int[] uids) { - return buildArgs(template, uids, FIELD_RX_BYTES | FIELD_TX_BYTES); + public static Bundle buildArgs(NetworkTemplate template, AppItem app) { + return buildArgs(template, app, FIELD_RX_BYTES | FIELD_TX_BYTES); } - public static Bundle buildArgs(NetworkTemplate template, int[] uids, int fields) { + public static Bundle buildArgs(NetworkTemplate template, AppItem app, int fields) { final Bundle args = new Bundle(); args.putParcelable(KEY_TEMPLATE, template); - args.putIntArray(KEY_UIDS, uids); + args.putParcelable(KEY_APP, app); args.putInt(KEY_FIELDS, fields); return args; } - public ChartDataLoader(Context context, INetworkStatsService statsService, Bundle args) { + public ChartDataLoader(Context context, INetworkStatsSession session, Bundle args) { super(context); - mStatsService = statsService; + mSession = session; mArgs = args; } @@ -68,11 +71,11 @@ public class ChartDataLoader extends AsyncTaskLoader<ChartData> { @Override public ChartData loadInBackground() { final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE); - final int[] uids = mArgs.getIntArray(KEY_UIDS); + final AppItem app = mArgs.getParcelable(KEY_APP); final int fields = mArgs.getInt(KEY_FIELDS); try { - return loadInBackground(template, uids, fields); + return loadInBackground(template, app, fields); } catch (RemoteException e) { // since we can't do much without history, and we don't want to // leave with half-baked UI, we bail hard. @@ -80,26 +83,31 @@ public class ChartDataLoader extends AsyncTaskLoader<ChartData> { } } - private ChartData loadInBackground(NetworkTemplate template, int[] uids, int fields) + private ChartData loadInBackground(NetworkTemplate template, AppItem app, int fields) throws RemoteException { final ChartData data = new ChartData(); - data.network = mStatsService.getHistoryForNetwork(template, fields); - - if (uids != null) { - data.detailDefault = null; - data.detailForeground = null; + data.network = mSession.getHistoryForNetwork(template, fields); + if (app != null) { // load stats for current uid and template - for (int uid : uids) { + final int size = app.uids.size(); + for (int i = 0; i < size; i++) { + final int uid = app.uids.keyAt(i); data.detailDefault = collectHistoryForUid( template, uid, SET_DEFAULT, data.detailDefault); data.detailForeground = collectHistoryForUid( template, uid, SET_FOREGROUND, data.detailForeground); } - data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration()); - data.detail.recordEntireHistory(data.detailDefault); - data.detail.recordEntireHistory(data.detailForeground); + if (size > 0) { + data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration()); + data.detail.recordEntireHistory(data.detailDefault); + data.detail.recordEntireHistory(data.detailForeground); + } else { + data.detailDefault = new NetworkStatsHistory(HOUR_IN_MILLIS); + data.detailForeground = new NetworkStatsHistory(HOUR_IN_MILLIS); + data.detail = new NetworkStatsHistory(HOUR_IN_MILLIS); + } } return data; @@ -124,7 +132,7 @@ public class ChartDataLoader extends AsyncTaskLoader<ChartData> { private NetworkStatsHistory collectHistoryForUid( NetworkTemplate template, int uid, int set, NetworkStatsHistory existing) throws RemoteException { - final NetworkStatsHistory history = mStatsService.getHistoryForUid( + final NetworkStatsHistory history = mSession.getHistoryForUid( template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES); if (existing != null) { diff --git a/src/com/android/settings/net/DataUsageMeteredSettings.java b/src/com/android/settings/net/DataUsageMeteredSettings.java new file mode 100644 index 0000000..ad12311 --- /dev/null +++ b/src/com/android/settings/net/DataUsageMeteredSettings.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2012 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.net; + +import static android.net.NetworkPolicy.LIMIT_DISABLED; +import static android.net.wifi.WifiInfo.removeDoubleQuotes; +import static com.android.settings.DataUsageSummary.hasReadyMobileRadio; +import static com.android.settings.DataUsageSummary.hasWifiRadio; + +import android.content.Context; +import android.net.NetworkPolicy; +import android.net.NetworkPolicyManager; +import android.net.NetworkTemplate; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.telephony.TelephonyManager; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +/** + * Panel to configure {@link NetworkPolicy#metered} for networks. + */ +public class DataUsageMeteredSettings extends SettingsPreferenceFragment { + + private static final boolean SHOW_MOBILE_CATEGORY = false; + + private NetworkPolicyManager mPolicyManager; + private WifiManager mWifiManager; + + private NetworkPolicyEditor mPolicyEditor; + + private PreferenceCategory mMobileCategory; + private PreferenceCategory mWifiCategory; + private Preference mWifiDisabled; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + final Context context = getActivity(); + + mPolicyManager = NetworkPolicyManager.from(context); + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + + mPolicyEditor = new NetworkPolicyEditor(mPolicyManager); + mPolicyEditor.read(); + + addPreferencesFromResource(R.xml.data_usage_metered_prefs); + mMobileCategory = (PreferenceCategory) findPreference("mobile"); + mWifiCategory = (PreferenceCategory) findPreference("wifi"); + mWifiDisabled = findPreference("wifi_disabled"); + + updateNetworks(context); + } + + private void updateNetworks(Context context) { + if (SHOW_MOBILE_CATEGORY && hasReadyMobileRadio(context)) { + mMobileCategory.removeAll(); + mMobileCategory.addPreference(buildMobilePref(context)); + } else { + getPreferenceScreen().removePreference(mMobileCategory); + } + + mWifiCategory.removeAll(); + if (hasWifiRadio(context) && mWifiManager.isWifiEnabled()) { + for (WifiConfiguration config : mWifiManager.getConfiguredNetworks()) { + if (config.SSID != null) { + mWifiCategory.addPreference(buildWifiPref(context, config)); + } + } + } else { + mWifiCategory.addPreference(mWifiDisabled); + } + } + + private Preference buildMobilePref(Context context) { + final TelephonyManager tele = TelephonyManager.from(context); + final NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll( + tele.getSubscriberId()); + final MeteredPreference pref = new MeteredPreference(context, template); + pref.setTitle(tele.getNetworkOperatorName()); + return pref; + } + + private Preference buildWifiPref(Context context, WifiConfiguration config) { + final String networkId = removeDoubleQuotes(config.SSID); + final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(networkId); + final MeteredPreference pref = new MeteredPreference(context, template); + pref.setTitle(networkId); + return pref; + } + + private class MeteredPreference extends CheckBoxPreference { + private final NetworkTemplate mTemplate; + private boolean mBinding; + + public MeteredPreference(Context context, NetworkTemplate template) { + super(context); + mTemplate = template; + + setPersistent(false); + + mBinding = true; + final NetworkPolicy policy = mPolicyEditor.getPolicy(template); + if (policy != null) { + if (policy.limitBytes != LIMIT_DISABLED) { + setChecked(true); + setEnabled(false); + } else { + setChecked(policy.metered); + } + } else { + setChecked(false); + } + mBinding = false; + } + + @Override + protected void notifyChanged() { + super.notifyChanged(); + if (!mBinding) { + mPolicyEditor.setPolicyMetered(mTemplate, isChecked()); + } + } + } +} diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java index 5ba8ca4..5fe4c06 100644 --- a/src/com/android/settings/net/NetworkPolicyEditor.java +++ b/src/com/android/settings/net/NetworkPolicyEditor.java @@ -16,6 +16,7 @@ package com.android.settings.net; +import static android.net.NetworkPolicy.CYCLE_NONE; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; @@ -27,11 +28,10 @@ import static android.net.NetworkTemplate.buildTemplateMobile4g; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static com.android.internal.util.Preconditions.checkNotNull; -import android.net.INetworkPolicyManager; import android.net.NetworkPolicy; +import android.net.NetworkPolicyManager; import android.net.NetworkTemplate; import android.os.AsyncTask; -import android.os.RemoteException; import android.text.format.Time; import com.android.internal.util.Objects; @@ -43,27 +43,23 @@ import java.util.HashSet; /** * Utility class to modify list of {@link NetworkPolicy}. Specifically knows - * about which policies can coexist. Not thread safe. + * about which policies can coexist. This editor offers thread safety when + * talking with {@link NetworkPolicyManager}. */ public class NetworkPolicyEditor { // TODO: be more robust when missing policies from service public static final boolean ENABLE_SPLIT_POLICIES = false; - private INetworkPolicyManager mPolicyService; + private NetworkPolicyManager mPolicyManager; private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList(); - public NetworkPolicyEditor(INetworkPolicyManager policyService) { - mPolicyService = checkNotNull(policyService); + public NetworkPolicyEditor(NetworkPolicyManager policyManager) { + mPolicyManager = checkNotNull(policyManager); } public void read() { - final NetworkPolicy[] policies; - try { - policies = mPolicyService.getNetworkPolicies(); - } catch (RemoteException e) { - throw new RuntimeException("problem reading policies", e); - } + final NetworkPolicy[] policies = mPolicyManager.getNetworkPolicies(); boolean modified = false; mPolicies.clear(); @@ -78,12 +74,6 @@ public class NetworkPolicyEditor { modified = true; } - // drop any WIFI policies that were defined - if (policy.template.getMatchRule() == MATCH_WIFI) { - modified = true; - continue; - } - mPolicies.add(policy); } @@ -109,11 +99,7 @@ public class NetworkPolicyEditor { } public void write(NetworkPolicy[] policies) { - try { - mPolicyService.setNetworkPolicies(policies); - } catch (RemoteException e) { - throw new RuntimeException("problem writing policies", e); - } + mPolicyManager.setNetworkPolicies(policies); } public boolean hasLimitedPolicy(NetworkTemplate template) { @@ -139,24 +125,39 @@ public class NetworkPolicyEditor { return null; } + @Deprecated private static NetworkPolicy buildDefaultPolicy(NetworkTemplate template) { // TODO: move this into framework to share with NetworkPolicyManagerService - final Time time = new Time(); - time.setToNow(); - final int cycleDay = time.monthDay; + final int cycleDay; + final String cycleTimezone; + final boolean metered; + + if (template.getMatchRule() == MATCH_WIFI) { + cycleDay = CYCLE_NONE; + cycleTimezone = Time.TIMEZONE_UTC; + metered = false; + } else { + final Time time = new Time(); + time.setToNow(); + cycleDay = time.monthDay; + cycleTimezone = time.timezone; + metered = true; + } - return new NetworkPolicy( - template, cycleDay, WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER); + return new NetworkPolicy(template, cycleDay, cycleTimezone, WARNING_DISABLED, + LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, metered, true); } public int getPolicyCycleDay(NetworkTemplate template) { return getPolicy(template).cycleDay; } - public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) { + public void setPolicyCycleDay(NetworkTemplate template, int cycleDay, String cycleTimezone) { final NetworkPolicy policy = getOrCreatePolicy(template); policy.cycleDay = cycleDay; - policy.lastSnooze = SNOOZE_NEVER; + policy.cycleTimezone = cycleTimezone; + policy.inferred = false; + policy.clearSnooze(); writeAsync(); } @@ -167,7 +168,8 @@ public class NetworkPolicyEditor { public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) { final NetworkPolicy policy = getOrCreatePolicy(template); policy.warningBytes = warningBytes; - policy.lastSnooze = SNOOZE_NEVER; + policy.inferred = false; + policy.clearSnooze(); writeAsync(); } @@ -178,10 +180,50 @@ public class NetworkPolicyEditor { public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) { final NetworkPolicy policy = getOrCreatePolicy(template); policy.limitBytes = limitBytes; - policy.lastSnooze = SNOOZE_NEVER; + policy.inferred = false; + policy.clearSnooze(); writeAsync(); } + public boolean getPolicyMetered(NetworkTemplate template) { + final NetworkPolicy policy = getPolicy(template); + if (policy != null) { + return policy.metered; + } else { + return false; + } + } + + public void setPolicyMetered(NetworkTemplate template, boolean metered) { + boolean modified = false; + + NetworkPolicy policy = getPolicy(template); + if (metered) { + if (policy == null) { + policy = buildDefaultPolicy(template); + policy.metered = true; + policy.inferred = false; + mPolicies.add(policy); + modified = true; + } else if (!policy.metered) { + policy.metered = true; + policy.inferred = false; + modified = true; + } + + } else { + if (policy == null) { + // ignore when policy doesn't exist + } else if (policy.metered) { + policy.metered = false; + policy.inferred = false; + modified = true; + } + } + + if (modified) writeAsync(); + } + /** * Remove any split {@link NetworkPolicy}. */ @@ -198,6 +240,7 @@ public class NetworkPolicyEditor { return modified; } + @Deprecated public boolean isMobilePolicySplit(String subscriberId) { boolean has3g = false; boolean has4g = false; @@ -217,6 +260,7 @@ public class NetworkPolicyEditor { return has3g && has4g; } + @Deprecated public void setMobilePolicySplit(String subscriberId, boolean split) { if (setMobilePolicySplitInternal(subscriberId, split)) { writeAsync(); @@ -229,6 +273,7 @@ public class NetworkPolicyEditor { * * @return {@code true} when any {@link NetworkPolicy} was mutated. */ + @Deprecated private boolean setMobilePolicySplitInternal(String subscriberId, boolean split) { final boolean beforeSplit = isMobilePolicySplit(subscriberId); @@ -249,21 +294,21 @@ public class NetworkPolicyEditor { : policy4g; mPolicies.remove(policy3g); mPolicies.remove(policy4g); - mPolicies.add( - new NetworkPolicy(templateAll, restrictive.cycleDay, restrictive.warningBytes, - restrictive.limitBytes, SNOOZE_NEVER)); + mPolicies.add(new NetworkPolicy(templateAll, restrictive.cycleDay, + restrictive.cycleTimezone, restrictive.warningBytes, restrictive.limitBytes, + SNOOZE_NEVER, SNOOZE_NEVER, restrictive.metered, restrictive.inferred)); return true; } else if (!beforeSplit && split) { // duplicate existing policy into two rules final NetworkPolicy policyAll = getPolicy(templateAll); mPolicies.remove(policyAll); - mPolicies.add( - new NetworkPolicy(template3g, policyAll.cycleDay, policyAll.warningBytes, - policyAll.limitBytes, SNOOZE_NEVER)); - mPolicies.add( - new NetworkPolicy(template4g, policyAll.cycleDay, policyAll.warningBytes, - policyAll.limitBytes, SNOOZE_NEVER)); + mPolicies.add(new NetworkPolicy(template3g, policyAll.cycleDay, policyAll.cycleTimezone, + policyAll.warningBytes, policyAll.limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, + policyAll.metered, policyAll.inferred)); + mPolicies.add(new NetworkPolicy(template4g, policyAll.cycleDay, policyAll.cycleTimezone, + policyAll.warningBytes, policyAll.limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, + policyAll.metered, policyAll.inferred)); return true; } else { return false; diff --git a/src/com/android/settings/net/SummaryForAllUidLoader.java b/src/com/android/settings/net/SummaryForAllUidLoader.java index c01de45..68dc799 100644 --- a/src/com/android/settings/net/SummaryForAllUidLoader.java +++ b/src/com/android/settings/net/SummaryForAllUidLoader.java @@ -18,7 +18,7 @@ package com.android.settings.net; import android.content.AsyncTaskLoader; import android.content.Context; -import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; import android.net.NetworkStats; import android.net.NetworkTemplate; import android.os.Bundle; @@ -29,7 +29,7 @@ public class SummaryForAllUidLoader extends AsyncTaskLoader<NetworkStats> { private static final String KEY_START = "start"; private static final String KEY_END = "end"; - private final INetworkStatsService mStatsService; + private final INetworkStatsSession mSession; private final Bundle mArgs; public static Bundle buildArgs(NetworkTemplate template, long start, long end) { @@ -40,10 +40,9 @@ public class SummaryForAllUidLoader extends AsyncTaskLoader<NetworkStats> { return args; } - public SummaryForAllUidLoader( - Context context, INetworkStatsService statsService, Bundle args) { + public SummaryForAllUidLoader(Context context, INetworkStatsSession session, Bundle args) { super(context); - mStatsService = statsService; + mSession = session; mArgs = args; } @@ -60,7 +59,7 @@ public class SummaryForAllUidLoader extends AsyncTaskLoader<NetworkStats> { final long end = mArgs.getLong(KEY_END); try { - return mStatsService.getSummaryForAllUid(template, start, end, false); + return mSession.getSummaryForAllUid(template, start, end, false); } catch (RemoteException e) { return null; } diff --git a/src/com/android/settings/net/UidDetailProvider.java b/src/com/android/settings/net/UidDetailProvider.java index 57d585b..dd2b8c0 100644 --- a/src/com/android/settings/net/UidDetailProvider.java +++ b/src/com/android/settings/net/UidDetailProvider.java @@ -22,6 +22,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.TrafficStats; import android.text.TextUtils; @@ -39,21 +40,42 @@ public class UidDetailProvider { mUidDetailCache = new SparseArray<UidDetail>(); } - public synchronized void clearCache() { - mUidDetailCache.clear(); + public void clearCache() { + synchronized (mUidDetailCache) { + mUidDetailCache.clear(); + } } /** * Resolve best descriptive label for the given UID. */ - public synchronized UidDetail getUidDetail(int uid, boolean blocking) { - final UidDetail cached = mUidDetailCache.get(uid); - if (cached != null) { - return cached; + public UidDetail getUidDetail(int uid, boolean blocking) { + UidDetail detail; + + synchronized (mUidDetailCache) { + detail = mUidDetailCache.get(uid); + } + + if (detail != null) { + return detail; } else if (!blocking) { return null; } + detail = buildUidDetail(uid); + + synchronized (mUidDetailCache) { + mUidDetailCache.put(uid, detail); + } + + return detail; + } + + /** + * Build {@link UidDetail} object, blocking until all {@link Drawable} + * lookup is finished. + */ + private UidDetail buildUidDetail(int uid) { final Resources res = mContext.getResources(); final PackageManager pm = mContext.getPackageManager(); @@ -66,19 +88,16 @@ public class UidDetailProvider { case android.os.Process.SYSTEM_UID: detail.label = res.getString(R.string.process_kernel_label); detail.icon = pm.getDefaultActivityIcon(); - mUidDetailCache.put(uid, detail); return detail; case TrafficStats.UID_REMOVED: detail.label = res.getString(R.string.data_usage_uninstalled_apps); detail.icon = pm.getDefaultActivityIcon(); - mUidDetailCache.put(uid, detail); return detail; case TrafficStats.UID_TETHERING: final ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService( Context.CONNECTIVITY_SERVICE); detail.label = res.getString(Utils.getTetheringLabel(cm)); detail.icon = pm.getDefaultActivityIcon(); - mUidDetailCache.put(uid, detail); return detail; } @@ -113,7 +132,6 @@ public class UidDetailProvider { detail.label = Integer.toString(uid); } - mUidDetailCache.put(uid, detail); return detail; } } diff --git a/src/com/android/settings/tts/TextToSpeechSettings.java b/src/com/android/settings/tts/TextToSpeechSettings.java index 517eade..fbcdb4f 100644 --- a/src/com/android/settings/tts/TextToSpeechSettings.java +++ b/src/com/android/settings/tts/TextToSpeechSettings.java @@ -192,23 +192,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements checkVoiceData(mCurrentEngine); } - private void maybeUpdateTtsLanguage(String currentEngine) { - if (currentEngine != null && mTts != null) { - final String localeString = mEnginesHelper.getLocalePrefForEngine( - currentEngine); - if (localeString != null) { - final String[] locale = TtsEngines.parseLocalePref(localeString); - final Locale newLocale = new Locale(locale[0], locale[1], locale[2]); - final Locale engineLocale = mTts.getLanguage(); - - if (!newLocale.equals(engineLocale)) { - if (DBG) Log.d(TAG, "Loading language ahead of sample check : " + locale); - mTts.setLanguage(newLocale); - } - } - } - } - /** * Ask the current default engine to return a string of sample text to be * spoken to the user. @@ -218,7 +201,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements if (TextUtils.isEmpty(currentEngine)) currentEngine = mTts.getDefaultEngine(); - maybeUpdateTtsLanguage(currentEngine); Locale currentLocale = mTts.getLanguage(); // TODO: This is currently a hidden private API. The intent extras diff --git a/src/com/android/settings/tts/TtsEngineSettingsFragment.java b/src/com/android/settings/tts/TtsEngineSettingsFragment.java index 18d1fdb..fcc2f2e 100644 --- a/src/com/android/settings/tts/TtsEngineSettingsFragment.java +++ b/src/com/android/settings/tts/TtsEngineSettingsFragment.java @@ -51,6 +51,25 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem private Preference mInstallVoicesPreference; private Intent mEngineSettingsIntent; + private TextToSpeech mTts; + + private final TextToSpeech.OnInitListener mTtsInitListener = new TextToSpeech.OnInitListener() { + @Override + public void onInit(int status) { + if (status != TextToSpeech.SUCCESS) { + finishFragment(); + } else { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + mLocalePreference.setEnabled(true); + updateVoiceDetails(); + } + }); + } + } + }; + public TtsEngineSettingsFragment() { super(); } @@ -83,7 +102,15 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem } mInstallVoicesPreference.setEnabled(false); - updateVoiceDetails(); + mLocalePreference.setEnabled(false); + mTts = new TextToSpeech(getActivity().getApplicationContext(), mTtsInitListener, + getEngineName()); + } + + @Override + public void onDestroy() { + mTts.shutdown(); + super.onDestroy(); } private void updateVoiceDetails() { @@ -153,8 +180,7 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem mLocalePreference.setValueIndex(selectedLanguageIndex); } else { mLocalePreference.setValueIndex(0); - mEnginesHelper.updateLocalePrefForEngine(getEngineName(), - availableLangs.get(0)); + updateLanguageTo(availableLangs.get(0)); } } @@ -191,13 +217,23 @@ public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implem @Override public boolean onPreferenceChange(Preference preference, Object newValue) { if (preference == mLocalePreference) { - mEnginesHelper.updateLocalePrefForEngine(getEngineName(), (String) newValue); + updateLanguageTo((String) newValue); return true; } return false; } + private void updateLanguageTo(String locale) { + mEnginesHelper.updateLocalePrefForEngine(getEngineName(), locale); + if (getEngineName().equals(mTts.getCurrentEngine())) { + String[] localeArray = TtsEngines.parseLocalePref(locale); + if (localeArray != null) { + mTts.setLanguage(new Locale(localeArray[0], localeArray[1], localeArray[2])); + } + } + } + private String getEngineName() { return getArguments().getString(TtsEnginePreference.FRAGMENT_ARGS_NAME); } diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java new file mode 100644 index 0000000..84cabe9 --- /dev/null +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2012 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.users; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.Preference; +import android.preference.PreferenceGroup; +import android.text.TextUtils; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.android.settings.DialogCreatable; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +import java.util.HashMap; +import java.util.List; + +public class UserDetailsSettings extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener, DialogCreatable { + + private static final String TAG = "UserDetailsSettings"; + + private static final int MENU_REMOVE_USER = Menu.FIRST; + private static final int DIALOG_CONFIRM_REMOVE = 1; + + private static final String KEY_USER_NAME = "user_name"; + private static final String KEY_INSTALLED_APPS = "market_apps_category"; + private static final String KEY_SYSTEM_APPS = "system_apps_category"; + public static final String EXTRA_USER_ID = "user_id"; + + private static final String[] SYSTEM_APPS = { + "com.google.android.browser", + "com.google.android.gm", + "com.google.android.youtube" + }; + + static class AppState { + boolean dirty; + boolean enabled; + + AppState(boolean enabled) { + this.enabled = enabled; + } + } + + private HashMap<String, AppState> mAppStates = new HashMap<String, AppState>(); + private PreferenceGroup mSystemAppGroup; + private PreferenceGroup mInstalledAppGroup; + private EditTextPreference mNamePref; + + private IPackageManager mIPm; + private PackageManager mPm; + private int mUserId; + private boolean mNewUser; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + addPreferencesFromResource(R.xml.user_details); + Bundle args = getArguments(); + mNewUser = args == null || args.getInt(EXTRA_USER_ID, -1) == -1; + mUserId = mNewUser ? -1 : args.getInt(EXTRA_USER_ID, -1); + mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); + + if (mUserId == -1) { + try { + mUserId = mIPm.createUser(getString(R.string.user_new_user_name), 0).id; + } catch (RemoteException re) { + } + } + mSystemAppGroup = (PreferenceGroup) findPreference(KEY_SYSTEM_APPS); + mInstalledAppGroup = (PreferenceGroup) findPreference(KEY_INSTALLED_APPS); + mNamePref = (EditTextPreference) findPreference(KEY_USER_NAME); + mNamePref.setOnPreferenceChangeListener(this); + + setHasOptionsMenu(true); + } + + @Override + public void onResume() { + super.onResume(); + mPm = getActivity().getPackageManager(); + if (mUserId > 0) { + initExistingUser(); + } else { + initNewUser(); + } + refreshApps(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + MenuItem addAccountItem = menu.add(0, MENU_REMOVE_USER, 0, + mNewUser ? R.string.user_discard_user_menu : R.string.user_remove_user_menu); + addAccountItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int itemId = item.getItemId(); + if (itemId == MENU_REMOVE_USER) { + onRemoveUserClicked(); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + private void initExistingUser() { + List<UserInfo> users = mPm.getUsers(); + UserInfo foundUser = null; + for (UserInfo user : users) { + if (user.id == mUserId) { + foundUser = user; + break; + } + } + if (foundUser != null) { + mNamePref.setSummary(foundUser.name); + mNamePref.setText(foundUser.name); + } + } + + private void initNewUser() { + // TODO: Check if there's already a "New user" and localize + mNamePref.setText(getString(R.string.user_new_user_name)); + mNamePref.setSummary(getString(R.string.user_new_user_name)); + } + + private void onRemoveUserClicked() { + if (mNewUser) { + removeUserNow(); + } else { + showDialog(DIALOG_CONFIRM_REMOVE); + } + } + + private void removeUserNow() { + try { + mIPm.removeUser(mUserId); + } catch (RemoteException re) { + // Couldn't remove user. Shouldn't happen + Log.e(TAG, "Couldn't remove user " + mUserId + "\n" + re); + } + finish(); + } + + private void insertAppInfo(PreferenceGroup group, HashMap<String, AppState> appStateMap, + PackageInfo info, boolean defaultState) { + if (info != null) { + String pkgName = info.packageName; + String name = info.applicationInfo.loadLabel(mPm).toString(); + Drawable icon = info.applicationInfo.loadIcon(mPm); + AppState appState = appStateMap.get(info.packageName); + boolean enabled = appState == null ? defaultState : appState.enabled; + CheckBoxPreference appPref = new CheckBoxPreference(getActivity()); + appPref.setTitle(name != null ? name : pkgName); + appPref.setIcon(icon); + appPref.setChecked(enabled); + appPref.setKey(pkgName); + appPref.setPersistent(false); + appPref.setOnPreferenceChangeListener(this); + group.addPreference(appPref); + } + } + + private void refreshApps() { + mSystemAppGroup.removeAll(); + mInstalledAppGroup.removeAll(); + + boolean firstTime = mAppStates.isEmpty(); + + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + List<ResolveInfo> apps = mPm.queryIntentActivities(mainIntent, 0); + + for (ResolveInfo resolveInfo : apps) { + PackageInfo info; + try { + info = mIPm.getPackageInfo(resolveInfo.activityInfo.packageName, + 0 /* flags */, + mUserId < 0 ? 0 : mUserId); + } catch (RemoteException re) { + continue; + } + if (firstTime) { + mAppStates.put(resolveInfo.activityInfo.packageName, + new AppState(info.applicationInfo.enabled)); + } + if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + if (mSystemAppGroup.findPreference(info.packageName) != null) { + continue; + } + insertAppInfo(mSystemAppGroup, mAppStates, info, false); + } else { + if (mInstalledAppGroup.findPreference(info.packageName) != null) { + continue; + } + insertAppInfo(mInstalledAppGroup, mAppStates, info, false); + } + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference instanceof CheckBoxPreference) { + String packageName = preference.getKey(); + int newState = ((Boolean) newValue) ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; + try { + mIPm.setApplicationEnabledSetting(packageName, newState, 0, mUserId); + } catch (RemoteException re) { + Log.e(TAG, "Unable to change enabled state of package " + packageName + + " for user " + mUserId); + } + } else if (preference == mNamePref) { + String name = (String) newValue; + if (TextUtils.isEmpty(name)) { + return false; + } + try { + mIPm.updateUserName(mUserId, (String) newValue); + mNamePref.setSummary((String) newValue); + } catch (RemoteException re) { + return false; + } + } + return true; + } + + @Override + public Dialog onCreateDialog(int dialogId) { + switch (dialogId) { + case DIALOG_CONFIRM_REMOVE: + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.user_confirm_remove_title) + .setMessage(R.string.user_confirm_remove_message) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + removeUserNow(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + default: + return null; + } + } +} diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java new file mode 100644 index 0000000..9380586 --- /dev/null +++ b/src/com/android/settings/users/UserSettings.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2012 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.users; + +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceActivity; +import android.preference.PreferenceGroup; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +import java.util.List; + +public class UserSettings extends SettingsPreferenceFragment + implements OnPreferenceClickListener { + + private static final String KEY_USER_LIST = "user_list"; + private static final int MENU_ADD_USER = Menu.FIRST; + + private PreferenceGroup mUserListCategory; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + addPreferencesFromResource(R.xml.user_settings); + mUserListCategory = (PreferenceGroup) findPreference(KEY_USER_LIST); + + setHasOptionsMenu(true); + } + + @Override + public void onResume() { + super.onResume(); + updateUserList(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + MenuItem addAccountItem = menu.add(0, MENU_ADD_USER, 0, R.string.user_add_user_menu); + addAccountItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int itemId = item.getItemId(); + if (itemId == MENU_ADD_USER) { + onAddUserClicked(); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + private void onAddUserClicked() { + ((PreferenceActivity) getActivity()).startPreferencePanel( + UserDetailsSettings.class.getName(), null, R.string.user_details_title, + null, this, 0); + } + + private void updateUserList() { + List<UserInfo> users = getActivity().getPackageManager().getUsers(); + + mUserListCategory.removeAll(); + for (UserInfo user : users) { + if (user.id == 0) continue; + Preference pref = new Preference(getActivity()); + pref.setTitle(user.name); + pref.setOnPreferenceClickListener(this); + pref.setKey("id=" + user.id); + mUserListCategory.addPreference(pref); + } + } + + @Override + public boolean onPreferenceClick(Preference pref) { + String sid = pref.getKey(); + if (sid != null && sid.startsWith("id=")) { + int id = Integer.parseInt(sid.substring(3)); + Bundle args = new Bundle(); + args.putInt(UserDetailsSettings.EXTRA_USER_ID, id); + ((PreferenceActivity) getActivity()).startPreferencePanel( + UserDetailsSettings.class.getName(), + args, 0, pref.getTitle(), this, 0); + return true; + } + return false; + } +} diff --git a/src/com/android/settings/vpn2/VpnDialog.java b/src/com/android/settings/vpn2/VpnDialog.java index aaae887..ca85d99 100644 --- a/src/com/android/settings/vpn2/VpnDialog.java +++ b/src/com/android/settings/vpn2/VpnDialog.java @@ -102,8 +102,10 @@ class VpnDialog extends AlertDialog implements TextWatcher, mName.setText(mProfile.name); mType.setSelection(mProfile.type); mServer.setText(mProfile.server); - mUsername.setText(mProfile.username); - mPassword.setText(mProfile.password); + if (mProfile.saveLogin) { + mUsername.setText(mProfile.username); + mPassword.setText(mProfile.password); + } mSearchDomains.setText(mProfile.searchDomains); mDnsServers.setText(mProfile.dnsServers); mRoutes.setText(mProfile.routes); @@ -111,7 +113,7 @@ class VpnDialog extends AlertDialog implements TextWatcher, mL2tpSecret.setText(mProfile.l2tpSecret); mIpsecIdentifier.setText(mProfile.ipsecIdentifier); mIpsecSecret.setText(mProfile.ipsecSecret); - loadCertificates(mIpsecUserCert, Credentials.USER_CERTIFICATE, + loadCertificates(mIpsecUserCert, Credentials.USER_PRIVATE_KEY, 0, mProfile.ipsecUserCert); loadCertificates(mIpsecCaCert, Credentials.CA_CERTIFICATE, R.string.vpn_no_ca_cert, mProfile.ipsecCaCert); diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java index 655306a..a26623b 100644 --- a/src/com/android/settings/vpn2/VpnSettings.java +++ b/src/com/android/settings/vpn2/VpnSettings.java @@ -38,6 +38,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.Toast; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; @@ -324,10 +325,12 @@ public class VpnSettings extends SettingsPreferenceFragment implements private String[] getDefaultNetwork() throws Exception { LinkProperties network = mService.getActiveLinkProperties(); if (network == null) { + Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show(); throw new IllegalStateException("Network is not available"); } String interfaze = network.getInterfaceName(); if (interfaze == null) { + Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show(); throw new IllegalStateException("Cannot get the default interface"); } String gateway = null; @@ -339,6 +342,7 @@ public class VpnSettings extends SettingsPreferenceFragment implements } } if (gateway == null) { + Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show(); throw new IllegalStateException("Cannot get the default gateway"); } return new String[] {interfaze, gateway}; @@ -356,9 +360,12 @@ public class VpnSettings extends SettingsPreferenceFragment implements String caCert = ""; String serverCert = ""; if (!profile.ipsecUserCert.isEmpty()) { - byte[] value = mKeyStore.get(Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert); - privateKey = (value == null) ? null : new String(value, Charsets.UTF_8); - value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert); + /* + * VPN has a special exception in keystore to allow it to use system + * UID certs. + */ + privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert; + byte[] value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert); userCert = (value == null) ? null : new String(value, Charsets.UTF_8); } if (!profile.ipsecCaCert.isEmpty()) { @@ -370,7 +377,7 @@ public class VpnSettings extends SettingsPreferenceFragment implements serverCert = (value == null) ? null : new String(value, Charsets.UTF_8); } if (privateKey == null || userCert == null || caCert == null || serverCert == null) { - // TODO: find out a proper way to handle this. Delete these keys? + Toast.makeText(getActivity(), R.string.vpn_missing_cert, Toast.LENGTH_LONG).show(); throw new IllegalStateException("Cannot load credentials"); } @@ -457,6 +464,11 @@ public class VpnSettings extends SettingsPreferenceFragment implements } } + @Override + protected int getHelpResource() { + return R.string.help_url_vpn; + } + private class VpnPreference extends Preference { private VpnProfile mProfile; private int mState = -1; diff --git a/src/com/android/settings/widget/ChartDataUsageView.java b/src/com/android/settings/widget/ChartDataUsageView.java index f10ea5e..19de100 100644 --- a/src/com/android/settings/widget/ChartDataUsageView.java +++ b/src/com/android/settings/widget/ChartDataUsageView.java @@ -16,6 +16,9 @@ package com.android.settings.widget; +import static android.net.TrafficStats.GB_IN_BYTES; +import static android.net.TrafficStats.MB_IN_BYTES; + import android.content.Context; import android.content.res.Resources; import android.net.NetworkPolicy; @@ -26,6 +29,7 @@ import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.format.DateUtils; +import android.text.format.Time; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; @@ -34,16 +38,15 @@ import com.android.internal.util.Objects; import com.android.settings.R; import com.android.settings.widget.ChartSweepView.OnSweepListener; +import java.util.Arrays; +import java.util.Calendar; + /** * Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along * with {@link ChartSweepView} for inspection ranges and warning/limits. */ public class ChartDataUsageView extends ChartView { - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; - private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; - private static final int MSG_UPDATE_AXIS = 100; private static final long DELAY_MILLIS = 250; @@ -234,7 +237,7 @@ public class ChartDataUsageView extends ChartView { final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue()); final long maxSeries = Math.max(mSeries.getMaxVisible(), mDetailSeries.getMaxVisible()); final long maxVisible = Math.max(maxSeries, maxSweep) * 12 / 10; - final long maxDefault = Math.max(maxVisible, 2 * GB_IN_BYTES); + final long maxDefault = Math.max(maxVisible, 50 * MB_IN_BYTES); newMax = Math.max(maxDefault, newMax); // only invalidate when vertMax actually changed @@ -293,7 +296,7 @@ public class ChartDataUsageView extends ChartView { } private OnSweepListener mHorizListener = new OnSweepListener() { - /** {@inheritDoc} */ + @Override public void onSweep(ChartSweepView sweep, boolean sweepDone) { updatePrimaryRange(); @@ -303,7 +306,7 @@ public class ChartDataUsageView extends ChartView { } } - /** {@inheritDoc} */ + @Override public void requestEdit(ChartSweepView sweep) { // ignored } @@ -321,7 +324,7 @@ public class ChartDataUsageView extends ChartView { } private OnSweepListener mVertListener = new OnSweepListener() { - /** {@inheritDoc} */ + @Override public void onSweep(ChartSweepView sweep, boolean sweepDone) { if (sweepDone) { clearUpdateAxisDelayed(sweep); @@ -338,7 +341,7 @@ public class ChartDataUsageView extends ChartView { } } - /** {@inheritDoc} */ + @Override public void requestEdit(ChartSweepView sweep) { if (sweep == mSweepWarning && mListener != null) { mListener.requestWarningEdit(); @@ -450,7 +453,7 @@ public class ChartDataUsageView extends ChartView { } public static class TimeAxis implements ChartAxis { - private static final long TICK_INTERVAL = DateUtils.DAY_IN_MILLIS * 7; + private static final int FIRST_DAY_OF_WEEK = Calendar.getInstance().getFirstDayOfWeek() - 1; private long mMin; private long mMax; @@ -466,7 +469,7 @@ public class ChartDataUsageView extends ChartView { return Objects.hashCode(mMin, mMax, mSize); } - /** {@inheritDoc} */ + @Override public boolean setBounds(long min, long max) { if (mMin != min || mMax != max) { mMin = min; @@ -477,7 +480,7 @@ public class ChartDataUsageView extends ChartView { } } - /** {@inheritDoc} */ + @Override public boolean setSize(float size) { if (mSize != size) { mSize = size; @@ -487,35 +490,49 @@ public class ChartDataUsageView extends ChartView { } } - /** {@inheritDoc} */ + @Override public float convertToPoint(long value) { return (mSize * (value - mMin)) / (mMax - mMin); } - /** {@inheritDoc} */ + @Override public long convertToValue(float point) { return (long) (mMin + ((point * (mMax - mMin)) / mSize)); } - /** {@inheritDoc} */ + @Override public long buildLabel(Resources res, SpannableStringBuilder builder, long value) { // TODO: convert to better string builder.replace(0, builder.length(), Long.toString(value)); return value; } - /** {@inheritDoc} */ + @Override public float[] getTickPoints() { - // tick mark for every week - final int tickCount = (int) ((mMax - mMin) / TICK_INTERVAL); - final float[] tickPoints = new float[tickCount]; - for (int i = 0; i < tickCount; i++) { - tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * (i + 1))); + final float[] ticks = new float[32]; + int i = 0; + + // tick mark for first day of each week + final Time time = new Time(); + time.set(mMax); + time.monthDay -= time.weekDay - FIRST_DAY_OF_WEEK; + time.hour = time.minute = time.second = 0; + + time.normalize(true); + long timeMillis = time.toMillis(true); + while (timeMillis > mMin) { + if (timeMillis <= mMax) { + ticks[i++] = convertToPoint(timeMillis); + } + time.monthDay -= 7; + time.normalize(true); + timeMillis = time.toMillis(true); } - return tickPoints; + + return Arrays.copyOf(ticks, i); } - /** {@inheritDoc} */ + @Override public int shouldAdjustAxis(long value) { // time axis never adjusts return 0; @@ -527,12 +544,14 @@ public class ChartDataUsageView extends ChartView { private long mMax; private float mSize; + private static final boolean LOG_SCALE = false; + @Override public int hashCode() { return Objects.hashCode(mMin, mMax, mSize); } - /** {@inheritDoc} */ + @Override public boolean setBounds(long min, long max) { if (mMin != min || mMax != max) { mMin = min; @@ -543,7 +562,7 @@ public class ChartDataUsageView extends ChartView { } } - /** {@inheritDoc} */ + @Override public boolean setSize(float size) { if (mSize != size) { mSize = size; @@ -553,27 +572,35 @@ public class ChartDataUsageView extends ChartView { } } - /** {@inheritDoc} */ + @Override public float convertToPoint(long value) { - // derived polynomial fit to make lower values more visible - final double normalized = ((double) value - mMin) / (mMax - mMin); - final double fraction = Math.pow( - 10, 0.36884343106175121463 * Math.log10(normalized) + -0.04328199452018252624); - return (float) (fraction * mSize); + if (LOG_SCALE) { + // derived polynomial fit to make lower values more visible + final double normalized = ((double) value - mMin) / (mMax - mMin); + final double fraction = Math.pow(10, + 0.36884343106175121463 * Math.log10(normalized) + -0.04328199452018252624); + return (float) (fraction * mSize); + } else { + return (mSize * (value - mMin)) / (mMax - mMin); + } } - /** {@inheritDoc} */ + @Override public long convertToValue(float point) { - final double normalized = point / mSize; - final double fraction = 1.3102228476089056629 - * Math.pow(normalized, 2.7111774693164631640); - return (long) (mMin + (fraction * (mMax - mMin))); + if (LOG_SCALE) { + final double normalized = point / mSize; + final double fraction = 1.3102228476089056629 + * Math.pow(normalized, 2.7111774693164631640); + return (long) (mMin + (fraction * (mMax - mMin))); + } else { + return (long) (mMin + ((point * (mMax - mMin)) / mSize)); + } } private static final Object sSpanSize = new Object(); private static final Object sSpanUnit = new Object(); - /** {@inheritDoc} */ + @Override public long buildLabel(Resources res, SpannableStringBuilder builder, long value) { final CharSequence unit; @@ -598,26 +625,18 @@ public class ChartDataUsageView extends ChartView { resultRounded = unitFactor * Math.round(result); } - final int[] sizeBounds = findOrCreateSpan(builder, sSpanSize, "^1"); - builder.replace(sizeBounds[0], sizeBounds[1], size); - final int[] unitBounds = findOrCreateSpan(builder, sSpanUnit, "^2"); - builder.replace(unitBounds[0], unitBounds[1], unit); + setText(builder, sSpanSize, size, "^1"); + setText(builder, sSpanUnit, unit, "^2"); return (long) resultRounded; } - /** {@inheritDoc} */ + @Override public float[] getTickPoints() { final long range = mMax - mMin; - final long tickJump; - if (range < 6 * GB_IN_BYTES) { - tickJump = 256 * MB_IN_BYTES; - } else if (range < 12 * GB_IN_BYTES) { - tickJump = 512 * MB_IN_BYTES; - } else { - tickJump = 1 * GB_IN_BYTES; - } + // target about 16 ticks on screen, rounded to nearest power of 2 + final long tickJump = roundUpToPowerOfTwo(range / 16); final int tickCount = (int) (range / tickJump); final float[] tickPoints = new float[tickCount]; long value = mMin; @@ -629,7 +648,7 @@ public class ChartDataUsageView extends ChartView { return tickPoints; } - /** {@inheritDoc} */ + @Override public int shouldAdjustAxis(long value) { final float point = convertToPoint(value); if (point < mSize * 0.1) { @@ -642,8 +661,8 @@ public class ChartDataUsageView extends ChartView { } } - private static int[] findOrCreateSpan( - SpannableStringBuilder builder, Object key, CharSequence bootstrap) { + private static void setText( + SpannableStringBuilder builder, Object key, CharSequence text, String bootstrap) { int start = builder.getSpanStart(key); int end = builder.getSpanEnd(key); if (start == -1) { @@ -651,7 +670,24 @@ public class ChartDataUsageView extends ChartView { end = start + bootstrap.length(); builder.setSpan(key, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE); } - return new int[] { start, end }; + builder.replace(start, end, text); } + private static long roundUpToPowerOfTwo(long i) { + // NOTE: borrowed from Hashtable.roundUpToPowerOfTwo() + + i--; // If input is a power of two, shift its high-order bit right + + // "Smear" the high-order bit all the way to the right + i |= i >>> 1; + i |= i >>> 2; + i |= i >>> 4; + i |= i >>> 8; + i |= i >>> 16; + i |= i >>> 32; + + i++; + + return i > 0 ? i : Long.MAX_VALUE; + } } diff --git a/src/com/android/settings/widget/ChartGridView.java b/src/com/android/settings/widget/ChartGridView.java index b930c62..f358ada 100644 --- a/src/com/android/settings/widget/ChartGridView.java +++ b/src/com/android/settings/widget/ChartGridView.java @@ -16,6 +16,8 @@ package com.android.settings.widget; +import static com.android.settings.DataUsageSummary.formatDateRange; + import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -30,7 +32,6 @@ import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; -import com.android.settings.DataUsageSummary; import com.android.settings.R; import com.google.common.base.Preconditions; @@ -82,8 +83,8 @@ public class ChartGridView extends View { void setBounds(long start, long end) { final Context context = getContext(); - mLayoutStart = makeLayout(DataUsageSummary.formatDateRange(context, start, start, true)); - mLayoutEnd = makeLayout(DataUsageSummary.formatDateRange(context, end, end, true)); + mLayoutStart = makeLayout(formatDateRange(context, start, start)); + mLayoutEnd = makeLayout(formatDateRange(context, end, end)); invalidate(); } diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java index 7a4617b..5a37bfc 100644 --- a/src/com/android/settings/widget/ChartNetworkSeriesView.java +++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java @@ -44,6 +44,8 @@ public class ChartNetworkSeriesView extends View { private static final String TAG = "ChartNetworkSeriesView"; private static final boolean LOGD = false; + private static final boolean ESTIMATE_ENABLED = false; + private ChartAxis mHoriz; private ChartAxis mVert; @@ -252,36 +254,38 @@ public class ChartNetworkSeriesView extends View { mMax = totalData; - // build estimated data - mPathEstimate.moveTo(lastX, lastY); + if (ESTIMATE_ENABLED) { + // build estimated data + mPathEstimate.moveTo(lastX, lastY); - final long now = System.currentTimeMillis(); - final long bucketDuration = mStats.getBucketDuration(); + final long now = System.currentTimeMillis(); + final long bucketDuration = mStats.getBucketDuration(); - // long window is average over two weeks - entry = mStats.getValues(lastTime - WEEK_IN_MILLIS * 2, lastTime, now, entry); - final long longWindow = (entry.rxBytes + entry.txBytes) * bucketDuration - / entry.bucketDuration; + // long window is average over two weeks + entry = mStats.getValues(lastTime - WEEK_IN_MILLIS * 2, lastTime, now, entry); + final long longWindow = (entry.rxBytes + entry.txBytes) * bucketDuration + / entry.bucketDuration; - long futureTime = 0; - while (lastX < width) { - futureTime += bucketDuration; + long futureTime = 0; + while (lastX < width) { + futureTime += bucketDuration; - // short window is day average last week - final long lastWeekTime = lastTime - WEEK_IN_MILLIS + (futureTime % WEEK_IN_MILLIS); - entry = mStats.getValues(lastWeekTime - DAY_IN_MILLIS, lastWeekTime, now, entry); - final long shortWindow = (entry.rxBytes + entry.txBytes) * bucketDuration - / entry.bucketDuration; + // short window is day average last week + final long lastWeekTime = lastTime - WEEK_IN_MILLIS + (futureTime % WEEK_IN_MILLIS); + entry = mStats.getValues(lastWeekTime - DAY_IN_MILLIS, lastWeekTime, now, entry); + final long shortWindow = (entry.rxBytes + entry.txBytes) * bucketDuration + / entry.bucketDuration; - totalData += (longWindow * 7 + shortWindow * 3) / 10; + totalData += (longWindow * 7 + shortWindow * 3) / 10; - lastX = mHoriz.convertToPoint(lastTime + futureTime); - lastY = mVert.convertToPoint(totalData); + lastX = mHoriz.convertToPoint(lastTime + futureTime); + lastY = mVert.convertToPoint(totalData); - mPathEstimate.lineTo(lastX, lastY); - } + mPathEstimate.lineTo(lastX, lastY); + } - mMaxEstimate = totalData; + mMaxEstimate = totalData; + } invalidate(); } @@ -291,7 +295,7 @@ public class ChartNetworkSeriesView extends View { } public void setEstimateVisible(boolean estimateVisible) { - mEstimateVisible = estimateVisible; + mEstimateVisible = ESTIMATE_ENABLED ? estimateVisible : false; invalidate(); } diff --git a/src/com/android/settings/widget/InvertedChartAxis.java b/src/com/android/settings/widget/InvertedChartAxis.java index 2d820d9..6875749 100644 --- a/src/com/android/settings/widget/InvertedChartAxis.java +++ b/src/com/android/settings/widget/InvertedChartAxis.java @@ -30,33 +30,33 @@ public class InvertedChartAxis implements ChartAxis { mWrapped = wrapped; } - /** {@inheritDoc} */ + @Override public boolean setBounds(long min, long max) { return mWrapped.setBounds(min, max); } - /** {@inheritDoc} */ + @Override public boolean setSize(float size) { mSize = size; return mWrapped.setSize(size); } - /** {@inheritDoc} */ + @Override public float convertToPoint(long value) { return mSize - mWrapped.convertToPoint(value); } - /** {@inheritDoc} */ + @Override public long convertToValue(float point) { return mWrapped.convertToValue(mSize - point); } - /** {@inheritDoc} */ + @Override public long buildLabel(Resources res, SpannableStringBuilder builder, long value) { return mWrapped.buildLabel(res, builder, value); } - /** {@inheritDoc} */ + @Override public float[] getTickPoints() { final float[] points = mWrapped.getTickPoints(); for (int i = 0; i < points.length; i++) { @@ -65,7 +65,7 @@ public class InvertedChartAxis implements ChartAxis { return points; } - /** {@inheritDoc} */ + @Override public int shouldAdjustAxis(long value) { return mWrapped.shouldAdjustAxis(value); } diff --git a/src/com/android/settings/widget/SettingsAppWidgetProvider.java b/src/com/android/settings/widget/SettingsAppWidgetProvider.java index 656d072..1d411f7 100644 --- a/src/com/android/settings/widget/SettingsAppWidgetProvider.java +++ b/src/com/android/settings/widget/SettingsAppWidgetProvider.java @@ -97,9 +97,9 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { /** * Minimum and maximum brightnesses. Don't go to 0 since that makes the display unusable */ - private static final int MINIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_DIM + 10; - private static final int MAXIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_ON; - private static final int DEFAULT_BACKLIGHT = (int) (android.os.Power.BRIGHTNESS_ON * 0.4f); + private static final int MINIMUM_BACKLIGHT = android.os.PowerManager.BRIGHTNESS_DIM + 10; + private static final int MAXIMUM_BACKLIGHT = android.os.PowerManager.BRIGHTNESS_ON; + private static final int DEFAULT_BACKLIGHT = (int) (android.os.PowerManager.BRIGHTNESS_ON * 0.4f); /** Minimum brightness at which the indicator is shown at half-full and ON */ private static final int HALF_BRIGHTNESS_THRESHOLD = (int) (0.3 * MAXIMUM_BACKLIGHT); /** Minimum brightness at which the indicator is shown at full */ @@ -605,9 +605,10 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { @Override public void onEnabled(Context context) { + Class clazz = com.android.settings.widget.SettingsAppWidgetProvider.class; PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting( - new ComponentName("com.android.settings", ".widget.SettingsAppWidgetProvider"), + new ComponentName(context.getPackageName(), clazz.getName()), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); checkObserver(context); @@ -618,7 +619,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { Class clazz = com.android.settings.widget.SettingsAppWidgetProvider.class; PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting( - new ComponentName("com.android.settings", ".widget.SettingsAppWidgetProvider"), + new ComponentName(context.getPackageName(), clazz.getName()), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); if (sSettingsObserver != null) { diff --git a/src/com/android/settings/wifi/AdvancedWifiSettings.java b/src/com/android/settings/wifi/AdvancedWifiSettings.java index c213512..bb50d2a 100644 --- a/src/com/android/settings/wifi/AdvancedWifiSettings.java +++ b/src/com/android/settings/wifi/AdvancedWifiSettings.java @@ -43,7 +43,7 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment private static final String KEY_FREQUENCY_BAND = "frequency_band"; private static final String KEY_NOTIFY_OPEN_NETWORKS = "notify_open_networks"; private static final String KEY_SLEEP_POLICY = "sleep_policy"; - private static final String KEY_ENABLE_WIFI_WATCHDOG = "wifi_enable_watchdog_service"; + private static final String KEY_POOR_NETWORK_DETECTION = "wifi_poor_network_detection"; private WifiManager mWifiManager; @@ -73,14 +73,15 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1); notifyOpenNetworks.setEnabled(mWifiManager.isWifiEnabled()); - CheckBoxPreference watchdogEnabled = - (CheckBoxPreference) findPreference(KEY_ENABLE_WIFI_WATCHDOG); - if (watchdogEnabled != null) { - watchdogEnabled.setChecked(Secure.getInt(getContentResolver(), - Secure.WIFI_WATCHDOG_ON, 1) == 1); - - //TODO: Bring this back after changing watchdog behavior - getPreferenceScreen().removePreference(watchdogEnabled); + CheckBoxPreference poorNetworkDetection = + (CheckBoxPreference) findPreference(KEY_POOR_NETWORK_DETECTION); + if (poorNetworkDetection != null) { + if (Utils.isWifiOnly(getActivity())) { + getPreferenceScreen().removePreference(poorNetworkDetection); + } else { + poorNetworkDetection.setChecked(Secure.getInt(getContentResolver(), + Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, 1) == 1); + } } ListPreference frequencyPref = (ListPreference) findPreference(KEY_FREQUENCY_BAND); @@ -143,9 +144,9 @@ public class AdvancedWifiSettings extends SettingsPreferenceFragment Secure.putInt(getContentResolver(), Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, ((CheckBoxPreference) preference).isChecked() ? 1 : 0); - } else if (KEY_ENABLE_WIFI_WATCHDOG.equals(key)) { + } else if (KEY_POOR_NETWORK_DETECTION.equals(key)) { Secure.putInt(getContentResolver(), - Secure.WIFI_WATCHDOG_ON, + Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, ((CheckBoxPreference) preference).isChecked() ? 1 : 0); } else { return super.onPreferenceTreeClick(screen, preference); diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java index c64a225..2598a0e 100644 --- a/src/com/android/settings/wifi/WifiConfigController.java +++ b/src/com/android/settings/wifi/WifiConfigController.java @@ -33,12 +33,13 @@ import android.net.wifi.WifiConfiguration.KeyMgmt; import android.net.wifi.WifiConfiguration.ProxySettings; import android.net.wifi.WifiConfiguration.Status; import android.net.wifi.WifiInfo; -import android.net.wifi.WpsInfo; +import android.os.Handler; import android.security.Credentials; import android.security.KeyStore; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; +import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -46,6 +47,7 @@ import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; +import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; @@ -61,7 +63,9 @@ import java.util.Iterator; */ public class WifiConfigController implements TextWatcher, View.OnClickListener, AdapterView.OnItemSelectedListener { - private static final String KEYSTORE_SPACE = "keystore://"; + private static final String KEYSTORE_SPACE = WifiConfiguration.KEYSTORE_URI; + + private static final String PHASE2_PREFIX = "auth="; private final WifiConfigUiBase mConfigUi; private final View mView; @@ -87,12 +91,6 @@ public class WifiConfigController implements TextWatcher, private static final int DHCP = 0; private static final int STATIC_IP = 1; - /* These values come from "wifi_network_setup" resource array */ - public static final int MANUAL = 0; - public static final int WPS_PBC = 1; - public static final int WPS_KEYPAD = 2; - public static final int WPS_DISPLAY = 3; - /* These values come from "wifi_proxy_settings" resource array */ public static final int PROXY_NONE = 0; public static final int PROXY_STATIC = 1; @@ -105,7 +103,6 @@ public class WifiConfigController implements TextWatcher, private static final String TAG = "WifiConfigController"; - private Spinner mNetworkSetupSpinner; private Spinner mIpSettingsSpinner; private TextView mIpAddressView; private TextView mGatewayView; @@ -125,12 +122,16 @@ public class WifiConfigController implements TextWatcher, // True when this instance is used in SetupWizard XL context. private final boolean mInXlSetupWizard; + private final Handler mTextViewChangedHandler; + static boolean requireKeyStore(WifiConfiguration config) { if (config == null) { return false; } - String values[] = {config.ca_cert.value(), config.client_cert.value(), - config.private_key.value()}; + if (!TextUtils.isEmpty(config.key_id.value())) { + return true; + } + String values[] = { config.ca_cert.value(), config.client_cert.value() }; for (String value : values) { if (value != null && value.startsWith(KEYSTORE_SPACE)) { return true; @@ -150,9 +151,15 @@ public class WifiConfigController implements TextWatcher, accessPoint.security; mEdit = edit; + mTextViewChangedHandler = new Handler(); final Context context = mConfigUi.getContext(); final Resources resources = context.getResources(); + mIpSettingsSpinner = (Spinner) mView.findViewById(R.id.ip_settings); + mIpSettingsSpinner.setOnItemSelectedListener(this); + mProxySettingsSpinner = (Spinner) mView.findViewById(R.id.proxy_settings); + mProxySettingsSpinner.setOnItemSelectedListener(this); + if (mAccessPoint == null) { // new network mConfigUi.setTitle(R.string.wifi_add_network); @@ -172,15 +179,16 @@ public class WifiConfigController implements TextWatcher, } else { mView.findViewById(R.id.type).setVisibility(View.VISIBLE); } + + showIpConfigFields(); + showProxyFields(); + mView.findViewById(R.id.wifi_advanced_toggle).setVisibility(View.VISIBLE); + mView.findViewById(R.id.wifi_advanced_togglebox).setOnClickListener(this); + mConfigUi.setSubmitButton(context.getString(R.string.wifi_save)); } else { mConfigUi.setTitle(mAccessPoint.ssid); - mIpSettingsSpinner = (Spinner) mView.findViewById(R.id.ip_settings); - mIpSettingsSpinner.setOnItemSelectedListener(this); - mProxySettingsSpinner = (Spinner) mView.findViewById(R.id.proxy_settings); - mProxySettingsSpinner.setOnItemSelectedListener(this); - ViewGroup group = (ViewGroup) mView.findViewById(R.id.info); DetailedState state = mAccessPoint.getState(); @@ -222,18 +230,6 @@ public class WifiConfigController implements TextWatcher, } else { mProxySettingsSpinner.setSelection(PROXY_NONE); } - - if (config.status == Status.DISABLED && - config.disableReason == WifiConfiguration.DISABLED_DNS_FAILURE) { - addRow(group, R.string.wifi_disabled_heading, - context.getString(R.string.wifi_disabled_help)); - } - - } - - /* Show network setup options only for a new network */ - if (mAccessPoint.networkId == INVALID_NETWORK_ID && mAccessPoint.wpsAvailable) { - showNetworkSetupFields(); } if (mAccessPoint.networkId == INVALID_NETWORK_ID || mEdit) { @@ -277,15 +273,14 @@ public class WifiConfigController implements TextWatcher, } /* show submit button if password, ip and proxy settings are valid */ - private void enableSubmitIfAppropriate() { + void enableSubmitIfAppropriate() { Button submit = mConfigUi.getSubmitButton(); if (submit == null) return; - boolean enabled = false; + boolean enabled = false; boolean passwordInvalid = false; - /* Check password invalidity for manual network set up alone */ - if (chosenNetworkSetupMethod() == MANUAL && + if (mPasswordView != null && ((mAccessPointSecurity == AccessPoint.SECURITY_WEP && mPasswordView.length() == 0) || (mAccessPointSecurity == AccessPoint.SECURITY_PSK && mPasswordView.length() < 8))) { passwordInvalid = true; @@ -364,16 +359,19 @@ public class WifiConfigController implements TextWatcher, config.eap.setValue((String) mEapMethodSpinner.getSelectedItem()); config.phase2.setValue((mPhase2Spinner.getSelectedItemPosition() == 0) ? "" : - "auth=" + mPhase2Spinner.getSelectedItem()); + PHASE2_PREFIX + mPhase2Spinner.getSelectedItem()); config.ca_cert.setValue((mEapCaCertSpinner.getSelectedItemPosition() == 0) ? "" : KEYSTORE_SPACE + Credentials.CA_CERTIFICATE + (String) mEapCaCertSpinner.getSelectedItem()); config.client_cert.setValue((mEapUserCertSpinner.getSelectedItemPosition() == 0) ? "" : KEYSTORE_SPACE + Credentials.USER_CERTIFICATE + (String) mEapUserCertSpinner.getSelectedItem()); - config.private_key.setValue((mEapUserCertSpinner.getSelectedItemPosition() == 0) ? - "" : KEYSTORE_SPACE + Credentials.USER_PRIVATE_KEY + + final boolean isEmptyKeyId = (mEapUserCertSpinner.getSelectedItemPosition() == 0); + config.key_id.setValue(isEmptyKeyId ? "" : Credentials.USER_PRIVATE_KEY + (String) mEapUserCertSpinner.getSelectedItem()); + config.engine.setValue(isEmptyKeyId ? WifiConfiguration.ENGINE_DISABLE : + WifiConfiguration.ENGINE_ENABLE); + config.engine_id.setValue(isEmptyKeyId ? "" : WifiConfiguration.KEYSTORE_ENGINE_ID); config.identity.setValue((mEapIdentityView.length() == 0) ? "" : mEapIdentityView.getText().toString()); config.anonymous_identity.setValue((mEapAnonymousView.length() == 0) ? "" : @@ -411,7 +409,7 @@ public class WifiConfigController implements TextWatcher, mProxySettingsSpinner.getSelectedItemPosition() == PROXY_STATIC) ? ProxySettings.STATIC : ProxySettings.NONE; - if (mProxySettings == ProxySettings.STATIC) { + if (mProxySettings == ProxySettings.STATIC && mProxyHostView != null) { String host = mProxyHostView.getText().toString(); String portStr = mProxyPortView.getText().toString(); String exclusionList = mProxyExclusionListView.getText().toString(); @@ -434,7 +432,11 @@ public class WifiConfigController implements TextWatcher, } private int validateIpConfigFields(LinkProperties linkProperties) { + if (mIpAddressView == null) return 0; + String ipAddr = mIpAddressView.getText().toString(); + if (TextUtils.isEmpty(ipAddr)) return R.string.wifi_ip_settings_invalid_ip_address; + InetAddress inetAddr = null; try { inetAddr = NetworkUtils.numericToInetAddress(ipAddr); @@ -445,31 +447,52 @@ public class WifiConfigController implements TextWatcher, int networkPrefixLength = -1; try { networkPrefixLength = Integer.parseInt(mNetworkPrefixLengthView.getText().toString()); + if (networkPrefixLength < 0 || networkPrefixLength > 32) { + return R.string.wifi_ip_settings_invalid_network_prefix_length; + } + linkProperties.addLinkAddress(new LinkAddress(inetAddr, networkPrefixLength)); } catch (NumberFormatException e) { - // Use -1 - } - if (networkPrefixLength < 0 || networkPrefixLength > 32) { - return R.string.wifi_ip_settings_invalid_network_prefix_length; + // Set the hint as default after user types in ip address + mNetworkPrefixLengthView.setText(mConfigUi.getContext().getString( + R.string.wifi_network_prefix_length_hint)); } - linkProperties.addLinkAddress(new LinkAddress(inetAddr, networkPrefixLength)); String gateway = mGatewayView.getText().toString(); - InetAddress gatewayAddr = null; - try { - gatewayAddr = NetworkUtils.numericToInetAddress(gateway); - } catch (IllegalArgumentException e) { - return R.string.wifi_ip_settings_invalid_gateway; + if (TextUtils.isEmpty(gateway)) { + try { + //Extract a default gateway from IP address + InetAddress netPart = NetworkUtils.getNetworkPart(inetAddr, networkPrefixLength); + byte[] addr = netPart.getAddress(); + addr[addr.length-1] = 1; + mGatewayView.setText(InetAddress.getByAddress(addr).getHostAddress()); + } catch (RuntimeException ee) { + } catch (java.net.UnknownHostException u) { + } + } else { + InetAddress gatewayAddr = null; + try { + gatewayAddr = NetworkUtils.numericToInetAddress(gateway); + } catch (IllegalArgumentException e) { + return R.string.wifi_ip_settings_invalid_gateway; + } + linkProperties.addRoute(new RouteInfo(gatewayAddr)); } - linkProperties.addRoute(new RouteInfo(gatewayAddr)); String dns = mDns1View.getText().toString(); InetAddress dnsAddr = null; - try { - dnsAddr = NetworkUtils.numericToInetAddress(dns); - } catch (IllegalArgumentException e) { - return R.string.wifi_ip_settings_invalid_dns; + + if (TextUtils.isEmpty(dns)) { + //If everything else is valid, provide hint as a default option + mDns1View.setText(mConfigUi.getContext().getString(R.string.wifi_dns1_hint)); + } else { + try { + dnsAddr = NetworkUtils.numericToInetAddress(dns); + } catch (IllegalArgumentException e) { + return R.string.wifi_ip_settings_invalid_dns; + } + linkProperties.addDns(dnsAddr); } - linkProperties.addDns(dnsAddr); + if (mDns2View.length() > 0) { dns = mDns2View.getText().toString(); try { @@ -482,39 +505,6 @@ public class WifiConfigController implements TextWatcher, return 0; } - int chosenNetworkSetupMethod() { - if (mNetworkSetupSpinner != null) { - return mNetworkSetupSpinner.getSelectedItemPosition(); - } - return MANUAL; - } - - WpsInfo getWpsConfig() { - WpsInfo config = new WpsInfo(); - switch (mNetworkSetupSpinner.getSelectedItemPosition()) { - case WPS_PBC: - config.setup = WpsInfo.PBC; - break; - case WPS_KEYPAD: - config.setup = WpsInfo.KEYPAD; - break; - case WPS_DISPLAY: - config.setup = WpsInfo.DISPLAY; - break; - default: - config.setup = WpsInfo.INVALID; - Log.e(TAG, "WPS not selected type"); - return config; - } - config.pin = ((TextView) mView.findViewById(R.id.wps_pin)).getText().toString(); - config.BSSID = (mAccessPoint != null) ? mAccessPoint.bssid : null; - - config.proxySettings = mProxySettings; - config.ipAssignment = mIpAssignment; - config.linkProperties = new LinkProperties(mLinkProperties); - return config; - } - private void showSecurityFields() { if (mInXlSetupWizard) { // Note: XL SetupWizard won't hide "EAP" settings here. @@ -560,11 +550,18 @@ public class WifiConfigController implements TextWatcher, if (mAccessPoint != null && mAccessPoint.networkId != INVALID_NETWORK_ID) { WifiConfiguration config = mAccessPoint.getConfig(); setSelection(mEapMethodSpinner, config.eap.value()); - setSelection(mPhase2Spinner, config.phase2.value()); - setCertificate(mEapCaCertSpinner, Credentials.CA_CERTIFICATE, + + final String phase2Method = config.phase2.value(); + if (phase2Method != null && phase2Method.startsWith(PHASE2_PREFIX)) { + setSelection(mPhase2Spinner, phase2Method.substring(PHASE2_PREFIX.length())); + } else { + setSelection(mPhase2Spinner, phase2Method); + } + + setCertificate(mEapCaCertSpinner, KEYSTORE_SPACE + Credentials.CA_CERTIFICATE, config.ca_cert.value()); setCertificate(mEapUserCertSpinner, Credentials.USER_PRIVATE_KEY, - config.private_key.value()); + config.key_id.value()); mEapIdentityView.setText(config.identity.value()); mEapAnonymousView.setText(config.anonymous_identity.value()); } @@ -585,33 +582,6 @@ public class WifiConfigController implements TextWatcher, mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE); } } - - private void showNetworkSetupFields() { - mView.findViewById(R.id.setup_fields).setVisibility(View.VISIBLE); - - if (mNetworkSetupSpinner == null) { - mNetworkSetupSpinner = (Spinner) mView.findViewById(R.id.network_setup); - mNetworkSetupSpinner.setOnItemSelectedListener(this); - } - - int pos = mNetworkSetupSpinner.getSelectedItemPosition(); - - /* Show pin text input if needed */ - if (pos == WPS_KEYPAD) { - mView.findViewById(R.id.wps_fields).setVisibility(View.VISIBLE); - } else { - mView.findViewById(R.id.wps_fields).setVisibility(View.GONE); - } - - /* show/hide manual security fields appropriately */ - if ((pos == WPS_DISPLAY) || (pos == WPS_KEYPAD) - || (pos == WPS_PBC)) { - mView.findViewById(R.id.security_fields).setVisibility(View.GONE); - } else { - mView.findViewById(R.id.security_fields).setVisibility(View.VISIBLE); - } - - } private void showIpConfigFields() { WifiConfiguration config = null; @@ -724,7 +694,6 @@ public class WifiConfigController implements TextWatcher, } private void setCertificate(Spinner spinner, String prefix, String cert) { - prefix = KEYSTORE_SPACE + prefix; if (cert != null && cert.startsWith(prefix)) { setSelection(spinner, cert.substring(prefix.length())); } @@ -749,7 +718,11 @@ public class WifiConfigController implements TextWatcher, @Override public void afterTextChanged(Editable s) { - enableSubmitIfAppropriate(); + mTextViewChangedHandler.post(new Runnable() { + public void run() { + enableSubmitIfAppropriate(); + } + }); } @Override @@ -765,10 +738,14 @@ public class WifiConfigController implements TextWatcher, @Override public void onClick(View view) { if (view.getId() == R.id.show_password) { + int pos = mPasswordView.getSelectionEnd(); mPasswordView.setInputType( InputType.TYPE_CLASS_TEXT | (((CheckBox) view).isChecked() ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_TEXT_VARIATION_PASSWORD)); + if (pos >= 0) { + ((EditText)mPasswordView).setSelection(pos); + } } else if (view.getId() == R.id.wifi_advanced_togglebox) { if (((CheckBox) view).isChecked()) { mView.findViewById(R.id.wifi_advanced_fields).setVisibility(View.VISIBLE); @@ -785,8 +762,6 @@ public class WifiConfigController implements TextWatcher, showSecurityFields(); } else if (parent == mEapMethodSpinner) { showSecurityFields(); - } else if (parent == mNetworkSetupSpinner) { - showNetworkSetupFields(); } else if (parent == mProxySettingsSpinner) { showProxyFields(); } else { diff --git a/src/com/android/settings/wifi/WifiDialog.java b/src/com/android/settings/wifi/WifiDialog.java index f980d0c..82b0cc6 100644 --- a/src/com/android/settings/wifi/WifiDialog.java +++ b/src/com/android/settings/wifi/WifiDialog.java @@ -56,6 +56,9 @@ class WifiDialog extends AlertDialog implements WifiConfigUiBase { setInverseBackgroundForced(true); mController = new WifiConfigController(this, mView, mAccessPoint, mEdit); super.onCreate(savedInstanceState); + /* During creation, the submit button can be unavailable to determine + * visibility. Right after creation, update button visibility */ + mController.enableSubmitIfAppropriate(); } @Override diff --git a/src/com/android/settings/wifi/WifiPickerActivity.java b/src/com/android/settings/wifi/WifiPickerActivity.java index 6a2f865..12612b5 100644 --- a/src/com/android/settings/wifi/WifiPickerActivity.java +++ b/src/com/android/settings/wifi/WifiPickerActivity.java @@ -29,6 +29,8 @@ public class WifiPickerActivity extends PreferenceActivity implements ButtonBarH private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar"; private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text"; private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text"; + private static final String EXTRA_WIFI_SHOW_ACTION_BAR = "wifi_show_action_bar"; + private static final String EXTRA_WIFI_SHOW_MENUS = "wifi_show_menus"; @Override public Intent getIntent() { @@ -67,6 +69,14 @@ public class WifiPickerActivity extends PreferenceActivity implements ButtonBarH intent.putExtra(EXTRA_PREFS_SET_BACK_TEXT, orgIntent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT)); } + if (orgIntent.hasExtra(EXTRA_WIFI_SHOW_ACTION_BAR)) { + intent.putExtra(EXTRA_WIFI_SHOW_ACTION_BAR, + orgIntent.getBooleanExtra(EXTRA_WIFI_SHOW_ACTION_BAR, true)); + } + if (orgIntent.hasExtra(EXTRA_WIFI_SHOW_MENUS)) { + intent.putExtra(EXTRA_WIFI_SHOW_MENUS, + orgIntent.getBooleanExtra(EXTRA_WIFI_SHOW_MENUS, true)); + } if (resultTo == null) { startActivity(intent); diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java index 29c1229..28b0f36 100644 --- a/src/com/android/settings/wifi/WifiSettings.java +++ b/src/com/android/settings/wifi/WifiSettings.java @@ -20,23 +20,23 @@ import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID; import android.app.ActionBar; import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.wifi.ScanResult; import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiConfiguration.KeyMgmt; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WpsResult; +import android.net.wifi.WpsInfo; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -45,22 +45,30 @@ import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; import android.security.Credentials; import android.security.KeyStore; +import android.util.AttributeSet; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.ImageButton; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnMenuItemClickListener; +import android.widget.RelativeLayout; import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; -import com.android.internal.util.AsyncChannel; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.wifi.p2p.WifiP2pSettings; import java.util.ArrayList; import java.util.Collection; @@ -70,27 +78,29 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** - * This currently provides three types of UI. + * Two types of UI are provided here. * - * Two are for phones with relatively small screens: "for SetupWizard" and "for usual Settings". - * Users just need to launch WifiSettings Activity as usual. The request will be appropriately - * handled by ActivityManager, and they will have appropriate look-and-feel with this fragment. + * The first is for "usual Settings", appearing as any other Setup fragment. * - * Third type is for Setup Wizard with X-Large, landscape UI. Users need to launch - * {@link WifiSettingsForSetupWizardXL} Activity, which contains this fragment but also has - * other decorations specific to that screen. + * The second is for Setup Wizard, with a simplified interface that hides the action bar + * and menus. */ public class WifiSettings extends SettingsPreferenceFragment implements DialogInterface.OnClickListener { private static final String TAG = "WifiSettings"; - private static final int MENU_ID_SCAN = Menu.FIRST; - private static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 1; - private static final int MENU_ID_ADVANCED = Menu.FIRST + 2; - private static final int MENU_ID_CONNECT = Menu.FIRST + 3; - private static final int MENU_ID_FORGET = Menu.FIRST + 4; - private static final int MENU_ID_MODIFY = Menu.FIRST + 5; + private static final int MENU_ID_WPS_PBC = Menu.FIRST; + private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1; + private static final int MENU_ID_P2P = Menu.FIRST + 2; + private static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3; + private static final int MENU_ID_ADVANCED = Menu.FIRST + 4; + private static final int MENU_ID_SCAN = Menu.FIRST + 5; + private static final int MENU_ID_CONNECT = Menu.FIRST + 6; + private static final int MENU_ID_FORGET = Menu.FIRST + 7; + private static final int MENU_ID_MODIFY = Menu.FIRST + 8; private static final int WIFI_DIALOG_ID = 1; + private static final int WPS_PBC_DIALOG_ID = 2; + private static final int WPS_PIN_DIALOG_ID = 3; // Combo scans can take 5-6s to complete - set to 10s. private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000; @@ -104,6 +114,13 @@ public class WifiSettings extends SettingsPreferenceFragment private final Scanner mScanner; private WifiManager mWifiManager; + private WifiManager.Channel mChannel; + private WifiManager.ActionListener mConnectListener; + private WifiManager.ActionListener mSaveListener; + private WifiManager.ActionListener mForgetListener; + private boolean mP2pSupported; + + private WifiEnabler mWifiEnabler; // An access point being editted is stored here. private AccessPoint mSelectedAccessPoint; @@ -124,15 +141,28 @@ public class WifiSettings extends SettingsPreferenceFragment // this boolean extra specifies whether to disable the Next button when not connected private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect"; + // this boolean extra specifies whether to auto finish when connection is established + private static final String EXTRA_AUTO_FINISH_ON_CONNECT = "wifi_auto_finish_on_connect"; + + // this boolean extra is set if we are being invoked by the Setup Wizard + private static final String EXTRA_IS_FIRST_RUN = "firstRun"; + + private static final String EXTRA_WIFI_DISABLE_BACK = "wifi_disable_back"; + // should Next button only be enabled when we have a connection? private boolean mEnableNextOnConnection; - private boolean mInXlSetupWizard; + + // should activity finish once we have a connection? + private boolean mAutoFinishOnConnection; // Save the dialog details private boolean mDlgEdit; private AccessPoint mDlgAccessPoint; private Bundle mAccessPointSavedState; + // the action bar uses a different set of controls for Setup Wizard + private boolean mSetupWizardMode; + /* End of "used in Wifi Setup context" */ public WifiSettings() { @@ -145,7 +175,6 @@ public class WifiSettings extends SettingsPreferenceFragment mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); - mFilter.addAction(WifiManager.ERROR_ACTION); mReceiver = new BroadcastReceiver() { @Override @@ -158,10 +187,54 @@ public class WifiSettings extends SettingsPreferenceFragment } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onCreate(Bundle icicle) { + // Set this flag early, as it's needed by getHelpResource(), which is called by super + mSetupWizardMode = getActivity().getIntent().getBooleanExtra(EXTRA_IS_FIRST_RUN, false); + + super.onCreate(icicle); + } - mInXlSetupWizard = (activity instanceof WifiSettingsForSetupWizardXL); + @Override + public View onCreateView(final LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + if (mSetupWizardMode) { + View view = inflater.inflate(R.layout.setup_preference, container, false); + View other = view.findViewById(R.id.other_network); + other.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mWifiManager.isWifiEnabled()) { + onAddNetworkPressed(); + } + } + }); + final ImageButton b = (ImageButton) view.findViewById(R.id.more); + if (b != null) { + b.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mWifiManager.isWifiEnabled()) { + PopupMenu pm = new PopupMenu(inflater.getContext(), b); + pm.inflate(R.menu.wifi_setup); + pm.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + if (R.id.wifi_wps == item.getItemId()) { + showDialog(WPS_PBC_DIALOG_ID); + return true; + } + return false; + } + }); + pm.show(); + } + } + }); + } + return view; + } else { + return super.onCreateView(inflater, container, savedInstanceState); + } } @Override @@ -170,8 +243,40 @@ public class WifiSettings extends SettingsPreferenceFragment // Preference (probably in onCreate()), while WifiSettings exceptionally set it up in // this method. + mP2pSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT); mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); - mWifiManager.asyncConnect(getActivity(), new WifiServiceHandler()); + mChannel = mWifiManager.initialize(getActivity(), getActivity().getMainLooper(), null); + + mConnectListener = new WifiManager.ActionListener() { + public void onSuccess() { + } + public void onFailure(int reason) { + Toast.makeText(getActivity(), + R.string.wifi_failed_connect_message, + Toast.LENGTH_SHORT).show(); + } + }; + + mSaveListener = new WifiManager.ActionListener() { + public void onSuccess() { + } + public void onFailure(int reason) { + Toast.makeText(getActivity(), + R.string.wifi_failed_save_message, + Toast.LENGTH_SHORT).show(); + } + }; + + mForgetListener = new WifiManager.ActionListener() { + public void onSuccess() { + } + public void onFailure(int reason) { + Toast.makeText(getActivity(), + R.string.wifi_failed_forget_message, + Toast.LENGTH_SHORT).show(); + } + }; + if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) { mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE); @@ -181,6 +286,24 @@ public class WifiSettings extends SettingsPreferenceFragment final Activity activity = getActivity(); final Intent intent = activity.getIntent(); + // first if we're supposed to finish once we have a connection + mAutoFinishOnConnection = intent.getBooleanExtra(EXTRA_AUTO_FINISH_ON_CONNECT, false); + + if (mAutoFinishOnConnection) { + // Hide the next button + if (hasNextButton()) { + getNextButton().setVisibility(View.GONE); + } + + final ConnectivityManager connectivity = (ConnectivityManager) + getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity != null + && connectivity.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected()) { + activity.finish(); + return; + } + } + // if we're supposed to enable/disable the Next button based on our current connection // state, start it off in the right state mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false); @@ -197,11 +320,15 @@ public class WifiSettings extends SettingsPreferenceFragment } } - if (mInXlSetupWizard) { - addPreferencesFromResource(R.xml.wifi_access_points_for_wifi_setup_xl); - } else { - addPreferencesFromResource(R.xml.wifi_settings); + addPreferencesFromResource(R.xml.wifi_settings); + + // Back key is disabled if requested + if (intent.getBooleanExtra(EXTRA_WIFI_DISABLE_BACK, false)) { + getView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_BACK); + } + // On/off switch is hidden for Setup Wizard + if (!mSetupWizardMode) { Switch actionBarSwitch = new Switch(activity); if (activity instanceof PreferenceActivity) { @@ -225,7 +352,9 @@ public class WifiSettings extends SettingsPreferenceFragment mEmptyView = (TextView) getView().findViewById(android.R.id.empty); getListView().setEmptyView(mEmptyView); - registerForContextMenu(getListView()); + if (!mSetupWizardMode) { + registerForContextMenu(getListView()); + } setHasOptionsMenu(true); // After confirming PreferenceScreen is available, we call super. @@ -242,7 +371,7 @@ public class WifiSettings extends SettingsPreferenceFragment getActivity().registerReceiver(mReceiver, mFilter); if (mKeyStoreNetworkId != INVALID_NETWORK_ID && KeyStore.getInstance().state() == KeyStore.State.UNLOCKED) { - mWifiManager.connectNetwork(mKeyStoreNetworkId); + mWifiManager.connect(mChannel, mKeyStoreNetworkId, mConnectListener); } mKeyStoreNetworkId = INVALID_NETWORK_ID; @@ -261,16 +390,37 @@ public class WifiSettings extends SettingsPreferenceFragment @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - // We don't want menus in Setup Wizard XL. - if (!mInXlSetupWizard) { - final boolean wifiIsEnabled = mWifiManager.isWifiEnabled(); - menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan) - //.setIcon(R.drawable.ic_menu_scan_network) + final boolean wifiIsEnabled = mWifiManager.isWifiEnabled(); + if (mSetupWizardMode) { + // FIXME: add setIcon() when graphics are available + menu.add(Menu.NONE, MENU_ID_WPS_PBC, 0, R.string.wifi_menu_wps_pbc) + .setIcon(R.drawable.ic_wps) + .setEnabled(wifiIsEnabled) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network) + .setEnabled(wifiIsEnabled) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } else { + menu.add(Menu.NONE, MENU_ID_WPS_PBC, 0, R.string.wifi_menu_wps_pbc) + .setIcon(R.drawable.ic_wps) .setEnabled(wifiIsEnabled) .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network) + .setIcon(R.drawable.ic_menu_add) .setEnabled(wifiIsEnabled) .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan) + //.setIcon(R.drawable.ic_menu_scan_network) + .setEnabled(wifiIsEnabled) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(Menu.NONE, MENU_ID_WPS_PIN, 0, R.string.wifi_menu_wps_pin) + .setEnabled(wifiIsEnabled) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + if (mP2pSupported) { + menu.add(Menu.NONE, MENU_ID_P2P, 0, R.string.wifi_menu_p2p) + .setEnabled(wifiIsEnabled) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced) //.setIcon(android.R.drawable.ic_menu_manage) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); @@ -296,6 +446,23 @@ public class WifiSettings extends SettingsPreferenceFragment @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + case MENU_ID_WPS_PBC: + showDialog(WPS_PBC_DIALOG_ID); + return true; + case MENU_ID_P2P: + if (getActivity() instanceof PreferenceActivity) { + ((PreferenceActivity) getActivity()).startPreferencePanel( + WifiP2pSettings.class.getCanonicalName(), + null, + R.string.wifi_p2p_settings_title, null, + this, 0); + } else { + startFragment(this, WifiP2pSettings.class.getCanonicalName(), -1, null); + } + return true; + case MENU_ID_WPS_PIN: + showDialog(WPS_PIN_DIALOG_ID); + return true; case MENU_ID_SCAN: if (mWifiManager.isWifiEnabled()) { mScanner.forceScan(); @@ -323,9 +490,7 @@ public class WifiSettings extends SettingsPreferenceFragment @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { - if (mInXlSetupWizard) { - ((WifiSettingsForSetupWizardXL)getActivity()).onCreateContextMenu(menu, view, info); - } else if (info instanceof AdapterContextMenuInfo) { + if (info instanceof AdapterContextMenuInfo) { Preference preference = (Preference) getListView().getItemAtPosition( ((AdapterContextMenuInfo) info).position); @@ -353,23 +518,25 @@ public class WifiSettings extends SettingsPreferenceFragment case MENU_ID_CONNECT: { if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { if (!requireKeyStore(mSelectedAccessPoint.getConfig())) { - mWifiManager.connectNetwork(mSelectedAccessPoint.networkId); + mWifiManager.connect(mChannel, mSelectedAccessPoint.networkId, + mConnectListener); } } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) { /** Bypass dialog for unsecured networks */ mSelectedAccessPoint.generateOpenNetworkConfig(); - mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); + mWifiManager.connect(mChannel, mSelectedAccessPoint.getConfig(), + mConnectListener); } else { - showConfigUi(mSelectedAccessPoint, true); + showDialog(mSelectedAccessPoint, true); } return true; } case MENU_ID_FORGET: { - mWifiManager.forgetNetwork(mSelectedAccessPoint.networkId); + mWifiManager.forget(mChannel, mSelectedAccessPoint.networkId, mForgetListener); return true; } case MENU_ID_MODIFY: { - showConfigUi(mSelectedAccessPoint, true); + showDialog(mSelectedAccessPoint, true); return true; } } @@ -384,9 +551,9 @@ public class WifiSettings extends SettingsPreferenceFragment if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { mSelectedAccessPoint.generateOpenNetworkConfig(); - mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig()); + mWifiManager.connect(mChannel, mSelectedAccessPoint.getConfig(), mConnectListener); } else { - showConfigUi(mSelectedAccessPoint, false); + showDialog(mSelectedAccessPoint, false); } } else { return super.onPreferenceTreeClick(screen, preference); @@ -394,18 +561,6 @@ public class WifiSettings extends SettingsPreferenceFragment return true; } - /** - * Shows an appropriate Wifi configuration component. - * Called when a user clicks "Add network" preference or one of available networks is selected. - */ - private void showConfigUi(AccessPoint accessPoint, boolean edit) { - if (mInXlSetupWizard) { - ((WifiSettingsForSetupWizardXL)getActivity()).showConfigUi(accessPoint, edit); - } else { - showDialog(accessPoint, edit); - } - } - private void showDialog(AccessPoint accessPoint, boolean edit) { if (mDialog != null) { removeDialog(WIFI_DIALOG_ID); @@ -421,18 +576,26 @@ public class WifiSettings extends SettingsPreferenceFragment @Override public Dialog onCreateDialog(int dialogId) { - AccessPoint ap = mDlgAccessPoint; // For manual launch - if (ap == null) { // For re-launch from saved state - if (mAccessPointSavedState != null) { - ap = new AccessPoint(getActivity(), mAccessPointSavedState); - // For repeated orientation changes - mDlgAccessPoint = ap; - } + switch (dialogId) { + case WIFI_DIALOG_ID: + AccessPoint ap = mDlgAccessPoint; // For manual launch + if (ap == null) { // For re-launch from saved state + if (mAccessPointSavedState != null) { + ap = new AccessPoint(getActivity(), mAccessPointSavedState); + // For repeated orientation changes + mDlgAccessPoint = ap; + } + } + // If it's still null, fine, it's for Add Network + mSelectedAccessPoint = ap; + mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit); + return mDialog; + case WPS_PBC_DIALOG_ID: + return new WpsDialog(getActivity(), WpsInfo.PBC); + case WPS_PIN_DIALOG_ID: + return new WpsDialog(getActivity(), WpsInfo.DISPLAY); } - // If it's still null, fine, it's for Add Network - mSelectedAccessPoint = ap; - mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit); - return mDialog; + return super.onCreateDialog(dialogId); } private boolean requireKeyStore(WifiConfiguration config) { @@ -450,6 +613,9 @@ public class WifiSettings extends SettingsPreferenceFragment * the strength of network and the security for it. */ private void updateAccessPoints() { + // Safeguard from some delayed event handling + if (getActivity() == null) return; + final int wifiState = mWifiManager.getWifiState(); switch (wifiState) { @@ -457,16 +623,11 @@ public class WifiSettings extends SettingsPreferenceFragment // AccessPoints are automatically sorted with TreeSet. final Collection<AccessPoint> accessPoints = constructAccessPoints(); getPreferenceScreen().removeAll(); - if (mInXlSetupWizard) { - ((WifiSettingsForSetupWizardXL)getActivity()).onAccessPointsUpdated( - getPreferenceScreen(), accessPoints); - } else { - if(accessPoints.size() == 0) { - addMessagePreference(R.string.wifi_empty_list_wifi_on); - } - for (AccessPoint accessPoint : accessPoints) { - getPreferenceScreen().addPreference(accessPoint); - } + if(accessPoints.size() == 0) { + addMessagePreference(R.string.wifi_empty_list_wifi_on); + } + for (AccessPoint accessPoint : accessPoints) { + getPreferenceScreen().addPreference(accessPoint); } break; @@ -568,13 +729,10 @@ public class WifiSettings extends SettingsPreferenceFragment //network state change events so the apps dont have to worry about //ignoring supplicant state change when network is connected //to get more fine grained information. - if (!mConnected.get()) { - updateConnectionState(WifiInfo.getDetailedStateOf((SupplicantState) - intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE))); - } - - if (mInXlSetupWizard) { - ((WifiSettingsForSetupWizardXL)getActivity()).onSupplicantStateChanged(intent); + SupplicantState state = (SupplicantState) intent.getParcelableExtra( + WifiManager.EXTRA_NEW_STATE); + if (!mConnected.get() && SupplicantState.isHandshakeState(state)) { + updateConnectionState(WifiInfo.getDetailedStateOf(state)); } } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( @@ -583,16 +741,12 @@ public class WifiSettings extends SettingsPreferenceFragment changeNextButtonState(info.isConnected()); updateAccessPoints(); updateConnectionState(info.getDetailedState()); + if (mAutoFinishOnConnection && info.isConnected()) { + getActivity().finish(); + return; + } } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { updateConnectionState(null); - } else if (WifiManager.ERROR_ACTION.equals(action)) { - int errorCode = intent.getIntExtra(WifiManager.EXTRA_ERROR_CODE, 0); - switch (errorCode) { - case WifiManager.WPS_OVERLAP_ERROR: - Toast.makeText(context, R.string.wifi_wps_overlap_error, - Toast.LENGTH_SHORT).show(); - break; - } } } @@ -622,10 +776,6 @@ public class WifiSettings extends SettingsPreferenceFragment accessPoint.update(mLastInfo, mLastState); } } - - if (mInXlSetupWizard) { - ((WifiSettingsForSetupWizardXL)getActivity()).updateConnectionState(mLastState); - } } private void updateWifiState(int state) { @@ -683,51 +833,6 @@ public class WifiSettings extends SettingsPreferenceFragment } } - private class WifiServiceHandler extends Handler { - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: - if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { - //AsyncChannel in msg.obj - } else { - //AsyncChannel set up failure, ignore - Log.e(TAG, "Failed to establish AsyncChannel connection"); - } - break; - case WifiManager.CMD_WPS_COMPLETED: - WpsResult result = (WpsResult) msg.obj; - if (result == null) break; - AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.wifi_wps_setup_title) - .setPositiveButton(android.R.string.ok, null); - switch (result.status) { - case FAILURE: - dialog.setMessage(R.string.wifi_wps_failed); - dialog.show(); - break; - case IN_PROGRESS: - dialog.setMessage(R.string.wifi_wps_in_progress); - dialog.show(); - break; - default: - if (result.pin != null) { - dialog.setMessage(getResources().getString( - R.string.wifi_wps_pin_output, result.pin)); - dialog.show(); - } - break; - } - break; - //TODO: more connectivity feedback - default: - //Ignore - break; - } - } - } - /** * Renames/replaces "Next" button when appropriate. "Next" button usually exists in * Wifi setup screens, not in usual wifi settings screen. @@ -735,59 +840,40 @@ public class WifiSettings extends SettingsPreferenceFragment * @param connected true when the device is connected to a wifi network. */ private void changeNextButtonState(boolean connected) { - if (mInXlSetupWizard) { - ((WifiSettingsForSetupWizardXL)getActivity()).changeNextButtonState(connected); - } else if (mEnableNextOnConnection && hasNextButton()) { + if (mEnableNextOnConnection && hasNextButton()) { getNextButton().setEnabled(connected); } } public void onClick(DialogInterface dialogInterface, int button) { - if (mInXlSetupWizard) { - if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) { - forget(); - } else if (button == WifiDialog.BUTTON_SUBMIT) { - ((WifiSettingsForSetupWizardXL)getActivity()).onConnectButtonPressed(); - } - } else { - if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) { - forget(); - } else if (button == WifiDialog.BUTTON_SUBMIT) { - submit(mDialog.getController()); - } + if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) { + forget(); + } else if (button == WifiDialog.BUTTON_SUBMIT) { + submit(mDialog.getController()); } - } /* package */ void submit(WifiConfigController configController) { - int networkSetup = configController.chosenNetworkSetupMethod(); - switch(networkSetup) { - case WifiConfigController.WPS_PBC: - case WifiConfigController.WPS_DISPLAY: - case WifiConfigController.WPS_KEYPAD: - mWifiManager.startWps(configController.getWpsConfig()); - break; - case WifiConfigController.MANUAL: - final WifiConfiguration config = configController.getConfig(); - - if (config == null) { - if (mSelectedAccessPoint != null - && !requireKeyStore(mSelectedAccessPoint.getConfig()) - && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { - mWifiManager.connectNetwork(mSelectedAccessPoint.networkId); - } - } else if (config.networkId != INVALID_NETWORK_ID) { - if (mSelectedAccessPoint != null) { - saveNetwork(config); - } - } else { - if (configController.isEdit() || requireKeyStore(config)) { - saveNetwork(config); - } else { - mWifiManager.connectNetwork(config); - } - } - break; + + final WifiConfiguration config = configController.getConfig(); + + if (config == null) { + if (mSelectedAccessPoint != null + && !requireKeyStore(mSelectedAccessPoint.getConfig()) + && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { + mWifiManager.connect(mChannel, mSelectedAccessPoint.networkId, + mConnectListener); + } + } else if (config.networkId != INVALID_NETWORK_ID) { + if (mSelectedAccessPoint != null) { + mWifiManager.save(mChannel, config, mSaveListener); + } + } else { + if (configController.isEdit() || requireKeyStore(config)) { + mWifiManager.save(mChannel, config, mSaveListener); + } else { + mWifiManager.connect(mChannel, config, mConnectListener); + } } if (mWifiManager.isWifiEnabled()) { @@ -796,16 +882,14 @@ public class WifiSettings extends SettingsPreferenceFragment updateAccessPoints(); } - private void saveNetwork(WifiConfiguration config) { - if (mInXlSetupWizard) { - ((WifiSettingsForSetupWizardXL)getActivity()).onSaveNetwork(config); - } else { - mWifiManager.saveNetwork(config); + /* package */ void forget() { + if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { + // Should not happen, but a monkey seems to triger it + Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig()); + return; } - } - /* package */ void forget() { - mWifiManager.forgetNetwork(mSelectedAccessPoint.networkId); + mWifiManager.forget(mChannel, mSelectedAccessPoint.networkId, mForgetListener); if (mWifiManager.isWifiEnabled()) { mScanner.resume(); @@ -833,7 +917,7 @@ public class WifiSettings extends SettingsPreferenceFragment /* package */ void onAddNetworkPressed() { // No exact access point is selected. mSelectedAccessPoint = null; - showConfigUi(null, true); + showDialog(null, true); } /* package */ int getAccessPointsCount() { @@ -862,4 +946,52 @@ public class WifiSettings extends SettingsPreferenceFragment mScanner.resume(); } } + + @Override + protected int getHelpResource() { + if (mSetupWizardMode) { + return 0; + } + return R.string.help_url_wifi; + } + + /** + * Used as the outer frame of all setup wizard pages that need to adjust their margins based + * on the total size of the available display. (e.g. side margins set to 10% of total width.) + */ + public static class ProportionalOuterFrame extends RelativeLayout { + public ProportionalOuterFrame(Context context) { + super(context); + } + public ProportionalOuterFrame(Context context, AttributeSet attrs) { + super(context, attrs); + } + public ProportionalOuterFrame(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Set our margins and title area height proportionally to the available display size + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + int parentHeight = MeasureSpec.getSize(heightMeasureSpec); + final Resources resources = getContext().getResources(); + float titleHeight = resources.getFraction(R.dimen.setup_title_height, 1, 1); + float sideMargin = resources.getFraction(R.dimen.setup_border_width, 1, 1); + int bottom = resources.getDimensionPixelSize(R.dimen.setup_margin_bottom); + setPadding( + (int) (parentWidth * sideMargin), + 0, + (int) (parentWidth * sideMargin), + bottom); + View title = findViewById(R.id.title_area); + if (title != null) { + title.setMinimumHeight((int) (parentHeight * titleHeight)); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + } diff --git a/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java b/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java index 4b6a081..8258b55 100644 --- a/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java +++ b/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java @@ -74,6 +74,7 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis private WifiSettings mWifiSettings; private WifiManager mWifiManager; + private WifiManager.Channel mChannel; /** Used for resizing a padding above title. Hiden when software keyboard is shown. */ private View mTopPadding; @@ -142,10 +143,10 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis setContentView(R.layout.wifi_settings_for_setup_wizard_xl); mWifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE); + mChannel = mWifiManager.initialize(this, getMainLooper(), null); // There's no button here enabling wifi network, so we need to enable it without // users' request. mWifiManager.setWifiEnabled(true); - mWifiManager.asyncConnect(this, new WifiServiceHandler()); mWifiSettings = (WifiSettings)getFragmentManager().findFragmentById(R.id.wifi_setup_fragment); @@ -201,25 +202,6 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis mConnectingStatusView = (TextView) findViewById(R.id.connecting_status); } - private class WifiServiceHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: - if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { - //AsyncChannel in msg.obj - } else { - //AsyncChannel set up failure, ignore - Log.e(TAG, "Failed to establish AsyncChannel connection"); - } - break; - default: - //Ignore - break; - } - } - } - private void restoreFirstVisibilityState() { showDefaultTitle(); mAddNetworkButton.setVisibility(View.VISIBLE); @@ -614,7 +596,13 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis Log.d(TAG, String.format("forgeting Wi-Fi network \"%s\" (id: %d)", config.SSID, config.networkId)); } - mWifiManager.forgetNetwork(config.networkId); + mWifiManager.forget(mChannel, config.networkId, new WifiManager.ActionListener() { + public void onSuccess() { + } + public void onFailure(int reason) { + //TODO: Add failure UI + } + }); } mWifiSettingsFragmentLayout.setVisibility(View.GONE); @@ -773,6 +761,12 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis */ /* package */ void onSaveNetwork(WifiConfiguration config) { // We want to both save and connect a network. connectNetwork() does both. - mWifiManager.connectNetwork(config); + mWifiManager.connect(mChannel, config, new WifiManager.ActionListener() { + public void onSuccess() { + } + public void onFailure(int reason) { + //TODO: Add failure UI + } + }); } } diff --git a/src/com/android/settings/wifi/WifiSetupActivity.java b/src/com/android/settings/wifi/WifiSetupActivity.java new file mode 100644 index 0000000..8415954 --- /dev/null +++ b/src/com/android/settings/wifi/WifiSetupActivity.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2012 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.wifi; + +import com.android.settings.ButtonBarHandler; + +// dummy class for setup wizard theme +public class WifiSetupActivity extends WifiPickerActivity implements ButtonBarHandler { + +}
\ No newline at end of file diff --git a/src/com/android/settings/wifi/WpsDialog.java b/src/com/android/settings/wifi/WpsDialog.java new file mode 100644 index 0000000..40b2a35 --- /dev/null +++ b/src/com/android/settings/wifi/WpsDialog.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2012 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.wifi; + +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WpsInfo; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; + +import java.util.Timer; +import java.util.TimerTask; + +import com.android.settings.R; + + +/** + * Dialog to show WPS progress. + */ +public class WpsDialog extends AlertDialog { + + private final static String TAG = "WpsDialog"; + + private View mView; + private TextView mTextView; + private ProgressBar mTimeoutBar; + private ProgressBar mProgressBar; + private Button mButton; + private Timer mTimer; + + private static final int WPS_TIMEOUT_S = 120; + + private WifiManager mWifiManager; + private WifiManager.Channel mChannel; + private WifiManager.WpsListener mWpsListener; + private int mWpsSetup; + + private final IntentFilter mFilter; + private BroadcastReceiver mReceiver; + + private Context mContext; + private Handler mHandler = new Handler(); + + private enum DialogState { + WPS_INIT, + WPS_START, + WPS_COMPLETE, + CONNECTED, //WPS + IP config is done + WPS_FAILED + } + DialogState mDialogState = DialogState.WPS_INIT; + + public WpsDialog(Context context, int wpsSetup) { + super(context); + mContext = context; + mWpsSetup = wpsSetup; + + class WpsListener implements WifiManager.WpsListener { + public void onStartSuccess(String pin) { + if (pin != null) { + updateDialog(DialogState.WPS_START, String.format( + mContext.getString(R.string.wifi_wps_onstart_pin), pin)); + } else { + updateDialog(DialogState.WPS_START, mContext.getString( + R.string.wifi_wps_onstart_pbc)); + } + } + public void onCompletion() { + updateDialog(DialogState.WPS_COMPLETE, + mContext.getString(R.string.wifi_wps_complete)); + } + + public void onFailure(int reason) { + String msg; + switch (reason) { + case WifiManager.WPS_OVERLAP_ERROR: + msg = mContext.getString(R.string.wifi_wps_failed_overlap); + break; + case WifiManager.WPS_WEP_PROHIBITED: + msg = mContext.getString(R.string.wifi_wps_failed_wep); + break; + case WifiManager.WPS_TKIP_ONLY_PROHIBITED: + msg = mContext.getString(R.string.wifi_wps_failed_tkip); + break; + case WifiManager.IN_PROGRESS: + msg = mContext.getString(R.string.wifi_wps_in_progress); + break; + default: + msg = mContext.getString(R.string.wifi_wps_failed_generic); + break; + } + updateDialog(DialogState.WPS_FAILED, msg); + } + } + + mWpsListener = new WpsListener(); + + + mFilter = new IntentFilter(); + mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleEvent(context, intent); + } + }; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + mView = getLayoutInflater().inflate(R.layout.wifi_wps_dialog, null); + + mTextView = (TextView) mView.findViewById(R.id.wps_dialog_txt); + mTextView.setText(R.string.wifi_wps_setup_msg); + + mTimeoutBar = ((ProgressBar) mView.findViewById(R.id.wps_timeout_bar)); + mTimeoutBar.setMax(WPS_TIMEOUT_S); + mTimeoutBar.setProgress(0); + + mProgressBar = ((ProgressBar) mView.findViewById(R.id.wps_progress_bar)); + mProgressBar.setVisibility(View.GONE); + + mButton = ((Button) mView.findViewById(R.id.wps_dialog_btn)); + mButton.setText(R.string.wifi_cancel); + mButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + } + }); + + mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + mChannel = mWifiManager.initialize(mContext, mContext.getMainLooper(), null); + + setView(mView); + super.onCreate(savedInstanceState); + } + + @Override + protected void onStart() { + /* + * increment timeout bar per second. + */ + mTimer = new Timer(false); + mTimer.schedule(new TimerTask() { + @Override + public void run() { + mHandler.post(new Runnable() { + + @Override + public void run() { + mTimeoutBar.incrementProgressBy(1); + } + }); + } + }, 1000, 1000); + + mContext.registerReceiver(mReceiver, mFilter); + + WpsInfo wpsConfig = new WpsInfo(); + wpsConfig.setup = mWpsSetup; + mWifiManager.startWps(mChannel, wpsConfig, mWpsListener); + } + + @Override + protected void onStop() { + if (mDialogState != DialogState.WPS_COMPLETE) { + mWifiManager.cancelWps(mChannel, null); + } + + if (mReceiver != null) { + mContext.unregisterReceiver(mReceiver); + mReceiver = null; + } + + if (mTimer != null) { + mTimer.cancel(); + } + } + + private void updateDialog(DialogState state, String msg) { + if (mDialogState.ordinal() >= state.ordinal()) { + //ignore. + return; + } + mDialogState = state; + + switch(state) { + case WPS_COMPLETE: + mTimeoutBar.setVisibility(View.GONE); + mProgressBar.setVisibility(View.VISIBLE); + break; + case CONNECTED: + case WPS_FAILED: + mButton.setText(mContext.getString(R.string.dlg_ok)); + mTimeoutBar.setVisibility(View.GONE); + mProgressBar.setVisibility(View.GONE); + if (mReceiver != null) { + mContext.unregisterReceiver(mReceiver); + mReceiver = null; + } + break; + } + mTextView.setText(msg); + } + + private void handleEvent(Context context, Intent intent) { + String action = intent.getAction(); + if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { + NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( + WifiManager.EXTRA_NETWORK_INFO); + final NetworkInfo.DetailedState state = info.getDetailedState(); + if (state == DetailedState.CONNECTED && + mDialogState == DialogState.WPS_COMPLETE) { + WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + if (wifiInfo != null) { + String msg = String.format(mContext.getString( + R.string.wifi_wps_connected), wifiInfo.getSSID()); + updateDialog(DialogState.CONNECTED, msg); + } + } + } + } + +} diff --git a/src/com/android/settings/wifi/p2p/WifiP2pDialog.java b/src/com/android/settings/wifi/p2p/WifiP2pDialog.java deleted file mode 100644 index e688905..0000000 --- a/src/com/android/settings/wifi/p2p/WifiP2pDialog.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2011 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.wifi.p2p; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.net.wifi.WpsInfo; -import android.net.wifi.p2p.WifiP2pConfig; -import android.net.wifi.p2p.WifiP2pDevice; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; -import android.widget.CheckBox; -import android.widget.Spinner; -import android.widget.TextView; - -import com.android.settings.R; - -/** - * Dialog to setup a p2p connection - */ -public class WifiP2pDialog extends AlertDialog implements AdapterView.OnItemSelectedListener { - - static final int BUTTON_SUBMIT = DialogInterface.BUTTON_POSITIVE; - - private final DialogInterface.OnClickListener mListener; - - private View mView; - private TextView mDeviceName; - private TextView mDeviceAddress; - - /* These values come from "wifi_p2p_wps_setup" resource array */ - private static final int WPS_PBC = 0; - private static final int WPS_KEYPAD = 1; - private static final int WPS_DISPLAY = 2; - - private int mWpsSetupIndex = WPS_PBC; //default is pbc - - WifiP2pDevice mDevice; - - public WifiP2pDialog(Context context, DialogInterface.OnClickListener listener, - WifiP2pDevice device) { - super(context); - mListener = listener; - mDevice = device; - } - - public WifiP2pConfig getConfig() { - WifiP2pConfig config = new WifiP2pConfig(); - config.deviceAddress = mDeviceAddress.getText().toString(); - config.wps = new WpsInfo(); - switch (mWpsSetupIndex) { - case WPS_PBC: - config.wps.setup = WpsInfo.PBC; - break; - case WPS_KEYPAD: - config.wps.setup = WpsInfo.KEYPAD; - config.wps.pin = ((TextView) mView.findViewById(R.id.wps_pin)). - getText().toString(); - break; - case WPS_DISPLAY: - config.wps.setup = WpsInfo.DISPLAY; - break; - default: - config.wps.setup = WpsInfo.PBC; - break; - } - return config; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - - mView = getLayoutInflater().inflate(R.layout.wifi_p2p_dialog, null); - Spinner mWpsSetup = ((Spinner) mView.findViewById(R.id.wps_setup)); - - setView(mView); - setInverseBackgroundForced(true); - - Context context = getContext(); - - setTitle(R.string.wifi_p2p_settings_title); - mDeviceName = (TextView) mView.findViewById(R.id.device_name); - mDeviceAddress = (TextView) mView.findViewById(R.id.device_address); - - setButton(BUTTON_SUBMIT, context.getString(R.string.wifi_connect), mListener); - setButton(DialogInterface.BUTTON_NEGATIVE, - context.getString(R.string.wifi_cancel), mListener); - - if (mDevice != null) { - mDeviceName.setText(mDevice.deviceName); - mDeviceAddress.setText(mDevice.deviceAddress); - mWpsSetup.setSelection(mWpsSetupIndex); //keep pbc as default - } - - mWpsSetup.setOnItemSelectedListener(this); - - super.onCreate(savedInstanceState); - } - - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - mWpsSetupIndex = position; - - if (mWpsSetupIndex == WPS_KEYPAD) { - mView.findViewById(R.id.wps_pin_entry).setVisibility(View.VISIBLE); - } else { - mView.findViewById(R.id.wps_pin_entry).setVisibility(View.GONE); - } - return; - } - - @Override - public void onNothingSelected(AdapterView<?> parent) { - } - -} diff --git a/src/com/android/settings/wifi/p2p/WifiP2pEnabler.java b/src/com/android/settings/wifi/p2p/WifiP2pEnabler.java deleted file mode 100644 index 0747d64..0000000 --- a/src/com/android/settings/wifi/p2p/WifiP2pEnabler.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2011 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.wifi.p2p; - -import com.android.settings.R; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.wifi.p2p.WifiP2pManager; -import android.os.Message; -import android.preference.CheckBoxPreference; -import android.preference.Preference; -import android.provider.Settings; -import android.util.Log; - -/** - * WifiP2pEnabler is a helper to manage the Wifi p2p on/off - */ -public class WifiP2pEnabler implements Preference.OnPreferenceChangeListener { - private static final String TAG = "WifiP2pEnabler"; - - private final Context mContext; - private final CheckBoxPreference mCheckBox; - private final IntentFilter mIntentFilter; - private WifiP2pManager mWifiP2pManager; - private WifiP2pManager.Channel mChannel; - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { - handleP2pStateChanged(intent.getIntExtra( - WifiP2pManager.EXTRA_WIFI_STATE, WifiP2pManager.WIFI_P2P_STATE_DISABLED)); - } - } - }; - - public WifiP2pEnabler(Context context, CheckBoxPreference checkBox) { - mContext = context; - mCheckBox = checkBox; - - mWifiP2pManager = (WifiP2pManager) context.getSystemService(Context.WIFI_P2P_SERVICE); - if (mWifiP2pManager != null) { - mChannel = mWifiP2pManager.initialize(mContext, mContext.getMainLooper(), null); - if (mChannel == null) { - //Failure to set up connection - Log.e(TAG, "Failed to set up connection with wifi p2p service"); - mWifiP2pManager = null; - mCheckBox.setEnabled(false); - } - } else { - Log.e(TAG, "mWifiP2pManager is null!"); - } - mIntentFilter = new IntentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); - - } - - public void resume() { - if (mWifiP2pManager == null) return; - mContext.registerReceiver(mReceiver, mIntentFilter); - mCheckBox.setOnPreferenceChangeListener(this); - } - - public void pause() { - if (mWifiP2pManager == null) return; - mContext.unregisterReceiver(mReceiver); - mCheckBox.setOnPreferenceChangeListener(null); - } - - public boolean onPreferenceChange(Preference preference, Object value) { - - if (mWifiP2pManager == null) return false; - - mCheckBox.setEnabled(false); - final boolean enable = (Boolean) value; - if (enable) { - mWifiP2pManager.enableP2p(mChannel); - } else { - mWifiP2pManager.disableP2p(mChannel); - } - return false; - } - - private void handleP2pStateChanged(int state) { - mCheckBox.setEnabled(true); - switch (state) { - case WifiP2pManager.WIFI_P2P_STATE_ENABLED: - mCheckBox.setChecked(true); - break; - case WifiP2pManager.WIFI_P2P_STATE_DISABLED: - mCheckBox.setChecked(false); - break; - default: - Log.e(TAG,"Unhandled wifi state " + state); - break; - } - } - -} diff --git a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java index f6588b9..684ec30 100644 --- a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java +++ b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java @@ -32,8 +32,10 @@ import android.net.wifi.p2p.WifiP2pDevice; import android.net.wifi.p2p.WifiP2pDeviceList; import android.net.wifi.p2p.WifiP2pManager; import android.net.wifi.p2p.WifiP2pManager.PeerListListener; +import android.net.wifi.WpsInfo; import android.os.Bundle; -import android.os.Message; +import android.os.Handler; +import android.os.SystemProperties; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceCategory; @@ -45,6 +47,9 @@ import android.view.Gravity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.widget.EditText; +import android.widget.Switch; +import android.widget.Toast; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; @@ -60,36 +65,47 @@ public class WifiP2pSettings extends SettingsPreferenceFragment implements PeerListListener { private static final String TAG = "WifiP2pSettings"; + private static final boolean DBG = false; private static final int MENU_ID_SEARCH = Menu.FIRST; - private static final int MENU_ID_CREATE_GROUP = Menu.FIRST + 1; - private static final int MENU_ID_REMOVE_GROUP = Menu.FIRST + 2; - private static final int MENU_ID_ADVANCED = Menu.FIRST +3; - + private static final int MENU_ID_RENAME = Menu.FIRST + 1; private final IntentFilter mIntentFilter = new IntentFilter(); private WifiP2pManager mWifiP2pManager; private WifiP2pManager.Channel mChannel; - private WifiP2pDialog mConnectDialog; - private OnClickListener mConnectListener; + private OnClickListener mRenameListener; private OnClickListener mDisconnectListener; + private OnClickListener mCancelConnectListener; private WifiP2pPeer mSelectedWifiPeer; + private EditText mDeviceNameText; + + private boolean mWifiP2pEnabled; + private boolean mWifiP2pSearching; + private int mConnectedDevices; private PreferenceGroup mPeersGroup; private Preference mThisDevicePref; - private static final int DIALOG_CONNECT = 1; - private static final int DIALOG_DISCONNECT = 2; + private static final int DIALOG_DISCONNECT = 1; + private static final int DIALOG_CANCEL_CONNECT = 2; + private static final int DIALOG_RENAME = 3; + + private static final String SAVE_DIALOG_PEER = "PEER_STATE"; + private static final String SAVE_DEVICE_NAME = "DEV_NAME"; private WifiP2pDevice mThisDevice; private WifiP2pDeviceList mPeers = new WifiP2pDeviceList(); + private String mSavedDeviceName; + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { - //TODO: nothing right now + mWifiP2pEnabled = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, + WifiP2pManager.WIFI_P2P_STATE_DISABLED) == WifiP2pManager.WIFI_P2P_STATE_ENABLED; + handleP2pStateChanged(); } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { if (mWifiP2pManager != null) { mWifiP2pManager.requestPeers(mChannel, WifiP2pSettings.this); @@ -99,26 +115,38 @@ public class WifiP2pSettings extends SettingsPreferenceFragment NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra( WifiP2pManager.EXTRA_NETWORK_INFO); if (networkInfo.isConnected()) { - Log.d(TAG, "Connected"); + if (DBG) Log.d(TAG, "Connected"); + } else { + //start a search when we are disconnected + startSearch(); } } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { mThisDevice = (WifiP2pDevice) intent.getParcelableExtra( WifiP2pManager.EXTRA_WIFI_P2P_DEVICE); - Log.d(TAG, "Update device info: " + mThisDevice); + if (DBG) Log.d(TAG, "Update device info: " + mThisDevice); updateDevicePref(); + } else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) { + int discoveryState = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, + WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED); + if (DBG) Log.d(TAG, "Discovery state changed: " + discoveryState); + if (discoveryState == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) { + updateSearchMenu(true); + } else { + updateSearchMenu(false); + } } } }; @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); + public void onActivityCreated(Bundle savedInstanceState) { addPreferencesFromResource(R.xml.wifi_p2p_settings); mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); + mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION); final Activity activity = getActivity(); mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); @@ -133,20 +161,29 @@ public class WifiP2pSettings extends SettingsPreferenceFragment Log.e(TAG, "mWifiP2pManager is null !"); } - //connect dialog listener - mConnectListener = new OnClickListener() { + if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_DIALOG_PEER)) { + WifiP2pDevice device = savedInstanceState.getParcelable(SAVE_DIALOG_PEER); + mSelectedWifiPeer = new WifiP2pPeer(getActivity(), device); + } + if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_DEVICE_NAME)) { + mSavedDeviceName = savedInstanceState.getString(SAVE_DEVICE_NAME); + } + + mRenameListener = new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { - WifiP2pConfig config = mConnectDialog.getConfig(); if (mWifiP2pManager != null) { - mWifiP2pManager.connect(mChannel, config, + mWifiP2pManager.setDeviceName(mChannel, + mDeviceNameText.getText().toString(), new WifiP2pManager.ActionListener() { public void onSuccess() { - Log.d(TAG, " connect success"); + if (DBG) Log.d(TAG, " device rename success"); } public void onFailure(int reason) { - Log.d(TAG, " connect fail " + reason); + Toast.makeText(getActivity(), + R.string.wifi_p2p_failed_rename_message, + Toast.LENGTH_LONG).show(); } }); } @@ -162,96 +199,105 @@ public class WifiP2pSettings extends SettingsPreferenceFragment if (mWifiP2pManager != null) { mWifiP2pManager.removeGroup(mChannel, new WifiP2pManager.ActionListener() { public void onSuccess() { - Log.d(TAG, " remove group success"); + if (DBG) Log.d(TAG, " remove group success"); } public void onFailure(int reason) { - Log.d(TAG, " remove group fail " + reason); + if (DBG) Log.d(TAG, " remove group fail " + reason); } }); } } } }; + + //cancel connect dialog listener + mCancelConnectListener = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + if (mWifiP2pManager != null) { + mWifiP2pManager.cancelConnect(mChannel, + new WifiP2pManager.ActionListener() { + public void onSuccess() { + if (DBG) Log.d(TAG, " cancel connect success"); + } + public void onFailure(int reason) { + if (DBG) Log.d(TAG, " cancel connect fail " + reason); + } + }); + } + } + } + }; + setHasOptionsMenu(true); + + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + preferenceScreen.removeAll(); + + preferenceScreen.setOrderingAsAdded(true); + mThisDevicePref = new Preference(getActivity()); + preferenceScreen.addPreference(mThisDevicePref); + + mPeersGroup = new PreferenceCategory(getActivity()); + mPeersGroup.setTitle(R.string.wifi_p2p_peer_devices); + + super.onActivityCreated(savedInstanceState); } @Override public void onResume() { super.onResume(); getActivity().registerReceiver(mReceiver, mIntentFilter); - - if (mWifiP2pManager != null) { - mWifiP2pManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() { - public void onSuccess() { - Log.d(TAG, " discover success"); - } - public void onFailure(int reason) { - Log.d(TAG, " discover fail " + reason); - } - }); - } } @Override public void onPause() { super.onPause(); + mWifiP2pManager.stopPeerDiscovery(mChannel, null); getActivity().unregisterReceiver(mReceiver); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(Menu.NONE, MENU_ID_SEARCH, 0, R.string.wifi_p2p_menu_search) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - menu.add(Menu.NONE, MENU_ID_CREATE_GROUP, 0, R.string.wifi_p2p_menu_create_group) + int textId = mWifiP2pSearching ? R.string.wifi_p2p_menu_searching : + R.string.wifi_p2p_menu_search; + menu.add(Menu.NONE, MENU_ID_SEARCH, 0, textId) + .setEnabled(mWifiP2pEnabled) .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - menu.add(Menu.NONE, MENU_ID_REMOVE_GROUP, 0, R.string.wifi_p2p_menu_remove_group) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_p2p_menu_advanced) + menu.add(Menu.NONE, MENU_ID_RENAME, 0, R.string.wifi_p2p_menu_rename) + .setEnabled(mWifiP2pEnabled) .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); super.onCreateOptionsMenu(menu, inflater); } @Override + public void onPrepareOptionsMenu(Menu menu) { + MenuItem searchMenu = menu.findItem(MENU_ID_SEARCH); + MenuItem renameMenu = menu.findItem(MENU_ID_RENAME); + if (mWifiP2pEnabled) { + searchMenu.setEnabled(true); + renameMenu.setEnabled(true); + } else { + searchMenu.setEnabled(false); + renameMenu.setEnabled(false); + } + + if (mWifiP2pSearching) { + searchMenu.setTitle(R.string.wifi_p2p_menu_searching); + } else { + searchMenu.setTitle(R.string.wifi_p2p_menu_search); + } + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_ID_SEARCH: - if (mWifiP2pManager != null) { - mWifiP2pManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() { - public void onSuccess() { - Log.d(TAG, " discover success"); - } - public void onFailure(int reason) { - Log.d(TAG, " discover fail " + reason); - } - }); - } + startSearch(); return true; - case MENU_ID_CREATE_GROUP: - if (mWifiP2pManager != null) { - mWifiP2pManager.createGroup(mChannel, new WifiP2pManager.ActionListener() { - public void onSuccess() { - Log.d(TAG, " create group success"); - } - public void onFailure(int reason) { - Log.d(TAG, " create group fail " + reason); - } - }); - } - return true; - case MENU_ID_REMOVE_GROUP: - if (mWifiP2pManager != null) { - mWifiP2pManager.removeGroup(mChannel, new WifiP2pManager.ActionListener() { - public void onSuccess() { - Log.d(TAG, " remove group success"); - } - public void onFailure(int reason) { - Log.d(TAG, " remove group fail " + reason); - } - }); - } - return true; - case MENU_ID_ADVANCED: - //TODO: add advanced settings for p2p + case MENU_ID_RENAME: + showDialog(DIALOG_RENAME); return true; } return super.onOptionsItemSelected(item); @@ -263,8 +309,38 @@ public class WifiP2pSettings extends SettingsPreferenceFragment mSelectedWifiPeer = (WifiP2pPeer) preference; if (mSelectedWifiPeer.device.status == WifiP2pDevice.CONNECTED) { showDialog(DIALOG_DISCONNECT); + } else if (mSelectedWifiPeer.device.status == WifiP2pDevice.INVITED) { + showDialog(DIALOG_CANCEL_CONNECT); } else { - showDialog(DIALOG_CONNECT); + WifiP2pConfig config = new WifiP2pConfig(); + config.deviceAddress = mSelectedWifiPeer.device.deviceAddress; + + int forceWps = SystemProperties.getInt("wifidirect.wps", -1); + + if (forceWps != -1) { + config.wps.setup = forceWps; + } else { + if (mSelectedWifiPeer.device.wpsPbcSupported()) { + config.wps.setup = WpsInfo.PBC; + } else if (mSelectedWifiPeer.device.wpsKeypadSupported()) { + config.wps.setup = WpsInfo.KEYPAD; + } else { + config.wps.setup = WpsInfo.DISPLAY; + } + } + + mWifiP2pManager.connect(mChannel, config, + new WifiP2pManager.ActionListener() { + public void onSuccess() { + if (DBG) Log.d(TAG, " connect success"); + } + public void onFailure(int reason) { + Log.e(TAG, " connect fail " + reason); + Toast.makeText(getActivity(), + R.string.wifi_p2p_failed_connect_message, + Toast.LENGTH_SHORT).show(); + } + }); } } return super.onPreferenceTreeClick(screen, preference); @@ -272,50 +348,117 @@ public class WifiP2pSettings extends SettingsPreferenceFragment @Override public Dialog onCreateDialog(int id) { - if (id == DIALOG_CONNECT) { - mConnectDialog = new WifiP2pDialog(getActivity(), mConnectListener, - mSelectedWifiPeer.device); - return mConnectDialog; - } else if (id == DIALOG_DISCONNECT) { + if (id == DIALOG_DISCONNECT) { + String deviceName = TextUtils.isEmpty(mSelectedWifiPeer.device.deviceName) ? + mSelectedWifiPeer.device.deviceAddress : + mSelectedWifiPeer.device.deviceName; + String msg; + if (mConnectedDevices > 1) { + msg = getActivity().getString(R.string.wifi_p2p_disconnect_multiple_message, + deviceName, mConnectedDevices - 1); + } else { + msg = getActivity().getString(R.string.wifi_p2p_disconnect_message, deviceName); + } AlertDialog dialog = new AlertDialog.Builder(getActivity()) - .setTitle("Disconnect ?") - .setMessage("Do you want to disconnect ?") + .setTitle(R.string.wifi_p2p_disconnect_title) + .setMessage(msg) .setPositiveButton(getActivity().getString(R.string.dlg_ok), mDisconnectListener) .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null) .create(); return dialog; + } else if (id == DIALOG_CANCEL_CONNECT) { + int stringId = R.string.wifi_p2p_cancel_connect_message; + String deviceName = TextUtils.isEmpty(mSelectedWifiPeer.device.deviceName) ? + mSelectedWifiPeer.device.deviceAddress : + mSelectedWifiPeer.device.deviceName; + + AlertDialog dialog = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.wifi_p2p_cancel_connect_title) + .setMessage(getActivity().getString(stringId, deviceName)) + .setPositiveButton(getActivity().getString(R.string.dlg_ok), mCancelConnectListener) + .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null) + .create(); + return dialog; + } else if (id == DIALOG_RENAME) { + mDeviceNameText = new EditText(getActivity()); + if (mSavedDeviceName != null) { + mDeviceNameText.setText(mSavedDeviceName); + mDeviceNameText.setSelection(mSavedDeviceName.length()); + } else if (mThisDevice != null && !TextUtils.isEmpty(mThisDevice.deviceName)) { + mDeviceNameText.setText(mThisDevice.deviceName); + mDeviceNameText.setSelection(0, mThisDevice.deviceName.length()); + } + mSavedDeviceName = null; + AlertDialog dialog = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.wifi_p2p_menu_rename) + .setView(mDeviceNameText) + .setPositiveButton(getActivity().getString(R.string.dlg_ok), mRenameListener) + .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null) + .create(); + return dialog; } return null; } + @Override + public void onSaveInstanceState(Bundle outState) { + if (mSelectedWifiPeer != null) { + outState.putParcelable(SAVE_DIALOG_PEER, mSelectedWifiPeer.device); + } + if (mDeviceNameText != null) { + outState.putString(SAVE_DEVICE_NAME, mDeviceNameText.getText().toString()); + } + } + public void onPeersAvailable(WifiP2pDeviceList peers) { + mPeersGroup.removeAll(); - final PreferenceScreen preferenceScreen = getPreferenceScreen(); - preferenceScreen.removeAll(); + mPeers = peers; + mConnectedDevices = 0; + for (WifiP2pDevice peer: peers.getDeviceList()) { + if (DBG) Log.d(TAG, " peer " + peer); + mPeersGroup.addPreference(new WifiP2pPeer(getActivity(), peer)); + if (peer.status == WifiP2pDevice.CONNECTED) mConnectedDevices++; + } + if (DBG) Log.d(TAG, " mConnectedDevices " + mConnectedDevices); + } - preferenceScreen.setOrderingAsAdded(true); + private void handleP2pStateChanged() { + updateSearchMenu(false); + if (mWifiP2pEnabled) { + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + preferenceScreen.removeAll(); - if (mPeersGroup == null) { - mPeersGroup = new PreferenceCategory(getActivity()); - } else { - mPeersGroup.removeAll(); - } + preferenceScreen.setOrderingAsAdded(true); + preferenceScreen.addPreference(mThisDevicePref); - preferenceScreen.addPreference(mThisDevicePref); + mPeersGroup.setEnabled(true); + preferenceScreen.addPreference(mPeersGroup); - mPeersGroup.setTitle(R.string.wifi_p2p_available_devices); - mPeersGroup.setEnabled(true); - preferenceScreen.addPreference(mPeersGroup); + /* Request latest set of peers */ + mWifiP2pManager.requestPeers(mChannel, WifiP2pSettings.this); + } + } - mPeers = peers; - for (WifiP2pDevice peer: peers.getDeviceList()) { - mPeersGroup.addPreference(new WifiP2pPeer(getActivity(), peer)); + private void updateSearchMenu(boolean searching) { + mWifiP2pSearching = searching; + Activity activity = getActivity(); + if (activity != null) activity.invalidateOptionsMenu(); + } + + private void startSearch() { + if (mWifiP2pManager != null && !mWifiP2pSearching) { + mWifiP2pManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() { + public void onSuccess() { + } + public void onFailure(int reason) { + if (DBG) Log.d(TAG, " discover fail " + reason); + } + }); } } private void updateDevicePref() { - mThisDevicePref = new Preference(getActivity()); - if (mThisDevice != null) { if (TextUtils.isEmpty(mThisDevice.deviceName)) { mThisDevicePref.setTitle(mThisDevice.deviceAddress); @@ -323,15 +466,9 @@ public class WifiP2pSettings extends SettingsPreferenceFragment mThisDevicePref.setTitle(mThisDevice.deviceName); } - if (mThisDevice.status == WifiP2pDevice.CONNECTED) { - String[] statusArray = getActivity().getResources().getStringArray( - R.array.wifi_p2p_status); - mThisDevicePref.setSummary(statusArray[mThisDevice.status]); - } mThisDevicePref.setPersistent(false); mThisDevicePref.setEnabled(true); mThisDevicePref.setSelectable(false); } - onPeersAvailable(mPeers); //update UI } } |