Merge "Tuner: Nav bar tuner"

This commit is contained in:
Jason Monk
2017-01-26 02:23:02 +00:00
committed by Android (Google) Code Review
15 changed files with 493 additions and 599 deletions

View File

@@ -0,0 +1,24 @@
<!--
Copyright (C) 2017 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M15.41,16.09l-4.58,-4.59 4.58,-4.59L14.0,5.5l-6.0,6.0 6.0,6.0z"/>
</vector>

View File

@@ -0,0 +1,24 @@
<!--
Copyright (C) 2017 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3.0,18.0l18.0,0.0l0.0,-2.0L3.0,16.0l0.0,2.0zm0.0,-5.0l18.0,0.0l0.0,-2.0L3.0,11.0l0.0,2.0zm0.0,-7.0l0.0,2.0l18.0,0.0L21.0,6.0L3.0,6.0z"/>
</vector>

View File

@@ -0,0 +1,24 @@
<!--
Copyright (C) 2017 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19.0,13.0L5.0,13.0l0.0,-2.0l14.0,0.0l0.0,2.0z"/>
</vector>

View File

@@ -0,0 +1,24 @@
<!--
Copyright (C) 2017 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10.0,5.75l6.0,6.0 -6.0,6.0z"/>
</vector>

View File

@@ -34,7 +34,7 @@
<bool name="config_keyguardUserSwitcher">true</bool>
<!-- Nav bar button default ordering/layout -->
<string name="config_navBarLayout" translatable="false">space;back,home,recent;menu_ime</string>
<string name="config_navBarLayout" translatable="false">left;back,home,recent;right</string>
<!-- Animation duration when using long press on recents to dock -->
<integer name="long_press_dock_anim_duration">290</integer>

View File

@@ -19,6 +19,6 @@
<resources>
<!-- Nav bar button default ordering/layout -->
<string name="config_navBarLayout" translatable="false">back,home;space;menu_ime,recent</string>
<string name="config_navBarLayout" translatable="false">back,home,left;space;right,recent</string>
</resources>

View File

@@ -286,7 +286,7 @@
<string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
<!-- Nav bar button default ordering/layout -->
<string name="config_navBarLayout" translatable="false">space,back;home;recent,menu_ime</string>
<string name="config_navBarLayout" translatable="false">left,back;home;recent,right</string>
<bool name="quick_settings_show_full_alarm">false</bool>

View File

