Make flags UI available on release build of launcher
The UI will only be shown on eng/userdebug platform builds. Bug: 117223984 Change-Id: I27843f2d856a4a19f3fe53c4d306606eaa5714a2
This commit is contained in:
parent
cf7715511e
commit
fa530cd23f
|
@ -22,14 +22,10 @@ import android.content.Context;
|
||||||
* Defines a set of flags used to control various launcher behaviors
|
* Defines a set of flags used to control various launcher behaviors
|
||||||
*/
|
*/
|
||||||
public final class FeatureFlags extends BaseFlags {
|
public final class FeatureFlags extends BaseFlags {
|
||||||
private static FeatureFlags instance = new FeatureFlags();
|
private FeatureFlags() {
|
||||||
|
// Prevent instantiation
|
||||||
public static FeatureFlags getInstance(Context context) {
|
|
||||||
return instance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private FeatureFlags() {}
|
|
||||||
|
|
||||||
// Features to control Launcher3Go behavior
|
// Features to control Launcher3Go behavior
|
||||||
public static final boolean GO_DISABLE_WIDGETS = true;
|
public static final boolean GO_DISABLE_WIDGETS = true;
|
||||||
public static final boolean LAUNCHER3_SPRING_ICONS = false;
|
public static final boolean LAUNCHER3_SPRING_ICONS = false;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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"
|
||||||
|
android:key="feature_flags"
|
||||||
|
android:persistent="false">
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
|
@ -52,4 +52,10 @@
|
||||||
android:defaultValue=""
|
android:defaultValue=""
|
||||||
android:persistent="false" />
|
android:persistent="false" />
|
||||||
|
|
||||||
|
<PreferenceScreen
|
||||||
|
android:fragment="com.android.launcher3.config.FlagTogglerPreferenceFragment"
|
||||||
|
android:key="flag_toggler"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="Feature flags"/>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
|
@ -1753,12 +1753,12 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
||||||
@Override
|
@Override
|
||||||
public void bindScreens(IntArray orderedScreenIds) {
|
public void bindScreens(IntArray orderedScreenIds) {
|
||||||
// Make sure the first screen is always at the start.
|
// Make sure the first screen is always at the start.
|
||||||
if (FeatureFlags.getInstance(this).isQsbOnFirstScreenEnabled() &&
|
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
|
||||||
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
|
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
|
||||||
orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
|
orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
|
||||||
orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
|
orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
|
||||||
LauncherModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
|
LauncherModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
|
||||||
} else if (!FeatureFlags.getInstance(this).isQsbOnFirstScreenEnabled()
|
} else if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()
|
||||||
&& orderedScreenIds.isEmpty()) {
|
&& orderedScreenIds.isEmpty()) {
|
||||||
// If there are no screens, we need to have an empty screen
|
// If there are no screens, we need to have an empty screen
|
||||||
mWorkspace.addExtraEmptyScreen();
|
mWorkspace.addExtraEmptyScreen();
|
||||||
|
@ -1775,8 +1775,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
|
||||||
int count = orderedScreenIds.size();
|
int count = orderedScreenIds.size();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
int screenId = orderedScreenIds.get(i);
|
int screenId = orderedScreenIds.get(i);
|
||||||
if (!FeatureFlags.getInstance(this).isQsbOnFirstScreenEnabled()
|
if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenId != Workspace.FIRST_SCREEN_ID) {
|
||||||
|| screenId != Workspace.FIRST_SCREEN_ID) {
|
|
||||||
// No need to bind the first screen, as its always bound.
|
// No need to bind the first screen, as its always bound.
|
||||||
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
|
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,9 +70,6 @@ import java.io.PrintWriter;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
|
|
||||||
public class LauncherProvider extends ContentProvider {
|
public class LauncherProvider extends ContentProvider {
|
||||||
private static final String TAG = "LauncherProvider";
|
private static final String TAG = "LauncherProvider";
|
||||||
|
@ -793,7 +790,7 @@ public class LauncherProvider extends ContentProvider {
|
||||||
convertShortcutsToLauncherActivities(db);
|
convertShortcutsToLauncherActivities(db);
|
||||||
case 26:
|
case 26:
|
||||||
// QSB was moved to the grid. Clear the first row on screen 0.
|
// QSB was moved to the grid. Clear the first row on screen 0.
|
||||||
if (FeatureFlags.getInstance(mContext).isQsbOnFirstScreenEnabled() &&
|
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() &&
|
||||||
!LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
|
!LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package com.android.launcher3;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.android.launcher3.config.FeatureFlags;
|
||||||
import com.android.launcher3.graphics.IconShapeOverride;
|
import com.android.launcher3.graphics.IconShapeOverride;
|
||||||
import com.android.launcher3.logging.FileLog;
|
import com.android.launcher3.logging.FileLog;
|
||||||
import com.android.launcher3.util.ResourceBasedOverride;
|
import com.android.launcher3.util.ResourceBasedOverride;
|
||||||
|
@ -35,6 +36,7 @@ public class MainProcessInitializer implements ResourceBasedOverride {
|
||||||
|
|
||||||
protected void init(Context context) {
|
protected void init(Context context) {
|
||||||
FileLog.setDir(context.getApplicationContext().getFilesDir());
|
FileLog.setDir(context.getApplicationContext().getFilesDir());
|
||||||
|
FeatureFlags.initialize(context);
|
||||||
IconShapeOverride.apply(context);
|
IconShapeOverride.apply(context);
|
||||||
SessionCommitReceiver.applyDefaultUserPrefs(context);
|
SessionCommitReceiver.applyDefaultUserPrefs(context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import android.view.View;
|
||||||
import android.widget.Adapter;
|
import android.widget.Adapter;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import com.android.launcher3.config.FeatureFlags;
|
||||||
import com.android.launcher3.graphics.IconShapeOverride;
|
import com.android.launcher3.graphics.IconShapeOverride;
|
||||||
import com.android.launcher3.notification.NotificationListener;
|
import com.android.launcher3.notification.NotificationListener;
|
||||||
import com.android.launcher3.util.ListViewHighlighter;
|
import com.android.launcher3.util.ListViewHighlighter;
|
||||||
|
@ -57,6 +58,8 @@ import java.util.Objects;
|
||||||
public class SettingsActivity extends Activity
|
public class SettingsActivity extends Activity
|
||||||
implements PreferenceFragment.OnPreferenceStartFragmentCallback {
|
implements PreferenceFragment.OnPreferenceStartFragmentCallback {
|
||||||
|
|
||||||
|
private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
|
||||||
|
|
||||||
private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
|
private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
|
||||||
/** Hidden field Settings.Secure.NOTIFICATION_BADGING */
|
/** Hidden field Settings.Secure.NOTIFICATION_BADGING */
|
||||||
public static final String NOTIFICATION_BADGING = "notification_badging";
|
public static final String NOTIFICATION_BADGING = "notification_badging";
|
||||||
|
@ -126,6 +129,12 @@ public class SettingsActivity extends Activity
|
||||||
getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
|
getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
|
||||||
addPreferencesFromResource(R.xml.launcher_preferences);
|
addPreferencesFromResource(R.xml.launcher_preferences);
|
||||||
|
|
||||||
|
// Only show flag toggler UI if this build variant implements that.
|
||||||
|
Preference flagToggler = findPreference(FLAGS_PREFERENCE_KEY);
|
||||||
|
if (flagToggler != null && !FeatureFlags.showFlagTogglerUi()) {
|
||||||
|
getPreferenceScreen().removePreference(flagToggler);
|
||||||
|
}
|
||||||
|
|
||||||
ContentResolver resolver = getActivity().getContentResolver();
|
ContentResolver resolver = getActivity().getContentResolver();
|
||||||
|
|
||||||
ButtonPreference iconBadgingPref =
|
ButtonPreference iconBadgingPref =
|
||||||
|
|
|
@ -480,7 +480,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
|
||||||
* @param qsb an existing qsb to recycle or null.
|
* @param qsb an existing qsb to recycle or null.
|
||||||
*/
|
*/
|
||||||
public void bindAndInitFirstWorkspaceScreen(View qsb) {
|
public void bindAndInitFirstWorkspaceScreen(View qsb) {
|
||||||
if (!FeatureFlags.getInstance(getContext()).isQsbOnFirstScreenEnabled()) {
|
if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Add the first page
|
// Add the first page
|
||||||
|
@ -779,9 +779,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
|
||||||
int id = mWorkspaceScreens.keyAt(i);
|
int id = mWorkspaceScreens.keyAt(i);
|
||||||
CellLayout cl = mWorkspaceScreens.valueAt(i);
|
CellLayout cl = mWorkspaceScreens.valueAt(i);
|
||||||
// FIRST_SCREEN_ID can never be removed.
|
// FIRST_SCREEN_ID can never be removed.
|
||||||
boolean qsbFirstScreenEnabled =
|
if ((!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || id > FIRST_SCREEN_ID)
|
||||||
FeatureFlags.getInstance(getContext()).isQsbOnFirstScreenEnabled();
|
|
||||||
if ((!qsbFirstScreenEnabled || id > FIRST_SCREEN_ID)
|
|
||||||
&& cl.getShortcutsAndWidgets().getChildCount() == 0) {
|
&& cl.getShortcutsAndWidgets().getChildCount() == 0) {
|
||||||
removeScreens.add(id);
|
removeScreens.add(id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,21 @@
|
||||||
|
|
||||||
package com.android.launcher3.config;
|
package com.android.launcher3.config;
|
||||||
|
|
||||||
|
import static androidx.core.util.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import androidx.annotation.GuardedBy;
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
import com.android.launcher3.Utilities;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a set of flags used to control various launcher behaviors.
|
* Defines a set of flags used to control various launcher behaviors.
|
||||||
*
|
*
|
||||||
|
@ -23,11 +38,21 @@ package com.android.launcher3.config;
|
||||||
*
|
*
|
||||||
* <p>This class is kept package-private to prevent direct access.
|
* <p>This class is kept package-private to prevent direct access.
|
||||||
*/
|
*/
|
||||||
|
@Keep
|
||||||
abstract class BaseFlags {
|
abstract class BaseFlags {
|
||||||
|
|
||||||
private static final String TAG = "FeatureFlags";
|
private static final Object sLock = new Object();
|
||||||
|
@GuardedBy("sLock")
|
||||||
|
private static final List<TogglableFlag> sFlags = new ArrayList<>();
|
||||||
|
|
||||||
|
static final String FLAGS_PREF_NAME = "featureFlags";
|
||||||
|
|
||||||
BaseFlags() {
|
BaseFlags() {
|
||||||
|
throw new UnsupportedOperationException("Don't instantiate BaseFlags");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean showFlagTogglerUi() {
|
||||||
|
return Utilities.IS_DEBUG_DEVICE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final boolean IS_DOGFOOD_BUILD = false;
|
public static final boolean IS_DOGFOOD_BUILD = false;
|
||||||
|
@ -36,10 +61,12 @@ abstract class BaseFlags {
|
||||||
// When enabled the promise icon is visible in all apps while installation an app.
|
// When enabled the promise icon is visible in all apps while installation an app.
|
||||||
public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
|
public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
|
||||||
|
|
||||||
/** Feature flag to enable moving the QSB on the 0th screen of the workspace. */
|
public static final TogglableFlag QSB_ON_FIRST_SCREEN = new TogglableFlag("QSB_ON_FIRST_SCREEN",
|
||||||
public boolean isQsbOnFirstScreenEnabled() {
|
true,
|
||||||
return true;
|
"Enable moving the QSB on the 0th screen of the workspace");
|
||||||
}
|
|
||||||
|
public static final TogglableFlag EXAMPLE_FLAG = new TogglableFlag("EXAMPLE_FLAG", true,
|
||||||
|
"An example flag that doesn't do anything. Useful for testing");
|
||||||
|
|
||||||
//Feature flag to enable pulling down navigation shade from workspace.
|
//Feature flag to enable pulling down navigation shade from workspace.
|
||||||
public static final boolean PULL_DOWN_STATUS_BAR = true;
|
public static final boolean PULL_DOWN_STATUS_BAR = true;
|
||||||
|
@ -56,4 +83,110 @@ abstract class BaseFlags {
|
||||||
// When true, overview shows screenshots in the orientation they were taken rather than
|
// When true, overview shows screenshots in the orientation they were taken rather than
|
||||||
// trying to make them fit the orientation the device is in.
|
// trying to make them fit the orientation the device is in.
|
||||||
public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true;
|
public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true;
|
||||||
|
|
||||||
|
public static void initialize(Context context) {
|
||||||
|
// Avoid the disk read for builds without the flags UI.
|
||||||
|
if (showFlagTogglerUi()) {
|
||||||
|
SharedPreferences sharedPreferences =
|
||||||
|
context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
|
||||||
|
synchronized (sLock) {
|
||||||
|
for (TogglableFlag flag : sFlags) {
|
||||||
|
flag.currentValue = sharedPreferences.getBoolean(flag.key, flag.defaultValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
synchronized (sLock) {
|
||||||
|
for (TogglableFlag flag : sFlags) {
|
||||||
|
flag.currentValue = flag.defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<TogglableFlag> getTogglableFlags() {
|
||||||
|
// By Java Language Spec 12.4.2
|
||||||
|
// https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2, the
|
||||||
|
// TogglableFlag instances on BaseFlags will be created before those on the FeatureFlags
|
||||||
|
// subclass. This code handles flags that are redeclared in FeatureFlags, ensuring the
|
||||||
|
// FeatureFlags one takes priority.
|
||||||
|
SortedMap<String, TogglableFlag> flagsByKey = new TreeMap<>();
|
||||||
|
synchronized (sLock) {
|
||||||
|
for (TogglableFlag flag : sFlags) {
|
||||||
|
flagsByKey.put(flag.key, flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>(flagsByKey.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class TogglableFlag {
|
||||||
|
private final String key;
|
||||||
|
private final boolean defaultValue;
|
||||||
|
private final String description;
|
||||||
|
private boolean currentValue;
|
||||||
|
|
||||||
|
TogglableFlag(
|
||||||
|
String key,
|
||||||
|
boolean defaultValue,
|
||||||
|
String description) {
|
||||||
|
this.key = checkNotNull(key);
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
this.description = checkNotNull(description);
|
||||||
|
synchronized (sLock) {
|
||||||
|
sFlags.add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean getDefaultValue() {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the value of the flag at process start, including any overrides present. */
|
||||||
|
public boolean get() {
|
||||||
|
return currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TogglableFlag{"
|
||||||
|
+ "key=" + key + ", "
|
||||||
|
+ "defaultValue=" + defaultValue + ", "
|
||||||
|
+ "description=" + description
|
||||||
|
+ "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o instanceof TogglableFlag) {
|
||||||
|
TogglableFlag that = (TogglableFlag) o;
|
||||||
|
return (this.key.equals(that.getKey()))
|
||||||
|
&& (this.defaultValue == that.getDefaultValue())
|
||||||
|
&& (this.description.equals(that.getDescription()));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int h$ = 1;
|
||||||
|
h$ *= 1000003;
|
||||||
|
h$ ^= key.hashCode();
|
||||||
|
h$ *= 1000003;
|
||||||
|
h$ ^= defaultValue ? 1231 : 1237;
|
||||||
|
h$ *= 1000003;
|
||||||
|
h$ ^= description.hashCode();
|
||||||
|
return h$;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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.launcher3.config;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.preference.PreferenceDataStore;
|
||||||
|
import android.preference.PreferenceFragment;
|
||||||
|
import android.preference.SwitchPreference;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.android.launcher3.R;
|
||||||
|
import com.android.launcher3.config.BaseFlags.TogglableFlag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
|
||||||
|
*/
|
||||||
|
public final class FlagTogglerPreferenceFragment extends PreferenceFragment {
|
||||||
|
private static final String TAG = "FlagTogglerPrefFrag";
|
||||||
|
|
||||||
|
private SharedPreferences mSharedPreferences;
|
||||||
|
private MenuItem saveButton;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
addPreferencesFromResource(R.xml.flag_preferences);
|
||||||
|
mSharedPreferences = getContext().getSharedPreferences(
|
||||||
|
FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
// For flag overrides we only want to store when the engineer chose to override the
|
||||||
|
// flag with a different value than the default. That way, when we flip flags in
|
||||||
|
// future, engineers will pick up the new value immediately. To accomplish this, we use a
|
||||||
|
// custom preference data store.
|
||||||
|
getPreferenceManager().setPreferenceDataStore(new PreferenceDataStore() {
|
||||||
|
@Override
|
||||||
|
public void putBoolean(String key, boolean value) {
|
||||||
|
for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
|
||||||
|
if (flag.getKey().equals(key)) {
|
||||||
|
if (value == flag.getDefaultValue()) {
|
||||||
|
mSharedPreferences.edit().remove(key).apply();
|
||||||
|
} else {
|
||||||
|
mSharedPreferences.edit().putBoolean(key, value).apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
|
||||||
|
SwitchPreference switchPreference = new SwitchPreference(getContext());
|
||||||
|
switchPreference.setKey(flag.getKey());
|
||||||
|
switchPreference.setDefaultValue(flag.getDefaultValue());
|
||||||
|
switchPreference.setChecked(getFlagStateFromSharedPrefs(flag));
|
||||||
|
switchPreference.setTitle(flag.getKey());
|
||||||
|
switchPreference.setSummaryOn(flag.getDefaultValue() ? "" : "overridden");
|
||||||
|
switchPreference.setSummaryOff(flag.getDefaultValue() ? "overridden" : "");
|
||||||
|
getPreferenceScreen().addPreference(switchPreference);
|
||||||
|
}
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
saveButton = menu.add("Apply");
|
||||||
|
saveButton.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item == saveButton) {
|
||||||
|
mSharedPreferences.edit().commit();
|
||||||
|
Log.e(TAG,
|
||||||
|
"Killing launcher process " + Process.myPid() + " to apply new flag values");
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
boolean anyChanged = false;
|
||||||
|
for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
|
||||||
|
anyChanged = anyChanged ||
|
||||||
|
getFlagStateFromSharedPrefs(flag) != flag.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyChanged) {
|
||||||
|
Toast.makeText(
|
||||||
|
getContext(),
|
||||||
|
"Flag won't be applied until you restart launcher",
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean getFlagStateFromSharedPrefs(TogglableFlag flag) {
|
||||||
|
return mSharedPreferences.getBoolean(flag.getKey(), flag.getDefaultValue());
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import android.database.Cursor;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.launcher3.InvariantDeviceProfile;
|
import com.android.launcher3.InvariantDeviceProfile;
|
||||||
import com.android.launcher3.ItemInfo;
|
import com.android.launcher3.ItemInfo;
|
||||||
import com.android.launcher3.LauncherAppState;
|
import com.android.launcher3.LauncherAppState;
|
||||||
|
@ -109,6 +110,7 @@ public class GridSizeMigrationTask {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applied all the pending DB operations
|
* Applied all the pending DB operations
|
||||||
|
*
|
||||||
* @return true if any DB operation was commited.
|
* @return true if any DB operation was commited.
|
||||||
*/
|
*/
|
||||||
private boolean applyOperations() throws Exception {
|
private boolean applyOperations() throws Exception {
|
||||||
|
@ -135,6 +137,7 @@ public class GridSizeMigrationTask {
|
||||||
* entries is more than what can fit in the new hotseat, we drop the entries with least weight.
|
* entries is more than what can fit in the new hotseat, we drop the entries with least weight.
|
||||||
* For weight calculation {@see #WT_SHORTCUT}, {@see #WT_APPLICATION}
|
* For weight calculation {@see #WT_SHORTCUT}, {@see #WT_APPLICATION}
|
||||||
* & {@see #WT_FOLDER_FACTOR}.
|
* & {@see #WT_FOLDER_FACTOR}.
|
||||||
|
*
|
||||||
* @return true if any DB change was made
|
* @return true if any DB change was made
|
||||||
*/
|
*/
|
||||||
protected boolean migrateHotseat() throws Exception {
|
protected boolean migrateHotseat() throws Exception {
|
||||||
|
@ -235,7 +238,8 @@ public class GridSizeMigrationTask {
|
||||||
int screenId = allScreens.get(i);
|
int screenId = allScreens.get(i);
|
||||||
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
|
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
|
||||||
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
|
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
|
||||||
mUpdateOperations.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
|
mUpdateOperations.add(ContentProviderOperation.newInsert(uri).withValues(
|
||||||
|
v).build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return applyOperations();
|
return applyOperations();
|
||||||
|
@ -254,8 +258,7 @@ public class GridSizeMigrationTask {
|
||||||
protected void migrateScreen(int screenId) {
|
protected void migrateScreen(int screenId) {
|
||||||
// If we are migrating the first screen, do not touch the first row.
|
// If we are migrating the first screen, do not touch the first row.
|
||||||
int startY =
|
int startY =
|
||||||
(FeatureFlags.getInstance(mContext).isQsbOnFirstScreenEnabled()
|
(FeatureFlags.QSB_ON_FIRST_SCREEN.get() && screenId == Workspace.FIRST_SCREEN_ID)
|
||||||
&& screenId == Workspace.FIRST_SCREEN_ID)
|
|
||||||
? 1 : 0;
|
? 1 : 0;
|
||||||
|
|
||||||
ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
|
ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
|
||||||
|
@ -280,9 +283,11 @@ public class GridSizeMigrationTask {
|
||||||
for (int y = mSrcY - 1; y >= startY; y--) {
|
for (int y = mSrcY - 1; y >= startY; y--) {
|
||||||
// Use a deep copy when trying out a particular combination as it can change
|
// Use a deep copy when trying out a particular combination as it can change
|
||||||
// the underlying object.
|
// the underlying object.
|
||||||
ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, startY, deepCopy(items), outLoss);
|
ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, startY, deepCopy(items),
|
||||||
|
outLoss);
|
||||||
|
|
||||||
if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1] < moveWt))) {
|
if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1]
|
||||||
|
< moveWt))) {
|
||||||
removeWt = outLoss[0];
|
removeWt = outLoss[0];
|
||||||
moveWt = outLoss[1];
|
moveWt = outLoss[1];
|
||||||
removedCol = mShouldRemoveX ? x : removedCol;
|
removedCol = mShouldRemoveX ? x : removedCol;
|
||||||
|
@ -363,6 +368,7 @@ public class GridSizeMigrationTask {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries the remove the provided row and column.
|
* Tries the remove the provided row and column.
|
||||||
|
*
|
||||||
* @param items all the items on the screen under operation
|
* @param items all the items on the screen under operation
|
||||||
* @param outLoss array of size 2. The first entry is filled with weight loss, and the second
|
* @param outLoss array of size 2. The first entry is filled with weight loss, and the second
|
||||||
* with the overall item movement.
|
* with the overall item movement.
|
||||||
|
@ -438,6 +444,7 @@ public class GridSizeMigrationTask {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively finds a placement for the provided items.
|
* Recursively finds a placement for the provided items.
|
||||||
|
*
|
||||||
* @param index the position in {@link #itemsToPlace} to start looking at.
|
* @param index the position in {@link #itemsToPlace} to start looking at.
|
||||||
* @param weightLoss total weight loss upto this point
|
* @param weightLoss total weight loss upto this point
|
||||||
* @param moveCost total move cost upto this point
|
* @param moveCost total move cost upto this point
|
||||||
|
@ -550,7 +557,8 @@ public class GridSizeMigrationTask {
|
||||||
for (int x = 0; x < mTrgX; x++) {
|
for (int x = 0; x < mTrgX; x++) {
|
||||||
if (!occupied.cells[x][y]) {
|
if (!occupied.cells[x][y]) {
|
||||||
int dist = ignoreMove ? 0 :
|
int dist = ignoreMove ? 0 :
|
||||||
((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY - y));
|
((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY
|
||||||
|
- y));
|
||||||
if (dist < newDistance) {
|
if (dist < newDistance) {
|
||||||
newX = x;
|
newX = x;
|
||||||
newY = y;
|
newY = y;
|
||||||
|
@ -815,7 +823,8 @@ public class GridSizeMigrationTask {
|
||||||
|
|
||||||
public float weight;
|
public float weight;
|
||||||
|
|
||||||
public DbEntry() { }
|
public DbEntry() {
|
||||||
|
}
|
||||||
|
|
||||||
public DbEntry copy() {
|
public DbEntry copy() {
|
||||||
DbEntry entry = new DbEntry();
|
DbEntry entry = new DbEntry();
|
||||||
|
@ -887,6 +896,7 @@ public class GridSizeMigrationTask {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrates the workspace and hotseat in case their sizes changed.
|
* Migrates the workspace and hotseat in case their sizes changed.
|
||||||
|
*
|
||||||
* @return false if the migration failed.
|
* @return false if the migration failed.
|
||||||
*/
|
*/
|
||||||
public static boolean migrateGridIfNeeded(Context context) {
|
public static boolean migrateGridIfNeeded(Context context) {
|
||||||
|
@ -896,7 +906,8 @@ public class GridSizeMigrationTask {
|
||||||
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
|
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
|
||||||
|
|
||||||
if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
|
if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
|
||||||
idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)) {
|
idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
|
||||||
|
idp.numHotseatIcons)) {
|
||||||
// Skip if workspace and hotseat sizes have not changed.
|
// Skip if workspace and hotseat sizes have not changed.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -907,7 +918,8 @@ public class GridSizeMigrationTask {
|
||||||
|
|
||||||
HashSet<String> validPackages = getValidPackages(context);
|
HashSet<String> validPackages = getValidPackages(context);
|
||||||
// Hotseat
|
// Hotseat
|
||||||
int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
|
int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
|
||||||
|
idp.numHotseatIcons);
|
||||||
if (srcHotseatCount != idp.numHotseatIcons) {
|
if (srcHotseatCount != idp.numHotseatIcons) {
|
||||||
// Migrate hotseat.
|
// Migrate hotseat.
|
||||||
|
|
||||||
|
@ -920,7 +932,8 @@ public class GridSizeMigrationTask {
|
||||||
Point sourceSize = parsePoint(prefs.getString(
|
Point sourceSize = parsePoint(prefs.getString(
|
||||||
KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
|
KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
|
||||||
|
|
||||||
if (new MultiStepMigrationTask(validPackages, context).migrate(sourceSize, targetSize)) {
|
if (new MultiStepMigrationTask(validPackages, context).migrate(sourceSize,
|
||||||
|
targetSize)) {
|
||||||
dbChanged = true;
|
dbChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -970,9 +983,11 @@ public class GridSizeMigrationTask {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes any broken item from the hotseat.
|
* Removes any broken item from the hotseat.
|
||||||
|
*
|
||||||
* @return a map with occupied hotseat position set to non-null value.
|
* @return a map with occupied hotseat position set to non-null value.
|
||||||
*/
|
*/
|
||||||
public static IntSparseArrayMap<Object> removeBrokenHotseatItems(Context context) throws Exception {
|
public static IntSparseArrayMap<Object> removeBrokenHotseatItems(Context context)
|
||||||
|
throws Exception {
|
||||||
GridSizeMigrationTask task = new GridSizeMigrationTask(
|
GridSizeMigrationTask task = new GridSizeMigrationTask(
|
||||||
context, LauncherAppState.getIDP(context), getValidPackages(context),
|
context, LauncherAppState.getIDP(context), getValidPackages(context),
|
||||||
Integer.MAX_VALUE, Integer.MAX_VALUE);
|
Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
|
|
@ -441,7 +441,7 @@ public class LoaderCursor extends CursorWrapper {
|
||||||
// Mark the first row as occupied (if the feature is enabled)
|
// Mark the first row as occupied (if the feature is enabled)
|
||||||
// in order to account for the QSB.
|
// in order to account for the QSB.
|
||||||
screen.markCells(0, 0, countX + 1, 1,
|
screen.markCells(0, 0, countX + 1, 1,
|
||||||
FeatureFlags.getInstance(mContext).isQsbOnFirstScreenEnabled());
|
FeatureFlags.QSB_ON_FIRST_SCREEN.get());
|
||||||
}
|
}
|
||||||
occupied.put(item.screenId, screen);
|
occupied.put(item.screenId, screen);
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ public class ImportDataTask {
|
||||||
.getSerialNumberForUser(Process.myUserHandle()));
|
.getSerialNumberForUser(Process.myUserHandle()));
|
||||||
|
|
||||||
boolean createEmptyRowOnFirstScreen;
|
boolean createEmptyRowOnFirstScreen;
|
||||||
if (FeatureFlags.getInstance(mContext).isQsbOnFirstScreenEnabled()) {
|
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
|
||||||
try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null,
|
try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null,
|
||||||
// get items on the first row of the first screen
|
// get items on the first row of the first screen
|
||||||
"profileId = ? AND container = -100 AND screen = ? AND cellY = 0",
|
"profileId = ? AND container = -100 AND screen = ? AND cellY = 0",
|
||||||
|
|
|
@ -213,7 +213,7 @@ public class QsbContainerView extends FrameLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isQsbEnabled() {
|
public boolean isQsbEnabled() {
|
||||||
return FeatureFlags.getInstance(getContext()).isQsbOnFirstScreenEnabled();
|
return FeatureFlags.QSB_ON_FIRST_SCREEN.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Bundle createBindOptions() {
|
protected Bundle createBindOptions() {
|
||||||
|
|
|
@ -22,11 +22,7 @@ import android.content.Context;
|
||||||
* Defines a set of flags used to control various launcher behaviors
|
* Defines a set of flags used to control various launcher behaviors
|
||||||
*/
|
*/
|
||||||
public final class FeatureFlags extends BaseFlags {
|
public final class FeatureFlags extends BaseFlags {
|
||||||
private static FeatureFlags instance = new FeatureFlags();
|
private FeatureFlags() {
|
||||||
|
// Prevent instantiation
|
||||||
public static FeatureFlags getInstance(Context context) {
|
|
||||||
return instance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private FeatureFlags() {}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue