Merging from ub-launcher3-master @ build 6782663

Test: manual, presubmit on the source branch
x20/teams/android-launcher/merge/ub-launcher3-master_master_6782663.html

Change-Id: I649c4d931b84c1e8e22a58fa92b92c76f5222451
This commit is contained in:
Sunny Goyal 2020-08-21 14:35:09 -07:00
commit 9136897b65
11 changed files with 130 additions and 161 deletions

View File

@ -46,6 +46,12 @@
tools:node="replace" >
</activity>
<service
android:name="com.android.launcher3.notification.NotificationListener"
android:label="@string/notification_dots_service_title"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:enabled="false"
tools:node="replace" />
</application>
</manifest>

View File

@ -41,6 +41,7 @@ public class WidgetsModel {
// True is the widget support is disabled.
public static final boolean GO_DISABLE_WIDGETS = true;
public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true;
private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();

View File

@ -24,6 +24,7 @@ import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.UiThreadHelper;
@ -130,6 +131,10 @@ public class ExtendedEditText extends EditText {
public void reset() {
if (!TextUtils.isEmpty(getText())) {
setText("");
} else {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
return;
}
}
if (isFocused()) {
View nextFocus = focusSearch(View.FOCUS_DOWN);

View File

@ -44,6 +44,7 @@ import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.os.BuildCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -110,7 +111,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
private final MultiValueAlpha mMultiValueAlpha;
Rect mInsets = new Rect();
private Rect mInsets = new Rect();
public AllAppsContainerView(Context context) {
this(context, null);
@ -206,6 +207,17 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
mAH[AdapterHolder.WORK].applyPadding();
}
private void hideInput() {
if (!BuildCompat.isAtLeastR() || !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
WindowInsets insets = getRootWindowInsets();
if (insets == null) return;
if (insets.isVisible(WindowInsets.Type.ime())) {
getWindowInsetsController().hide(WindowInsets.Type.ime());
}
}
/**
* Returns whether the view itself will handle the touch event or not.
*/
@ -221,9 +233,14 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
}
if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
hideInput();
return false;
}
return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
boolean shouldScroll = rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
if (shouldScroll) {
hideInput();
}
return shouldScroll;
}
@Override

View File

@ -174,9 +174,6 @@ public final class FeatureFlags {
"SEPARATE_RECENTS_ACTIVITY", false,
"Uses a separate recents activity instead of using the integrated recents+Launcher UI");
public static final BooleanFlag USER_EVENT_DISPATCHER = new DeviceFlag(
"USER_EVENT_DISPATCHER", true, "User event dispatcher collects logs.");
public static final BooleanFlag ENABLE_MINIMAL_DEVICE = new DeviceFlag(
"ENABLE_MINIMAL_DEVICE", false,
"Allow user to toggle minimal device mode in launcher.");

View File

@ -297,8 +297,7 @@ public class BgDataModel {
.filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
.map(ShortcutKey::fromItemInfo),
// Pending shortcuts
ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts()
.stream().filter(si -> si.user.equals(user)))
ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
.collect(groupingBy(ShortcutKey::getPackageName,
mapping(ShortcutKey::getId, Collectors.toSet())));

View File

@ -21,7 +21,6 @@ import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@ -30,11 +29,9 @@ import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
@ -47,27 +44,19 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PersistedItemArray;
import com.android.launcher3.util.Preconditions;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Class to maintain a queue of pending items to be added to the workspace.
@ -79,7 +68,6 @@ public class ItemInstallQueue {
public static final int FLAG_DRAG_AND_DROP = 4;
private static final String TAG = "InstallShortcutReceiver";
private static final boolean DBG = false;
// The set of shortcuts that are pending install
private static final String APPS_PENDING_INSTALL = "apps_to_install";
@ -90,24 +78,34 @@ public class ItemInstallQueue {
public static MainThreadInitializedObject<ItemInstallQueue> INSTANCE =
new MainThreadInitializedObject<>(ItemInstallQueue::new);
private final PersistedItemArray<PendingInstallShortcutInfo> mStorage =
new PersistedItemArray<>(APPS_PENDING_INSTALL);
private final Context mContext;
// Determines whether to defer installing shortcuts immediately until
// processAllPendingInstalls() is called.
private int mInstallQueueDisabledFlags = 0;
// Only accessed on worker thread
private List<PendingInstallShortcutInfo> mItems;
private ItemInstallQueue(Context context) {
mContext = context;
}
@WorkerThread
private void ensureQueueLoaded() {
Preconditions.assertWorkerThread();
if (mItems == null) {
mItems = mStorage.read(mContext, this::decode);
}
}
@WorkerThread
private void addToQueue(PendingInstallShortcutInfo info) {
String encoded = info.encodeToString(mContext);
SharedPreferences prefs = Utilities.getPrefs(mContext);
Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1);
strings.add(encoded);
prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
ensureQueueLoaded();
mItems.add(info);
mStorage.write(mContext, mItems);
}
@WorkerThread
@ -117,28 +115,21 @@ public class ItemInstallQueue {
// Launcher not loaded
return;
}
ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
SharedPreferences prefs = Utilities.getPrefs(mContext);
Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
if (strings == null) {
ensureQueueLoaded();
if (mItems.isEmpty()) {
return;
}
for (String encoded : strings) {
PendingInstallShortcutInfo info = decode(encoded, mContext);
if (info == null) {
continue;
}
List<Pair<ItemInfo, Object>> installQueue = mItems.stream()
.map(info -> info.getItemInfo(mContext))
.collect(Collectors.toList());
// Generate a shortcut info to add into the model
installQueue.add(info.getItemInfo(mContext));
}
prefs.edit().remove(APPS_PENDING_INSTALL).apply();
// Add the items and clear queue
if (!installQueue.isEmpty()) {
launcher.getModel().addAndBindAddedWorkspaceItems(installQueue);
}
mItems.clear();
mStorage.getFile(mContext).delete();
}
/**
@ -149,33 +140,11 @@ public class ItemInstallQueue {
if (packageNames.isEmpty()) {
return;
}
Preconditions.assertWorkerThread();
SharedPreferences sp = Utilities.getPrefs(mContext);
Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
if (DBG) {
Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
+ ", removing packages: " + packageNames);
ensureQueueLoaded();
if (mItems.removeIf(item ->
item.user.equals(user) && packageNames.contains(getIntentPackage(item.intent)))) {
mStorage.write(mContext, mItems);
}
if (strings == null || ((Collection) strings).isEmpty()) {
return;
}
Set<String> newStrings = new HashSet<>(strings);
Iterator<String> newStringsIter = newStrings.iterator();
while (newStringsIter.hasNext()) {
String encoded = newStringsIter.next();
try {
Decoder decoder = new Decoder(encoded, mContext);
if (packageNames.contains(getIntentPackage(decoder.intent))
&& user.equals(decoder.user)) {
newStringsIter.remove();
}
} catch (JSONException | URISyntaxException e) {
Log.d(TAG, "Exception reading shortcut to add: " + e);
newStringsIter.remove();
}
}
sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
}
/**
@ -200,28 +169,14 @@ public class ItemInstallQueue {
}
/**
* Returns all pending shorts in the queue
* Returns a stream of all pending shortcuts in the queue
*/
@WorkerThread
public HashSet<ShortcutKey> getPendingShortcuts() {
HashSet<ShortcutKey> result = new HashSet<>();
Set<String> strings = Utilities.getPrefs(mContext).getStringSet(APPS_PENDING_INSTALL, null);
if (strings == null || ((Collection) strings).isEmpty()) {
return result;
}
for (String encoded : strings) {
try {
Decoder decoder = new Decoder(encoded, mContext);
if (decoder.optInt(Favorites.ITEM_TYPE, -1) == ITEM_TYPE_DEEP_SHORTCUT) {
result.add(ShortcutKey.fromIntent(decoder.intent, decoder.user));
}
} catch (JSONException | URISyntaxException e) {
Log.d(TAG, "Exception reading shortcut to add: " + e);
}
}
return result;
public Stream<ShortcutKey> getPendingShortcuts(UserHandle user) {
ensureQueueLoaded();
return mItems.stream()
.filter(item -> item.itemType == ITEM_TYPE_DEEP_SHORTCUT && user.equals(item.user))
.map(item -> ShortcutKey.fromIntent(item.intent, user));
}
private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
@ -293,19 +248,9 @@ public class ItemInstallQueue {
providerInfo = info;
}
public String encodeToString(Context context) {
try {
return new JSONStringer()
.object()
.key(Favorites.ITEM_TYPE).value(itemType)
.key(Favorites.INTENT).value(intent.toUri(0))
.key(PROFILE_ID).value(
UserCache.INSTANCE.get(context).getSerialNumberForUser(user))
.endObject().toString();
} catch (JSONException e) {
Log.d(TAG, "Exception when adding shortcut: " + e);
return null;
}
@Override
public Intent getIntent() {
return intent;
}
public Pair<ItemInfo, Object> getItemInfo(Context context) {
@ -365,55 +310,33 @@ public class ItemInstallQueue {
? intent.getPackage() : intent.getComponent().getPackageName();
}
private static PendingInstallShortcutInfo decode(String encoded, Context context) {
try {
Decoder decoder = new Decoder(encoded, context);
switch (decoder.optInt(Favorites.ITEM_TYPE, -1)) {
case Favorites.ITEM_TYPE_APPLICATION:
return new PendingInstallShortcutInfo(
decoder.intent.getPackage(), decoder.user);
case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
List<ShortcutInfo> si = ShortcutKey.fromIntent(decoder.intent, decoder.user)
.buildRequest(context)
.query(ShortcutRequest.ALL);
if (si.isEmpty()) {
return null;
} else {
return new PendingInstallShortcutInfo(si.get(0));
}
private PendingInstallShortcutInfo decode(int itemType, UserHandle user, Intent intent) {
switch (itemType) {
case Favorites.ITEM_TYPE_APPLICATION:
return new PendingInstallShortcutInfo(intent.getPackage(), user);
case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
List<ShortcutInfo> si = ShortcutKey.fromIntent(intent, user)
.buildRequest(mContext)
.query(ShortcutRequest.ALL);
if (si.isEmpty()) {
return null;
} else {
return new PendingInstallShortcutInfo(si.get(0));
}
case Favorites.ITEM_TYPE_APPWIDGET: {
int widgetId = decoder.intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
AppWidgetProviderInfo info =
AppWidgetManager.getInstance(context).getAppWidgetInfo(widgetId);
if (info == null || !info.provider.equals(decoder.intent.getComponent())
|| !info.getProfile().equals(decoder.user)) {
return null;
}
return new PendingInstallShortcutInfo(info, widgetId);
}
default:
Log.e(TAG, "Unknown item type");
}
} catch (JSONException | URISyntaxException e) {
Log.d(TAG, "Exception reading shortcut to add: " + e);
case Favorites.ITEM_TYPE_APPWIDGET: {
int widgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
AppWidgetProviderInfo info =
AppWidgetManager.getInstance(mContext).getAppWidgetInfo(widgetId);
if (info == null || !info.provider.equals(intent.getComponent())
|| !info.getProfile().equals(user)) {
return null;
}
return new PendingInstallShortcutInfo(info, widgetId);
}
default:
Log.e(TAG, "Unknown item type");
}
return null;
}
private static class Decoder extends JSONObject {
public final Intent intent;
public final UserHandle user;
private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
super(encoded);
intent = Intent.parseUri(getString(Favorites.INTENT), 0);
user = has(PROFILE_ID)
? UserCache.INSTANCE.get(context).getUserForSerialNumber(getLong(PROFILE_ID))
: Process.myUserHandle();
if (user == null || intent == null) {
throw new JSONException("Invalid data");
}
}
}
}

View File

@ -44,6 +44,7 @@ import com.android.launcher3.LauncherFiles;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.SecureSettingsObserver;
@ -171,6 +172,10 @@ public class SettingsActivity extends FragmentActivity
protected boolean initPreference(Preference preference) {
switch (preference.getKey()) {
case NOTIFICATION_DOTS_PREFERENCE_KEY:
if (WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS) {
return false;
}
// Listen to system notification dot settings while this UI is active.
mNotificationDotsObserver = newNotificationSettingsObserver(
getActivity(), (NotificationDotsPreference) preference);

View File

@ -50,7 +50,7 @@ public class DisplayController implements DisplayListener {
private final ArrayList<DisplayListChangeListener> mListListeners = new ArrayList<>();
private DisplayController(Context context) {
mDefaultDisplay = new DisplayHolder(context, DEFAULT_DISPLAY);
mDefaultDisplay = DisplayHolder.create(context, DEFAULT_DISPLAY);
DisplayManager dm = context.getSystemService(DisplayManager.class);
dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
@ -58,7 +58,11 @@ public class DisplayController implements DisplayListener {
@Override
public final void onDisplayAdded(int displayId) {
DisplayHolder holder = new DisplayHolder(mDefaultDisplay.mDisplayContext, displayId);
DisplayHolder holder = DisplayHolder.create(mDefaultDisplay.mDisplayContext, displayId);
if (holder == null) {
// Display is already removed by the time we dot this.
return;
}
synchronized (mOtherDisplays) {
mOtherDisplays.put(displayId, holder);
}
@ -153,12 +157,8 @@ public class DisplayController implements DisplayListener {
private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
private DisplayController.Info mInfo;
public DisplayHolder(Context context, int id) {
DisplayManager dm = context.getSystemService(DisplayManager.class);
// Use application context to create display context so that it can have its own
// Resources.
mDisplayContext = context.getApplicationContext()
.createDisplayContext(dm.getDisplay(id));
private DisplayHolder(Context displayContext) {
mDisplayContext = displayContext;
// Note that the Display object must be obtained from DisplayManager which is
// associated to the display context, so the Display is isolated from Activity and
// Application to provide the actual state of device that excludes the additional
@ -207,6 +207,17 @@ public class DisplayController implements DisplayListener {
}
}
private static DisplayHolder create(Context context, int id) {
DisplayManager dm = context.getSystemService(DisplayManager.class);
Display display = dm.getDisplay(id);
if (display == null) {
return null;
}
// Use application context to create display context so that it can have its own
// Resources.
Context displayContext = context.getApplicationContext().createDisplayContext(display);
return new DisplayHolder(displayContext);
}
}
public static class Info {

View File

@ -68,8 +68,7 @@ public class PersistedItemArray<T extends ItemInfo> {
*/
@WorkerThread
public void write(Context context, List<T> items) {
AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
AtomicFile file = getFile(context);
FileOutputStream fos;
try {
fos = file.startWrite();
@ -124,9 +123,7 @@ public class PersistedItemArray<T extends ItemInfo> {
@WorkerThread
public List<T> read(Context context, ItemFactory<T> factory, LongFunction<UserHandle> userFn) {
List<T> result = new ArrayList<>();
AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
try (FileInputStream fis = file.openRead()) {
try (FileInputStream fis = getFile(context).openRead()) {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new InputStreamReader(fis, StandardCharsets.UTF_8));
@ -166,6 +163,13 @@ public class PersistedItemArray<T extends ItemInfo> {
return result;
}
/**
* Returns the underlying file used for persisting data
*/
public AtomicFile getFile(Context context) {
return new AtomicFile(context.getFileStreamPath(mFileName));
}
/**
* Interface to create an ItemInfo during parsing
*/

View File

@ -51,6 +51,7 @@ public class WidgetsModel {
// True is the widget support is disabled.
public static final boolean GO_DISABLE_WIDGETS = false;
public static final boolean GO_DISABLE_NOTIFICATION_DOTS = false;
private static final String TAG = "WidgetsModel";
private static final boolean DEBUG = false;