@@ -1541,56 +1541,71 @@
<!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
<string name="nav_bar">Navigation bar</string>
<!-- SysUI Tuner: Group of buttons that show on the start of the screen [CHAR LIMIT=30] -->
<string name="start">Start</string>
<!-- SysUI Tuner: Group of buttons that show on the center of the screen [CHAR LIMIT=30] -->
<string name="center">Center</string>
<!-- SysUI Tuner: Group of buttons that show on the end of the screen [CHAR LIMIT=30] -->
<string name="end">End</string>
<!-- SysUI Tuner: Name of space used in custom navigation bar layouts [CHAR LIMIT=30] -->
<string name="space">Spacer</string>
<!-- SysUI Tuner: Button that controls layout of navigation bar [CHAR LIMIT=60] -->
<string name="nav_bar_layout">Layout</string>
<!-- SysUI Tuner: Label for section of settings about the left nav button [CHAR LIMIT=60] -->
<string name="nav_bar_left">Left</string>
<!-- SysUI Tuner: Label for section of settings about the right nav button [CHAR LIMIT=60] -->
<string name="nav_bar_right">Right</string>
<!-- SysUI Tuner: Setting for button type in nav bar [CHAR LIMIT=60] -->
<string name="nav_bar_button_type">Button type</string>
<!-- SysUI Tuner: Added to nav bar option to indicate it is the default [CHAR LIMIT=60] -->
<string name="nav_bar_default"> (default)</string>
<!-- SysUI Tuner: Labels for different types of navigation bar buttons [CHAR LIMIT=60] -->
<string-array name="nav_bar_buttons">
<item>Clipboard</item>
<item>Keycode</item>
<item>Menu / Keyboard Switcher</item>
<item>None</item>
</string-array>
<string-array name="nav_bar_button_values" translatable="false">
<item>clipboard</item>
<item>key</item>
<item>menu_ime</item>
<item>space</item>
</string-array>
<!-- SysUI Tuner: Labels for different types of navigation bar layouts [CHAR LIMIT=60] -->
<string-array name="nav_bar_layouts">
<item>Divided (default)</item>
<item>Centered</item>
<item>Left-aligned</item>
<item>Right-aligned</item>
</string-array>
<string-array name="nav_bar_layouts_values" translatable="false">
<item>default</item>
<item>left;back,home,recent;right</item>
<item>left,back,home,recent,right;space;space</item>
<item>space;space;left,back,home,recent,right</item>
</string-array>
<!-- SysUI Tuner: Name of Combination Menu / Keyboard Switcher button [CHAR LIMIT=30] -->
<string name="menu_ime">Menu / Keyboard Switcher</string>
<!-- SysUI Tuner: Title for dialog to add a button [CHAR LIMIT=30] -->
<string name="select_button">Select button to add</string>
<!-- SysUI Tuner: Button to add a button [CHAR LIMIT=30] -->
<string name="add_button">Add button</string>
<!-- SysUI Tuner: Save the current settings [CHAR LIMIT=30] -->
<string name="save">Save</string>
<!-- SysUI Tuner: Reset to default settings [CHAR LIMIT=30] -->
<string name="reset">Reset</string>
<!-- SysUI Tuner: Title of no home warning dialog [CHAR LIMIT=30] -->
<string name="no_home_title">No home button found</string>
<!-- SysUI Tuner: Message of no home warning dialog [CHAR LIMIT=NONE] -->
<string name="no_home_message">A home button is required to be able to navigate this device. Please add a home button before saving.</string>
<!-- SysUI Tuner: Adjust button width dialog title [CHAR LIMIT=60] -->
<string name="adjust_button_width">Adjust button width</string>
<!-- SysUI Tuner: Nav bar button that holds the clipboard [CHAR LIMIT=30] -->
<string name="clipboard">Clipboard</string>
<!-- SysUI Tuner: Description of nav bar button that holds the clipboard [CHAR LIMIT=NONE] -->
<string name="clipboard_description">The Clipboard allows items to be dragged directly to the clipboard. Items can also be dragged directly out of the clipboard when present.</string>
<!-- SysUI Tuner: Accessibility description for custom nav key [CHAR LIMIT=NONE] -->
<string name="accessibility_key">Custom navigation button</string>
<!-- SysUI Tuner: Nav bar button that emulates a keycode [CHAR LIMIT=30] -->
<string name="keycode">Keycode</string>
<!-- SysUI Tuner: Description of nav bar button that emulates a keycode [CHAR LIMIT=NONE] -->
<string name="keycode_description">Keycode buttons allow keyboard keys to
be added to the Navigation Bar. When pressed they emulate the selected
keyboard key. First the key must be selected for the button, followed
by an image to be shown on the button.</string>
<!-- SysUI Tuner: Title of dialog to select which key to emulate [CHAR LIMIT=60] -->
<string name="select_keycode">Select Keyboard Button</string>
<!-- SysUI Tuner: Label for preview area in navigation bar tuner [CHAR LIMIT=NONE] -->
<string name="preview">Preview</string>
<!-- SysUI Tuner: Settings to change nav bar icon [CHAR LIMIT=30] -->
<string name="icon">Icon</string>
<!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] -->
<string name="drag_to_add_tiles">Drag to add tiles</string>

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:title="@string/nav_bar">
<ListPreference
android:key="layout"
android:title="@string/nav_bar_layout"
android:summary="%s"
android:persistent="false"
android:entries="@array/nav_bar_layouts"
android:entryValues="@array/nav_bar_layouts_values" />
<PreferenceCategory
android:key="left"
android:title="@string/nav_bar_left">
<DropDownPreference
android:key="type_left"
android:title="@string/nav_bar_button_type"
android:persistent="false"
android:summary="%s"
android:entries="@array/nav_bar_buttons"
android:entryValues="@array/nav_bar_button_values" />
<Preference
android:key="keycode_left"
android:persistent="false"
android:title="@string/keycode" />
<com.android.systemui.tuner.BetterListPreference
android:key="icon_left"
android:persistent="false"
android:summary="%s"
android:title="@string/icon" />
</PreferenceCategory>
<PreferenceCategory
android:key="right"
android:title="@string/nav_bar_right">
<DropDownPreference
android:key="type_right"
android:title="@string/nav_bar_button_type"
android:summary="%s"
android:persistent="false"
android:entries="@array/nav_bar_buttons"
android:entryValues="@array/nav_bar_button_values" />
<Preference
android:key="keycode_right"
android:persistent="false"
android:title="@string/keycode" />
<com.android.systemui.tuner.BetterListPreference
android:key="icon_right"
android:persistent="false"
android:summary="%s"
android:title="@string/icon" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -150,12 +150,10 @@
</PreferenceScreen>
<!--
<Preference
android:key="nav_bar"
android:title="@string/nav_bar"
android:fragment="com.android.systemui.tuner.NavBarTuner" />
-->
<Preference
android:key="other"

