diff --git a/res/drawable/ic_add_24dp.xml b/res/drawable/ic_add_24dp.xml index 65351077df9..97178b2a3ab 100644 --- a/res/drawable/ic_add_24dp.xml +++ b/res/drawable/ic_add_24dp.xml @@ -14,11 +14,12 @@ ~ limitations under the License --> + android:width="24dp" + android:height="24dp" + android:viewportWidth="48" + android:viewportHeight="48" + android:tint="?android:attr/colorAccent"> + android:fillColor="@android:color/white" + android:pathData="M38,26L26,26l0,12l-4,0L22,26L10,26l0,-4l12,0L22,10l4,0l0,12l12,0l0,4.0z"/> diff --git a/res/drawable/ic_info.xml b/res/drawable/ic_info.xml index 13d00a41da9..6e19d26d9be 100644 --- a/res/drawable/ic_info.xml +++ b/res/drawable/ic_info.xml @@ -14,11 +14,11 @@ limitations under the License. --> + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:fillColor="@android:color/white" + android:pathData="M11,17l2,0l0,-6l-2,0l0,6.0zm1,-15.0C6.48,2 2,6.48 2,12.0s4.48,10 10,10 10,-4.48 10,-10.0S17.52,2 12,2.0zm0,18.0c-4.41,0 -8,-3.59 -8,-8.0s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8.0zM11,9l2,0L13,7l-2,0l0,2.0z"/> diff --git a/res/drawable/ic_remove_24dp.xml b/res/drawable/ic_remove_24dp.xml new file mode 100644 index 00000000000..923362360a5 --- /dev/null +++ b/res/drawable/ic_remove_24dp.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/screen_zoom_preview_action_background.xml b/res/drawable/screen_zoom_preview_action_background.xml new file mode 100644 index 00000000000..32fbb02604a --- /dev/null +++ b/res/drawable/screen_zoom_preview_action_background.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/res/layout/screen_zoom_activity.xml b/res/layout/screen_zoom_activity.xml new file mode 100644 index 00000000000..81ce1025d13 --- /dev/null +++ b/res/layout/screen_zoom_activity.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/screen_zoom_preview.xml b/res/layout/screen_zoom_preview.xml new file mode 100644 index 00000000000..7cdb6639e7f --- /dev/null +++ b/res/layout/screen_zoom_preview.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/screen_zoom_preview_item.xml b/res/layout/screen_zoom_preview_item.xml new file mode 100644 index 00000000000..68076f02b64 --- /dev/null +++ b/res/layout/screen_zoom_preview_item.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml index d15c254cc1c..7612c9720e7 100755 --- a/res/values-land/dimens.xml +++ b/res/values-land/dimens.xml @@ -48,4 +48,7 @@ 0.0 24dp + + + 160dp diff --git a/res/values/dimens.xml b/res/values/dimens.xml index dc0b186bcf2..481cc73475c 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -252,4 +252,7 @@ 8dp + + + 240dp diff --git a/res/values/strings.xml b/res/values/strings.xml index f0955a6198f..a1a19011011 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6613,23 +6613,33 @@ Quickly open camera without unlocking your screen - - Display scale - - display density zoom scale scaling - - Small - - Normal - - Large - - Larger - - Largest - - Custom (%d) + + Screen zoom + + display density screen zoom scale scaling + + Choose how zoomed you want the screen using the slider below the preview image. + + + Preview + + Make smaller + + Make larger + + + Small + + Normal + + Large + + Larger + + Largest + + Custom (%d) See all diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml index 60730a9397d..1016c2a4b71 100644 --- a/res/xml/display_settings.xml +++ b/res/xml/display_settings.xml @@ -99,12 +99,10 @@ android:entries="@array/entries_font_size" android:entryValues="@array/entryvalues_font_size" /> - + 0) { final float interval = (1 - minScale) / numSmaller; for (int i = numSmaller - 1; i >= 0; i--) { - final int density = (int) (initialDensity * (1 - (i + 1) * interval)); + final int density = (int) (normalDensity * (1 - (i + 1) * interval)); if (currentDensity == density) { currentDensityIndex = curIndex; } - values[curIndex] = Integer.toString(density); - entries[curIndex] = res.getText(SMALLER_SUMMARIES[i]); + entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]); + values[curIndex] = density; curIndex++; } } - if (currentDensity == initialDensity) { + if (currentDensity == normalDensity) { currentDensityIndex = curIndex; } - values[curIndex] = Integer.toString(DENSITY_VALUE_NORMAL); - entries[curIndex] = res.getText(DENSITY_SUMMARY_NORMAL); + values[curIndex] = normalDensity; + entries[curIndex] = res.getString(SUMMARY_NORMAL); curIndex++; if (numLarger > 0) { final float interval = (maxScale - 1) / numLarger; for (int i = 0; i < numLarger; i++) { - final int density = (int) (initialDensity * (1 + (i + 1) * interval)); + final int density = (int) (normalDensity * (1 + (i + 1) * interval)); if (currentDensity == density) { currentDensityIndex = curIndex; } - values[curIndex] = Integer.toString(density); - entries[curIndex] = res.getText(LARGER_SUMMARIES[i]); + values[curIndex] = density; + entries[curIndex] = res.getString(SUMMARIES_LARGER[i]); curIndex++; } } @@ -157,87 +153,84 @@ public class DisplayDensityPreference extends ListPreference { // We don't understand the current density. Must have been set by // someone else. Make room for another entry... values = Arrays.copyOf(values, values.length + 1); - values[curIndex] = res.getString(R.string.force_density_summary_custom, currentDensity); + values[curIndex] = currentDensity; entries = Arrays.copyOf(entries, values.length + 1); - entries[curIndex] = Integer.toString(currentDensity); + entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity); displayIndex = curIndex; } - super.setEntryValues(values); - super.setEntries(entries); - - setValueIndex(displayIndex); - - return true; + mNormalDensity = normalDensity; + mCurrentIndex = displayIndex; + mEntries = entries; + mValues = values; } - @Override - public boolean callChangeListener(Object newValue) { - final boolean allowed = super.callChangeListener(newValue); - if (allowed) { - final int density = Integer.parseInt((String) newValue); - setForcedDisplayDensity(mDisplayId, density); - } - - return allowed; + public String[] getEntries() { + return mEntries; } - @Override - public void setEntries(CharSequence[] entries) { - throw new UnsupportedOperationException(); + public int[] getValues() { + return mValues; } - @Override - public void setEntries(@ArrayRes int entriesResId) { - throw new UnsupportedOperationException(); + public int getCurrentIndex() { + return mCurrentIndex; } - @Override - public void setEntryValues(CharSequence[] entryValues) { - throw new UnsupportedOperationException(); - } - - @Override - public void setEntryValues(@ArrayRes int entryValuesResId) { - throw new UnsupportedOperationException(); + public int getNormalDensity() { + return mNormalDensity; } /** - * Returns the initial density for the specified display. + * Returns the normal (default) density for the specified display. * * @param displayId the identifier of the display - * @return the initial density of the specified display, or {@code -1} if + * @return the normal density of the specified display, or {@code -1} if * the display does not exist or the density could not be obtained */ - private static int getInitialDisplayDensity(int displayId) { - try { - IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - return wm.getInitialDisplayDensity(displayId); - } catch (RemoteException exc) { - return -1; - } + private static int getNormalDisplayDensity(int displayId) { + try { + final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + return wm.getInitialDisplayDensity(displayId); + } catch (RemoteException exc) { + return -1; + } } /** * Asynchronously applies display density changes to the specified display. * * @param displayId the identifier of the display to modify - * @param density the density to force for the specified display, or <= 0 - * to clear any previously forced density */ - private static void setForcedDisplayDensity(final int displayId, final int density) { + public static void clearForcedDisplayDensity(final int displayId) { AsyncTask.execute(new Runnable() { @Override public void run() { try { - IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - if (density <= 0) { - wm.clearForcedDisplayDensity(displayId); - } else { - wm.setForcedDisplayDensity(displayId, density); - } + final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + wm.clearForcedDisplayDensity(displayId); + } catch (RemoteException exc) { + Log.w(LOG_TAG, "Unable to clear forced display density setting"); + } + } + }); + } + + /** + * Asynchronously applies display density changes to the specified display. + * + * @param displayId the identifier of the display to modify + * @param density the density to force for the specified display + */ + public static void setForcedDisplayDensity(final int displayId, final int density) { + AsyncTask.execute(new Runnable() { + @Override + public void run() { + try { + final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + wm.setForcedDisplayDensity(displayId, density); } catch (RemoteException exc) { Log.w(LOG_TAG, "Unable to save forced display density setting"); } diff --git a/src/com/android/settings/display/ScreenZoomPreference.java b/src/com/android/settings/display/ScreenZoomPreference.java new file mode 100644 index 00000000000..78cc49c90be --- /dev/null +++ b/src/com/android/settings/display/ScreenZoomPreference.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settings.display; + +import android.content.Context; +import android.content.Intent; +import android.support.v4.content.res.TypedArrayUtils; +import android.support.v7.preference.PreferenceGroup; +import android.util.AttributeSet; + +/** + * Preference for changing the density of the display on which the preference + * is visible. + */ +public class ScreenZoomPreference extends PreferenceGroup { + public ScreenZoomPreference(Context context, AttributeSet attrs) { + super(context, attrs, TypedArrayUtils.getAttr(context, + android.support.v7.preference.R.attr.preferenceScreenStyle, + android.R.attr.preferenceScreenStyle)); + + setFragment("com.android.settings.display.ScreenZoomSettings"); + + final DisplayDensityUtils density = new DisplayDensityUtils(context); + final int defaultIndex = density.getCurrentIndex(); + if (defaultIndex < 0) { + setVisible(false); + setEnabled(false); + } else { + final String[] entries = density.getEntries(); + final int currentIndex = density.getCurrentIndex(); + setSummary(entries[currentIndex]); + } + } + + @Override + protected boolean isOnSameScreenAsChildren() { + return false; + } +} diff --git a/src/com/android/settings/display/ScreenZoomSettings.java b/src/com/android/settings/display/ScreenZoomSettings.java new file mode 100644 index 00000000000..1ddcaab386b --- /dev/null +++ b/src/com/android/settings/display/ScreenZoomSettings.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.display; + +import com.android.settings.InstrumentedFragment; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settings.search.SearchIndexableRaw; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Bundle; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.widget.FrameLayout; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +/** + * Preference fragment used to control screen zoom. + */ +public class ScreenZoomSettings extends SettingsPreferenceFragment implements Indexable { + /** Duration to use when cross-fading between previews. */ + private static final long CROSS_FADE_DURATION_MS = 400; + + /** Interpolator to use when cross-fading between previews. */ + private static final Interpolator FADE_IN_INTERPOLATOR = new DecelerateInterpolator(); + + /** Interpolator to use when cross-fading between previews. */ + private static final Interpolator FADE_OUT_INTERPOLATOR = new AccelerateInterpolator(); + + private ViewGroup mPreviewFrame; + private TextView mLabel; + private View mLarger; + private View mSmaller; + + private String[] mEntries; + private int[] mValues; + private int mNormalDensity; + private int mInitialIndex; + + private int mCurrentIndex; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final DisplayDensityUtils density = new DisplayDensityUtils(getContext()); + + final int initialIndex = density.getCurrentIndex(); + if (initialIndex < 0) { + // Failed to obtain normal density, which means we failed to + // connect to the window manager service. Just use the current + // density and don't let the user change anything. + final int densityDpi = getResources().getDisplayMetrics().densityDpi; + mValues = new int[] { densityDpi }; + mEntries = new String[] { getString(R.string.screen_zoom_summary_normal) }; + mInitialIndex = 0; + mNormalDensity = densityDpi; + } else { + mValues = density.getValues(); + mEntries = density.getEntries(); + mInitialIndex = initialIndex; + mNormalDensity = density.getNormalDensity(); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + final View root = super.onCreateView(inflater, container, savedInstanceState); + final ViewGroup list_container = (ViewGroup) root.findViewById(android.R.id.list_container); + list_container.removeAllViews(); + + final View content = inflater.inflate(R.layout.screen_zoom_activity, list_container, false); + list_container.addView(content); + + mLabel = (TextView) content.findViewById(R.id.current_density); + + // The maximum SeekBar value always needs to be non-zero. If there's + // only one available zoom level, we'll handle this by disabling the + // seek bar. + final int max = Math.max(1, mValues.length - 1); + + final SeekBar seekBar = (SeekBar) content.findViewById(R.id.seek_bar); + seekBar.setMax(max); + seekBar.setProgress(mInitialIndex); + seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + setPreviewLayer(progress, true); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} + }); + + mSmaller = content.findViewById(R.id.smaller); + mSmaller.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final int progress = seekBar.getProgress(); + if (progress > 0) { + seekBar.setProgress(progress - 1, true); + } + } + }); + + mLarger = content.findViewById(R.id.larger); + mLarger.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final int progress = seekBar.getProgress(); + if (progress < seekBar.getMax()) { + seekBar.setProgress(progress + 1, true); + } + } + }); + + if (mValues.length == 1) { + // The larger and smaller buttons will be disabled when we call + // setPreviewLayer() later in this method. + seekBar.setEnabled(false); + } + + mPreviewFrame = (FrameLayout) content.findViewById(R.id.preview_frame); + + // Populate the sample layouts. + final Context context = getContext(); + final Configuration origConfig = context.getResources().getConfiguration(); + for (int mValue : mValues) { + final Configuration config = new Configuration(origConfig); + config.densityDpi = mValue; + + // Create a new configuration for the specified density. It won't + // have any theme set, so manually apply the current theme. + final Context configContext = context.createConfigurationContext(config); + configContext.setTheme(context.getThemeResId()); + + final LayoutInflater configInflater = LayoutInflater.from(configContext); + final View sampleView = configInflater.inflate( + R.layout.screen_zoom_preview, mPreviewFrame, false); + sampleView.setAlpha(0); + sampleView.setVisibility(View.INVISIBLE); + + mPreviewFrame.addView(sampleView); + } + + setPreviewLayer(mInitialIndex, false); + + return root; + } + + @Override + public void onDetach() { + super.onDetach(); + + // This will adjust the density SLIGHTLY after the activity has + // finished, which could be considered a feature or a bug... + commit(); + } + + private void setPreviewLayer(int index, boolean animate) { + mLabel.setText(mEntries[index]); + + if (mCurrentIndex >= 0) { + final View lastLayer = mPreviewFrame.getChildAt(mCurrentIndex); + if (animate) { + lastLayer.animate() + .alpha(0) + .setInterpolator(FADE_OUT_INTERPOLATOR) + .setDuration(CROSS_FADE_DURATION_MS) + .withEndAction(new Runnable() { + @Override + public void run() { + lastLayer.setVisibility(View.INVISIBLE); + } + }); + } else { + lastLayer.setAlpha(0); + lastLayer.setVisibility(View.INVISIBLE); + } + } + + final View nextLayer = mPreviewFrame.getChildAt(index); + if (animate) { + nextLayer.animate() + .alpha(1) + .setInterpolator(FADE_IN_INTERPOLATOR) + .setDuration(CROSS_FADE_DURATION_MS) + .withStartAction(new Runnable() { + @Override + public void run() { + nextLayer.setVisibility(View.VISIBLE); + } + }); + } else { + nextLayer.setVisibility(View.VISIBLE); + nextLayer.setAlpha(1); + } + + mSmaller.setEnabled(index > 0); + mLarger.setEnabled(index < mEntries.length - 1); + + mCurrentIndex = index; + } + + /** + * Persists the selected density and sends a configuration change. + */ + private void commit() { + final int densityDpi = mValues[mCurrentIndex]; + if (densityDpi == mNormalDensity) { + DisplayDensityUtils.clearForcedDisplayDensity(Display.DEFAULT_DISPLAY); + } else { + DisplayDensityUtils.setForcedDisplayDensity(Display.DEFAULT_DISPLAY, densityDpi); + } + } + + @Override + protected int getMetricsCategory() { + return InstrumentedFragment.DISPLAY_SCREEN_ZOOM; + } + + /** Index provider used to expose this fragment in search. */ + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getRawDataToIndex(Context context, boolean enabled) { + final Resources res = context.getResources(); + final SearchIndexableRaw data = new SearchIndexableRaw(context); + data.title = res.getString(R.string.screen_zoom_title); + data.screenTitle = res.getString(R.string.screen_zoom_title); + data.keywords = res.getString(R.string.screen_zoom_keywords); + + final List result = new ArrayList<>(1); + result.add(data); + return result; + } + }; +} diff --git a/src/com/android/settings/display/TouchBlockingFrameLayout.java b/src/com/android/settings/display/TouchBlockingFrameLayout.java new file mode 100644 index 00000000000..3f5483d3d20 --- /dev/null +++ b/src/com/android/settings/display/TouchBlockingFrameLayout.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.display; + +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +/** + * Extension of FrameLayout that consumes all touch events. + */ +public class TouchBlockingFrameLayout extends FrameLayout { + public TouchBlockingFrameLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return true; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return true; + } +} diff --git a/src/com/android/settings/search/Ranking.java b/src/com/android/settings/search/Ranking.java index 4ac4fb85686..3d5ff357deb 100644 --- a/src/com/android/settings/search/Ranking.java +++ b/src/com/android/settings/search/Ranking.java @@ -36,6 +36,7 @@ import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.applications.ManageDefaultApps; import com.android.settings.bluetooth.BluetoothSettings; import com.android.settings.deviceinfo.StorageSettings; +import com.android.settings.display.ScreenZoomSettings; import com.android.settings.fuelgauge.BatterySaverSettings; import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; @@ -122,6 +123,7 @@ public final class Ranking { // Display sRankMap.put(DisplaySettings.class.getName(), RANK_DISPLAY); + sRankMap.put(ScreenZoomSettings.class.getName(), RANK_WIFI); // Wallpapers sRankMap.put(WallpaperTypeSettings.class.getName(), RANK_WALLPAPER); diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index 6d6ca088acf..f15ff638017 100644 --- a/src/com/android/settings/search/SearchIndexableResources.java +++ b/src/com/android/settings/search/SearchIndexableResources.java @@ -38,6 +38,7 @@ import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.applications.ManageDefaultApps; import com.android.settings.bluetooth.BluetoothSettings; import com.android.settings.deviceinfo.StorageSettings; +import com.android.settings.display.ScreenZoomSettings; import com.android.settings.fuelgauge.BatterySaverSettings; import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; @@ -131,6 +132,13 @@ public final class SearchIndexableResources { HomeSettings.class.getName(), R.drawable.ic_settings_home)); + sResMap.put(ScreenZoomSettings.class.getName(), + new SearchIndexableResource( + Ranking.getRankForClassName(ScreenZoomSettings.class.getName()), + NO_DATA_RES_ID, + ScreenZoomSettings.class.getName(), + R.drawable.ic_settings_display)); + sResMap.put(DisplaySettings.class.getName(), new SearchIndexableResource( Ranking.getRankForClassName(DisplaySettings.class.getName()),