View File

@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.Display;
@@ -47,6 +48,8 @@ public class NavigationBarInflaterView extends FrameLayout
private static final String TAG = "NavBarInflater";
public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
public static final String NAV_BAR_LEFT = "sysui_nav_bar_left";
public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right";
public static final String MENU_IME = "menu_ime";
public static final String BACK = "back";
@@ -55,6 +58,8 @@ public class NavigationBarInflaterView extends FrameLayout
public static final String NAVSPACE = "space";
public static final String CLIPBOARD = "clipboard";
public static final String KEY = "key";
public static final String LEFT = "left";
public static final String RIGHT = "right";
public static final String GRAVITY_SEPARATOR = ";";
public static final String BUTTON_SEPARATOR = ",";
@@ -130,7 +135,8 @@ public class NavigationBarInflaterView extends FrameLayout
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
NAV_BAR_RIGHT);
PluginManager.getInstance(getContext()).addPluginListener(NavBarButtonProvider.ACTION, this,
NavBarButtonProvider.VERSION, true /* Allow multiple */);
}
@@ -148,6 +154,9 @@ public class NavigationBarInflaterView extends FrameLayout
clearViews();
inflateLayout(newValue);
}
} else if (NAV_BAR_LEFT.equals(key) || NAV_BAR_RIGHT.equals(key)) {
clearViews();
inflateLayout(mCurrentLayout);
}
}
@@ -268,6 +277,13 @@ public class NavigationBarInflaterView extends FrameLayout
boolean landscape) {
View v = null;
String button = extractButton(buttonSpec);
if (LEFT.equals(button)) {
buttonSpec = TunerService.get(mContext).getValue(NAV_BAR_LEFT, NAVSPACE);
button = extractButton(buttonSpec);
} else if (RIGHT.equals(button)) {
buttonSpec = TunerService.get(mContext).getValue(NAV_BAR_RIGHT, MENU_IME);
button = extractButton(buttonSpec);
}
// Let plugins go first so they can override a standard view if they want.
for (NavBarButtonProvider provider : mPlugins) {
v = provider.createView(buttonSpec, parent);
@@ -291,7 +307,14 @@ public class NavigationBarInflaterView extends FrameLayout
v = inflater.inflate(R.layout.custom_key, parent, false);
((KeyButtonView) v).setCode(code);
if (uri != null) {
((KeyButtonView) v).loadAsync(uri);
if (uri.contains(":")) {
((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
} else if (uri.contains("/")) {
int index = uri.indexOf('/');
String pkg = uri.substring(0, index);
int id = Integer.parseInt(uri.substring(index + 1));
((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
}
}
}
return v;

View File

@@ -116,18 +116,18 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
mOnClickListener = onClickListener;
}
public void loadAsync(String uri) {
new AsyncTask<String, Void, Drawable>() {
public void loadAsync(Icon icon) {
new AsyncTask<Icon, Void, Drawable>() {
@Override
protected Drawable doInBackground(String... params) {
return Icon.createWithContentUri(params[0]).loadDrawable(mContext);
protected Drawable doInBackground(Icon... params) {
return params[0].loadDrawable(mContext);
}
@Override
protected void onPostExecute(Drawable drawable) {
setImageDrawable(drawable);
}
}.execute(uri);
}.execute(icon);
}
@Override

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2017 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.systemui.tuner;
import android.content.Context;
import android.support.v7.preference.ListPreference;
import android.util.AttributeSet;
public class BetterListPreference extends ListPreference {
private CharSequence mSummary;
public BetterListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setSummary(CharSequence summary) {
super.setSummary(summary);
mSummary = summary;
}
@Override
public CharSequence getSummary() {
return mSummary;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 The Android Open Source Project
* Copyright (C) 2017 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
@@ -14,590 +14,227 @@
package com.android.systemui.tuner;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.TypedValue;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import com.android.systemui.R;
import java.util.ArrayList;
import java.util.List;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BACK;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BUTTON_SEPARATOR;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.CLIPBOARD;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.GRAVITY_SEPARATOR;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.HOME;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_LEFT;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_RIGHT;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.RECENT;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_END;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_START;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractButton;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractSize;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractImage;
import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractKeycode;
public class NavBarTuner extends Fragment implements TunerService.Tunable {
import android.annotation.Nullable;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Handler;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.DropDownPreference;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceChangeListener;
import android.support.v7.preference.Preference.OnPreferenceClickListener;
import android.support.v7.preference.PreferenceCategory;
import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.widget.EditText;
private static final int SAVE = Menu.FIRST + 1;
private static final int RESET = Menu.FIRST + 2;
private static final int READ_REQUEST = 42;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.NavigationBarInflaterView;
import com.android.systemui.tuner.TunerService.Tunable;
private static final float PREVIEW_SCALE = .95f;
private static final float PREVIEW_SCALE_LANDSCAPE = .75f;
import java.util.ArrayList;
private NavBarAdapter mNavBarAdapter;
private PreviewNavInflater mPreview;
public class NavBarTuner extends PreferenceFragment {
private static final String LAYOUT = "layout";
private static final String LEFT = "left";
private static final String RIGHT = "right";
private static final String TYPE = "type";
private static final String KEYCODE = "keycode";
private static final String ICON = "icon";
private static final int[] ICONS = new int[]{
R.drawable.ic_qs_circle,
R.drawable.ic_add,
R.drawable.ic_remove,
R.drawable.ic_left,
R.drawable.ic_right,
R.drawable.ic_menu,
};
private final ArrayList<Tunable> mTunables = new ArrayList<>();
private Handler mHandler;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.nav_bar_tuner, container, false);
inflatePreview((ViewGroup) view.findViewById(R.id.nav_preview_frame));
return view;
}
private void inflatePreview(ViewGroup view) {
Display display = getActivity().getWindowManager().getDefaultDisplay();
boolean isRotated = display.getRotation() == Surface.ROTATION_90
|| display.getRotation() == Surface.ROTATION_270;
Configuration config = new Configuration(getContext().getResources().getConfiguration());
boolean isPhoneLandscape = isRotated && (config.smallestScreenWidthDp < 600);
final float scale = isPhoneLandscape ? PREVIEW_SCALE_LANDSCAPE : PREVIEW_SCALE;
config.densityDpi = (int) (config.densityDpi * scale);
mPreview = (PreviewNavInflater) LayoutInflater.from(getContext().createConfigurationContext(
config)).inflate(R.layout.nav_bar_tuner_inflater, view, false);
final ViewGroup.LayoutParams layoutParams = mPreview.getLayoutParams();
layoutParams.width = (int) ((isPhoneLandscape ? display.getHeight() : display.getWidth())
* scale);
// Not sure why, but the height dimen is not being scaled with the dp, set it manually
// for now.
layoutParams.height = (int) (layoutParams.height * scale);
if (isPhoneLandscape) {
int width = layoutParams.width;
layoutParams.width = layoutParams.height;
layoutParams.height = width;
}
view.addView(mPreview);
if (isRotated) {
mPreview.findViewById(R.id.rot0).setVisibility(View.GONE);
final View rot90 = mPreview.findViewById(R.id.rot90);
} else {
mPreview.findViewById(R.id.rot90).setVisibility(View.GONE);
final View rot0 = mPreview.findViewById(R.id.rot0);
}
}
private void notifyChanged() {
mPreview.onTuningChanged(NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
public void onCreate(@Nullable Bundle savedInstanceState) {
mHandler = new Handler();
super.onCreate(savedInstanceState);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
RecyclerView recyclerView = (RecyclerView) view.findViewById(android.R.id.list);
final Context context = getContext();
recyclerView.setLayoutManager(new LinearLayoutManager(context));
mNavBarAdapter = new NavBarAdapter(context);
recyclerView.setAdapter(mNavBarAdapter);
recyclerView.addItemDecoration(new Dividers(context));
final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mNavBarAdapter.mCallbacks);
mNavBarAdapter.setTouchHelper(itemTouchHelper);
itemTouchHelper.attachToRecyclerView(recyclerView);
TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public void onDestroyView() {
super.onDestroyView();
TunerService.get(getContext()).removeTunable(this);
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.nav_bar_tuner);
bindLayout((ListPreference) findPreference(LAYOUT));
bindButton((PreferenceCategory) findPreference(LEFT),
NAV_BAR_LEFT, NAVSPACE);
bindButton((PreferenceCategory) findPreference(RIGHT),
NAV_BAR_RIGHT, MENU_IME);
}
@Override
public void onTuningChanged(String key, String navLayout) {
if (!NAV_BAR_VIEWS.equals(key)) return;
Context context = getContext();
if (navLayout == null) {
navLayout = context.getString(R.string.config_navBarLayout);
}
String[] views = navLayout.split(GRAVITY_SEPARATOR);
String[] groups = new String[] { NavBarAdapter.START, NavBarAdapter.CENTER,
NavBarAdapter.END};
CharSequence[] groupLabels = new String[] { getString(R.string.start),
getString(R.string.center), getString(R.string.end) };
mNavBarAdapter.clear();
for (int i = 0; i < 3; i++) {
mNavBarAdapter.addButton(groups[i], groupLabels[i]);
for (String button : views[i].split(BUTTON_SEPARATOR)) {
mNavBarAdapter.addButton(button, getLabel(button, context));
public void onDestroy() {
super.onDestroy();
mTunables.forEach(t -> TunerService.get(getContext()).removeTunable(t));
}
private void addTunable(Tunable tunable, String... keys) {
mTunables.add(tunable);
TunerService.get(getContext()).addTunable(tunable, keys);
}
private void bindLayout(ListPreference preference) {
addTunable((key, newValue) -> mHandler.post(() -> {
String val = newValue;
if (val == null) {
val = "default";
}
}
mNavBarAdapter.addButton(NavBarAdapter.ADD, getString(R.string.add_button));
setHasOptionsMenu(true);
preference.setValue(val);
}), NAV_BAR_VIEWS);
preference.setOnPreferenceChangeListener((preference1, newValue) -> {
String val = (String) newValue;
if ("default".equals(val)) val = null;
TunerService.get(getContext()).setValue(NAV_BAR_VIEWS, val);
return true;
});
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
// TODO: Show save button conditionally, only when there are changes.
menu.add(Menu.NONE, SAVE, Menu.NONE, getString(R.string.save))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(Menu.NONE, RESET, Menu.NONE, getString(R.string.reset));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == SAVE) {
if (!mNavBarAdapter.hasHomeButton()) {
new AlertDialog.Builder(getContext())
.setTitle(R.string.no_home_title)
.setMessage(R.string.no_home_message)
.setPositiveButton(android.R.string.ok, null)
.show();
private void bindButton(PreferenceCategory parent, String setting, String def) {
String k = parent.getKey();
DropDownPreference type = (DropDownPreference) findPreference(TYPE + "_" + k);
Preference keycode = findPreference(KEYCODE + "_" + k);
ListPreference icon = (ListPreference) findPreference(ICON + "_" + k);
setupIcons(icon);
addTunable((key, newValue) -> mHandler.post(() -> {
String val = newValue;
if (val == null) {
val = def;
}
String button = extractButton(val);
if (button.startsWith(KEY)) {
type.setValue(KEY);
String uri = extractImage(button);
int code = extractKeycode(button);
icon.setValue(uri);
updateSummary(icon);
keycode.setSummary(code + "");
keycode.setVisible(true);
icon.setVisible(true);
} else {
Settings.Secure.putString(getContext().getContentResolver(),
NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
type.setValue(button);
keycode.setVisible(false);
icon.setVisible(false);
}
return true;
} else if (item.getItemId() == RESET) {
Settings.Secure.putString(getContext().getContentResolver(),
NAV_BAR_VIEWS, null);
return true;
}
return super.onOptionsItemSelected(item);
}
private static CharSequence getLabel(String button, Context context) {
if (button.startsWith(HOME)) {
return context.getString(R.string.accessibility_home);
} else if (button.startsWith(BACK)) {
return context.getString(R.string.accessibility_back);
} else if (button.startsWith(RECENT)) {
return context.getString(R.string.accessibility_recent);
} else if (button.startsWith(NAVSPACE)) {
return context.getString(R.string.space);
} else if (button.startsWith(MENU_IME)) {
return context.getString(R.string.menu_ime);
} else if (button.startsWith(CLIPBOARD)) {
return context.getString(R.string.clipboard);
} else if (button.startsWith(KEY)) {
return context.getString(R.string.keycode);
}
return button;
}
private static class Holder extends RecyclerView.ViewHolder {
private TextView title;
public Holder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(android.R.id.title);
}
}
private static class Dividers extends RecyclerView.ItemDecoration {
private final Drawable mDivider;
public Dividers(Context context) {
TypedValue value = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.listDivider, value, true);
mDivider = context.getDrawable(value.resourceId);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
private void selectImage() {
startActivityForResult(KeycodeSelectionHelper.getSelectImageIntent(), READ_REQUEST);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == READ_REQUEST && resultCode == Activity.RESULT_OK && data != null) {
final Uri uri = data.getData();
final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION);
getContext().getContentResolver().takePersistableUriPermission(uri, takeFlags);
mNavBarAdapter.onImageSelected(uri);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private class NavBarAdapter extends RecyclerView.Adapter<Holder>
implements View.OnClickListener {
private static final String START = "start";
private static final String CENTER = "center";
private static final String END = "end";
private static final String ADD = "add";
private static final int ADD_ID = 0;
private static final int BUTTON_ID = 1;
private static final int CATEGORY_ID = 2;
private List<String> mButtons = new ArrayList<>();
private List<CharSequence> mLabels = new ArrayList<>();
private int mCategoryLayout;
private int mButtonLayout;
private ItemTouchHelper mTouchHelper;
// Stored keycode while we wait for image selection on a KEY.
private int mKeycode;
public NavBarAdapter(Context context) {
TypedArray attrs = context.getTheme().obtainStyledAttributes(null,
android.R.styleable.Preference, android.R.attr.preferenceStyle, 0);
mButtonLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0);
attrs = context.getTheme().obtainStyledAttributes(null,
android.R.styleable.Preference, android.R.attr.preferenceCategoryStyle, 0);
mCategoryLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0);
}
public void setTouchHelper(ItemTouchHelper itemTouchHelper) {
mTouchHelper = itemTouchHelper;
}
public void clear() {
mButtons.clear();
mLabels.clear();
notifyDataSetChanged();
}
public void addButton(String button, CharSequence label) {
mButtons.add(button);
mLabels.add(label);
notifyItemInserted(mLabels.size() - 1);
notifyChanged();
}
public boolean hasHomeButton() {
final int N = mButtons.size();
for (int i = 0; i < N; i++) {
if (mButtons.get(i).startsWith(HOME)) {
return true;
}
}
return false;
}
public String getNavString() {
StringBuilder builder = new StringBuilder();
for (int i = 1; i < mButtons.size() - 1; i++) {
String button = mButtons.get(i);
if (button.equals(CENTER) || button.equals(END)) {
if (builder.length() == 0 || builder.toString().endsWith(GRAVITY_SEPARATOR)) {
// No start or center buttons, fill with a space.
builder.append(NAVSPACE);
}
builder.append(GRAVITY_SEPARATOR);
continue;
} else if (builder.length() != 0 && !builder.toString().endsWith(
GRAVITY_SEPARATOR)) {
builder.append(BUTTON_SEPARATOR);
}
builder.append(button);
}
if (builder.toString().endsWith(GRAVITY_SEPARATOR)) {
// No end buttons, fill with space.
builder.append(NAVSPACE);
}
return builder.toString();
}
@Override
public int getItemViewType(int position) {
String button = mButtons.get(position);
if (button.equals(START) || button.equals(CENTER) || button.equals(END)) {
return CATEGORY_ID;
}
if (button.equals(ADD)) {
return ADD_ID;
}
return BUTTON_ID;
}
@Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
final Context context = parent.getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
final View view = inflater.inflate(getLayoutId(viewType), parent, false);
if (viewType == BUTTON_ID) {
inflater.inflate(R.layout.nav_control_widget,
(ViewGroup) view.findViewById(android.R.id.widget_frame));
}
return new Holder(view);
}
private int getLayoutId(int viewType) {
if (viewType == CATEGORY_ID) {
return mCategoryLayout;
}
return mButtonLayout;
}
@Override
public void onBindViewHolder(Holder holder, int position) {
holder.title.setText(mLabels.get(position));
if (holder.getItemViewType() == BUTTON_ID) {
bindButton(holder, position);
} else if (holder.getItemViewType() == ADD_ID) {
bindAdd(holder);
}
}
private void bindAdd(Holder holder) {
TypedValue value = new TypedValue();
final Context context = holder.itemView.getContext();
context.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true);
final ImageView icon = (ImageView) holder.itemView.findViewById(android.R.id.icon);
icon.setImageResource(R.drawable.ic_add);
icon.setImageTintList(ColorStateList.valueOf(context.getColor(value.resourceId)));
holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE);
holder.itemView.setClickable(true);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showAddDialog(v.getContext());
}
}), setting);
OnPreferenceChangeListener listener = (preference, newValue) -> {
mHandler.post(() -> {
setValue(setting, type, keycode, icon);
updateSummary(icon);
});
}
private void bindButton(final Holder holder, int position) {
holder.itemView.findViewById(android.R.id.icon_frame).setVisibility(View.GONE);
holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE);
bindClick(holder.itemView.findViewById(R.id.close), holder);
bindClick(holder.itemView.findViewById(R.id.width), holder);
holder.itemView.findViewById(R.id.drag).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mTouchHelper.startDrag(holder);
return true;
}
});
}
private void showAddDialog(final Context context) {
final String[] options = new String[] {
BACK, HOME, RECENT, MENU_IME, NAVSPACE, CLIPBOARD, KEY,
};
final CharSequence[] labels = new CharSequence[options.length];
for (int i = 0; i < options.length; i++) {
labels[i] = getLabel(options[i], context);
}
new AlertDialog.Builder(context)
.setTitle(R.string.select_button)
.setItems(labels, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (KEY.equals(options[which])) {
showKeyDialogs(context);
} else {
int index = mButtons.size() - 1;
showAddedMessage(context, options[which]);
mButtons.add(index, options[which]);
mLabels.add(index, labels[which]);
notifyItemInserted(index);
notifyChanged();
}
}
}).setNegativeButton(android.R.string.cancel, null)
.show();
}
private void onImageSelected(Uri uri) {
int index = mButtons.size() - 1;
mButtons.add(index, KEY + KEY_CODE_START + mKeycode + KEY_IMAGE_DELIM + uri.toString()
+ KEY_CODE_END);
mLabels.add(index, getLabel(KEY, getContext()));
notifyItemInserted(index);
notifyChanged();
}
private void showKeyDialogs(final Context context) {
final KeycodeSelectionHelper.OnSelectionComplete listener =
new KeycodeSelectionHelper.OnSelectionComplete() {
@Override
public void onSelectionComplete(int code) {
mKeycode = code;
selectImage();
}
};
new AlertDialog.Builder(context)
.setTitle(R.string.keycode)
.setMessage(R.string.keycode_description)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
KeycodeSelectionHelper.showKeycodeSelect(context, listener);
}
}).show();
}
private void showAddedMessage(Context context, String button) {
if (CLIPBOARD.equals(button)) {
new AlertDialog.Builder(context)
.setTitle(R.string.clipboard)
.setMessage(R.string.clipboard_description)
.setPositiveButton(android.R.string.ok, null)
.show();
}
}
private void bindClick(View view, Holder holder) {
view.setOnClickListener(this);
view.setTag(holder);
}
@Override
public void onClick(View v) {
Holder holder = (Holder) v.getTag();
if (v.getId() == R.id.width) {
showWidthDialog(holder, v.getContext());
} else if (v.getId() == R.id.close) {
int position = holder.getAdapterPosition();
mButtons.remove(position);
mLabels.remove(position);
notifyItemRemoved(position);
notifyChanged();
}
}
private void showWidthDialog(final Holder holder, Context context) {
final String buttonSpec = mButtons.get(holder.getAdapterPosition());
float amount = extractSize(buttonSpec);
final AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(R.string.adjust_button_width)
.setView(R.layout.nav_width_view)
.setNegativeButton(android.R.string.cancel, null).create();
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
final String button = extractButton(buttonSpec);
SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekbar);
if (seekBar.getProgress() == 75) {
mButtons.set(holder.getAdapterPosition(), button);
} else {
float amount = (seekBar.getProgress() + 25) / 100f;
mButtons.set(holder.getAdapterPosition(), button
+ SIZE_MOD_START + amount + SIZE_MOD_END);
}
notifyChanged();
}
});
dialog.show();
SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekbar);
// Range is .25 - 1.75.
seekBar.setMax(150);
seekBar.setProgress((int) ((amount - .25f) * 100));
}
@Override
public int getItemCount() {
return mButtons.size();
}
private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public int getMovementFlags(RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder) {
if (viewHolder.getItemViewType() != BUTTON_ID) {
return makeMovementFlags(0, 0);
}
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
return makeMovementFlags(dragFlags, 0);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
if (to == 0) {
// Can't go above the top.
return false;
}
move(from, to, mButtons);
move(from, to, mLabels);
notifyChanged();
notifyItemMoved(from, to);
return true;
}
private <T> void move(int from, int to, List<T> list) {
list.add(from > to ? to : to + 1, list.get(from));
list.remove(from > to ? from + 1 : from);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// Don't care.
}
return true;
};
type.setOnPreferenceChangeListener(listener);
icon.setOnPreferenceChangeListener(listener);
keycode.setOnPreferenceClickListener(preference -> {
EditText editText = new EditText(getContext());
new AlertDialog.Builder(getContext())
.setTitle(preference.getTitle())
.setView(editText)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
int code = KeyEvent.KEYCODE_ENTER;
try {
code = Integer.parseInt(editText.getText().toString());
} catch (Exception e) {
}
keycode.setSummary(code + "");
setValue(setting, type, keycode, icon);
}).show();
return true;
});
}
private void updateSummary(ListPreference icon) {
try {
int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14,
getContext().getResources().getDisplayMetrics());
String pkg = icon.getValue().split("/")[0];
int id = Integer.parseInt(icon.getValue().split("/")[1]);
SpannableStringBuilder builder = new SpannableStringBuilder();
Drawable d = Icon.createWithResource(pkg, id)
.loadDrawable(getContext());
d.setTint(Color.BLACK);
d.setBounds(0, 0, size, size);
ImageSpan span = new ImageSpan(d);
builder.append(" ", span, 0);
icon.setSummary(builder);
} catch (Exception e) {
Log.d("NavButton", "Problem with summary", e);
icon.setSummary(null);
}
}
private void setValue(String setting, DropDownPreference type, Preference keycode,
ListPreference icon) {
String button = type.getValue();
if (KEY.equals(button)) {
String uri = icon.getValue();
int code = KeyEvent.KEYCODE_ENTER;
try {
code = Integer.parseInt(keycode.getSummary().toString());
} catch (Exception e) {
}
button = button + KEY_CODE_START + code + KEY_IMAGE_DELIM + uri + KEY_CODE_END;
}
TunerService.get(getContext()).setValue(setting, button);
}
private void setupIcons(ListPreference icon) {
CharSequence[] labels = new CharSequence[ICONS.length];
CharSequence[] values = new CharSequence[ICONS.length];
int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14,
getContext().getResources().getDisplayMetrics());
for (int i = 0; i < ICONS.length; i++) {
SpannableStringBuilder builder = new SpannableStringBuilder();
Drawable d = Icon.createWithResource(getContext().getPackageName(), ICONS[i])
.loadDrawable(getContext());
d.setTint(Color.BLACK);
d.setBounds(0, 0, size, size);
ImageSpan span = new ImageSpan(d);
builder.append(" ", span, 0);
labels[i] = builder;
values[i] = getContext().getPackageName() + "/" + ICONS[i];
}
icon.setEntries(labels);
icon.setEntryValues(values);
}
}

View File

@@ -33,6 +33,7 @@ import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -126,6 +127,12 @@ public class TunerService extends SystemUI {
return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser);
}
public String getValue(String setting, String def) {
String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
if (ret == null) return def;
return ret;
}
public void setValue(String setting, int value) {
Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser);
}