Merging from ub-launcher3-master @ build 6777814

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

Change-Id: I0bb722c8f29e91cb382337f8b11bcf43d711949b
This commit is contained in:
Tony Wickham
2020-08-19 19:59:27 -07:00
35 changed files with 769 additions and 320 deletions
@@ -43,9 +43,13 @@ import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
@@ -74,6 +78,9 @@ public final class WellbeingModel {
private static final int MSG_PACKAGE_REMOVED = 2;
private static final int MSG_FULL_REFRESH = 3;
private static final int UNKNOWN_MINIMAL_DEVICE_STATE = 0;
private static final int IN_MINIMAL_DEVICE = 2;
// Welbeing contract
private static final String PATH_ACTIONS = "actions";
private static final String PATH_MINIMAL_DEVICE = "minimal_device";
@@ -84,6 +91,8 @@ public final class WellbeingModel {
private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
private static final String EXTRA_PACKAGES = "packages";
private static final String EXTRA_SUCCESS = "success";
private static final String EXTRA_MINIMAL_DEVICE_STATE = "minimal_device_state";
private static final String DB_NAME_MINIMAL_DEVICE = "minimal.db";
public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
new MainThreadInitializedObject<>(WellbeingModel::new);
@@ -121,11 +130,12 @@ public final class WellbeingModel {
updateWellbeingData();
} else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
// Wellbeing reports that minimal device state or config is changed.
updateLauncherModel();
updateLauncherModel(context);
}
}
};
FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, this::updateLauncherModel);
FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, () ->
updateLauncherModel(context));
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
context.registerReceiver(
@@ -170,7 +180,6 @@ public final class WellbeingModel {
Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
if (mIsInTest) throw new RuntimeException(e);
}
updateWellbeingData();
}
@@ -208,10 +217,34 @@ public final class WellbeingModel {
mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH);
}
private void updateLauncherModel() {
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) return;
private void updateLauncherModel(@NonNull final Context context) {
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
reloadLauncherInNormalMode(context);
return;
}
runWithMinimalDeviceConfigs((bundle) -> {
if (bundle.getInt(EXTRA_MINIMAL_DEVICE_STATE, UNKNOWN_MINIMAL_DEVICE_STATE)
== IN_MINIMAL_DEVICE) {
reloadLauncherInMinimalMode(context);
} else {
reloadLauncherInNormalMode(context);
}
});
}
// TODO: init Launcher in minimal device / normal mode
private void reloadLauncherInNormalMode(@NonNull final Context context) {
LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
InvariantDeviceProfile.INSTANCE.get(context).dbFile);
}
private void reloadLauncherInMinimalMode(@NonNull final Context context) {
final Bundle extras = new Bundle();
extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY,
mWellbeingProviderPkg + ".api");
LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
DB_NAME_MINIMAL_DEVICE, extras);
}
private Uri.Builder apiBuilder() {
@@ -225,6 +258,9 @@ public final class WellbeingModel {
*/
@WorkerThread
private void runWithMinimalDeviceConfigs(Consumer<Bundle> consumer) {
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
return;
}
if (DEBUG || mIsInTest) {
Log.d(TAG, "runWithMinimalDeviceConfigs() called");
}
@@ -18,6 +18,7 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.graphics.OverviewScrim.SCRIM_MULTIPLIER;
import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
@@ -70,6 +71,7 @@ public abstract class BaseRecentsViewStateController<T extends RecentsView>
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
SCRIM_MULTIPLIER.set(scrim, 1f);
getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
}
@@ -108,6 +110,8 @@ public abstract class BaseRecentsViewStateController<T extends RecentsView>
OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
setter.setFloat(scrim, SCRIM_MULTIPLIER, 1f,
config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
setter.setFloat(
mRecentsView, getTaskModalnessProperty(),
@@ -66,8 +66,8 @@ public class NavBarToHomeTouchController implements TouchController,
SingleAxisSwipeDetector.Listener {
private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
// How much of the overview scrim we can remove during the transition.
private static final float OVERVIEW_TO_HOME_SCRIM_PROGRESS = 0.5f;
// The min amount of overview scrim we keep during the transition.
private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;
private final Launcher mLauncher;
private final SingleAxisSwipeDetector mSwipeDetector;
@@ -163,11 +163,11 @@ public class NavBarToHomeTouchController implements TouchController,
RecentsView recentsView = mLauncher.getOverviewPanel();
AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
builder);
float endScrimAlpha = Utilities.mapRange(OVERVIEW_TO_HOME_SCRIM_PROGRESS,
mStartState.getOverviewScrimAlpha(mLauncher),
mEndState.getOverviewScrimAlpha(mLauncher));
builder.setFloat(mLauncher.getDragLayer().getOverviewScrim(),
OverviewScrim.SCRIM_PROGRESS, endScrimAlpha, PULLBACK_INTERPOLATOR);
OverviewScrim.SCRIM_MULTIPLIER, OVERVIEW_TO_HOME_SCRIM_MULTIPLIER,
PULLBACK_INTERPOLATOR);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
builder.addOnFrameCallback(recentsView::redrawLiveTile);
}
@@ -82,7 +82,15 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
mDetector = new SingleAxisSwipeDetector(activity, this, dir);
}
private boolean canInterceptTouch() {
private boolean canInterceptTouch(MotionEvent ev) {
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0) {
// Don't intercept swipes on the nav bar, as user might be trying to go home
// during a task dismiss animation.
if (mCurrentAnimation != null) {
mCurrentAnimation.getAnimationPlayer().end();
}
return false;
}
if (mCurrentAnimation != null) {
mCurrentAnimation.forceFinishIfCloseToEnd();
}
@@ -118,7 +126,7 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
clearState();
}
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !canInterceptTouch();
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
return false;
}
@@ -664,17 +664,17 @@ public class TouchInteractionService extends Service implements PluginListener<O
runningComponent != null && runningComponent.equals(homeComponent);
}
if (gestureState.getRunningTask() == null) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()
&& gestureState.getActivityInterface().isInLiveTileMode()) {
return createOverviewInputConsumer(
previousGestureState, gestureState, event, forceOverviewInputConsumer);
} else if (gestureState.getRunningTask() == null) {
return mResetGestureInputConsumer;
} else if (previousGestureState.isRunningAnimationToLauncher()
|| gestureState.getActivityInterface().isResumed()
|| forceOverviewInputConsumer) {
return createOverviewInputConsumer(
previousGestureState, gestureState, event, forceOverviewInputConsumer);
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get()
&& gestureState.getActivityInterface().isInLiveTileMode()) {
return createOverviewInputConsumer(
previousGestureState, gestureState, event, forceOverviewInputConsumer);
} else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) {
return mResetGestureInputConsumer;
} else {
@@ -40,14 +40,13 @@ public class LiveTileOverlay extends Drawable {
public static final LiveTileOverlay INSTANCE = new LiveTileOverlay();
private final Paint mPaint = new Paint();
private final RectF mCurrentRect = new RectF();
private final Rect mBoundsRect = new Rect();
private RectF mCurrentRect;
private float mCornerRadius;
private Drawable mIcon;
private Animator mIconAnimator;
private boolean mDrawEnabled = true;
private float mIconAnimationProgress = 0f;
private boolean mIsAttached;
@@ -58,7 +57,7 @@ public class LiveTileOverlay extends Drawable {
public void update(RectF currentRect, float cornerRadius) {
invalidateSelf();
mCurrentRect = currentRect;
mCurrentRect.set(currentRect);
mCornerRadius = cornerRadius;
mCurrentRect.roundOut(mBoundsRect);
@@ -93,16 +92,9 @@ public class LiveTileOverlay extends Drawable {
return mIconAnimationProgress;
}
public void setDrawEnabled(boolean drawEnabled) {
if (mDrawEnabled != drawEnabled) {
mDrawEnabled = drawEnabled;
invalidateSelf();
}
}
@Override
public void draw(Canvas canvas) {
if (mCurrentRect != null && mDrawEnabled) {
if (mCurrentRect != null) {
canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
if (mIcon != null && mIconAnimationProgress > 0f) {
canvas.save();
+75
View File
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 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.
-->
<com.android.launcher3.views.SearchResultPlayItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:orientation="horizontal">
<View
android:id="@+id/icon"
android:layout_width="@dimen/deep_shortcut_icon_size"
android:layout_height="@dimen/deep_shortcut_icon_size"
android:layout_gravity="start|center_vertical"
android:background="@drawable/ic_deepshortcut_placeholder" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/title_view"
style="@style/TextHeadline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp" />
<TextView
android:id="@+id/detail_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/detail_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:visibility="gone" />
<TextView
android:id="@+id/detail_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:visibility="gone" />
</LinearLayout>
<Button
android:id="@+id/try_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:background="?android:attr/selectableItemBackground"
android:text="@string/search_action_try_now">
</Button>
</com.android.launcher3.views.SearchResultPlayItem>
+8 -8
View File
@@ -13,11 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/section_title"
android:textSize="14sp"
android:fontFamily="@style/TextHeadline"
android:layout_width="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:padding="4dp"
android:layout_height="wrap_content"/>
<com.android.launcher3.views.SearchSectionHeaderView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/section_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@style/TextHeadline"
android:padding="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp" />
+2
View File
@@ -70,6 +70,8 @@
<!--All apps Search-->
<!-- Section title for apps [CHAR_LIMIT=50] -->
<string name="search_corpus_apps">Apps</string>
<!-- try instant app action for play search result [CHAR_LIMIT=50 -->
<string name="search_action_try_now">Try Now</string>
<!-- Popup items -->
<!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
+14 -10
View File
@@ -24,7 +24,6 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
import static com.android.launcher3.InstallShortcutReceiver.FLAG_DRAG_AND_DROP;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
@@ -42,6 +41,9 @@ import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKG
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONSTOP;
import static com.android.launcher3.logging.StatsLogManager.containerTypeToAtomState;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -119,6 +121,7 @@ import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.model.ModelUtils;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.data.AppInfo;
@@ -911,8 +914,8 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
getUserEventDispatcher().startSession();
// Process any items that were added while Launcher was away.
InstallShortcutReceiver.disableAndFlushInstallQueue(
InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
ItemInstallQueue.INSTANCE.get(this)
.resumeModelPush(FLAG_ACTIVITY_PAUSED);
// Refresh shortcuts if the permission changed.
mModel.validateModelDataOnResume();
@@ -1006,7 +1009,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
if (state == SPRING_LOADED) {
// Prevent any Un/InstallShortcutReceivers from updating the db while we are
// not on homescreen
InstallShortcutReceiver.enableInstallQueue(FLAG_DRAG_AND_DROP);
ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_DRAG_AND_DROP);
getRotationHelper().setCurrentStateRequest(REQUEST_LOCK);
mWorkspace.showPageIndicatorAtCurrentScroll();
@@ -1031,7 +1034,8 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
if (state == NORMAL) {
// Re-enable any Un/InstallShortcutReceiver and now process any queued items
InstallShortcutReceiver.disableAndFlushInstallQueue(FLAG_DRAG_AND_DROP, this);
ItemInstallQueue.INSTANCE.get(this)
.resumeModelPush(FLAG_DRAG_AND_DROP);
// Clear any rotation locks when going to normal state
getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
@@ -1065,7 +1069,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
@Override
protected void onPause() {
// Ensure that items added to Launcher are queued until Launcher returns
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED);
ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED);
super.onPause();
mDragController.cancelDrag();
@@ -2420,8 +2424,8 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
mPendingActivityResult = null;
}
InstallShortcutReceiver.disableAndFlushInstallQueue(
InstallShortcutReceiver.FLAG_LOADER_RUNNING, this);
ItemInstallQueue.INSTANCE.get(this)
.resumeModelPush(FLAG_LOADER_RUNNING);
// When undoing the removal of the last item on a page, return to that page.
// Since we are just resetting the current page without user interaction,
@@ -2448,8 +2452,8 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
private ValueAnimator createNewAppBounceAnimation(View v, int i) {
ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
.setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION);
bounceAnim.setStartDelay(i * ItemInstallQueue.NEW_SHORTCUT_STAGGER_DELAY);
bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
return bounceAnim;
}
+3 -1
View File
@@ -43,6 +43,7 @@ import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.CacheDataUpdatedTask;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.model.LoaderResults;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDelegate;
@@ -328,7 +329,8 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
*/
public boolean startLoader() {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
ItemInstallQueue.INSTANCE.get(mApp.getContext())
.pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
synchronized (mLock) {
// Don't bother to start the thread if we know it's not going to do anything
final Callbacks[] callbacksList = getCallbacks();
@@ -100,10 +100,12 @@ public class LauncherProvider extends ContentProvider {
public static final int SCHEMA_VERSION = 28;
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings";
public static final String KEY_LAYOUT_PROVIDER_AUTHORITY = "KEY_LAYOUT_PROVIDER_AUTHORITY";
static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
protected DatabaseHelper mOpenHelper;
protected String mProviderAuthority;
private long mLastRestoreTimestamp = 0L;
@@ -367,7 +369,8 @@ public class LauncherProvider extends ContentProvider {
case LauncherSettings.Settings.METHOD_WAS_EMPTY_DB_CREATED : {
Bundle result = new Bundle();
result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
Utilities.getPrefs(getContext()).getBoolean(EMPTY_DATABASE_CREATED, false));
Utilities.getPrefs(getContext()).getBoolean(
mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false));
return result;
}
case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
@@ -437,6 +440,7 @@ public class LauncherProvider extends ContentProvider {
getContext(), true /* forMigration */)));
return result;
}
return null;
}
case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
@@ -450,6 +454,23 @@ public class LauncherProvider extends ContentProvider {
() -> mOpenHelper));
return result;
}
return null;
}
case LauncherSettings.Settings.METHOD_SWITCH_DATABASE: {
if (TextUtils.equals(arg, mOpenHelper.getDatabaseName())) return null;
final DatabaseHelper helper = mOpenHelper;
if (extras == null || !extras.containsKey(KEY_LAYOUT_PROVIDER_AUTHORITY)) {
mProviderAuthority = null;
} else {
mProviderAuthority = extras.getString(KEY_LAYOUT_PROVIDER_AUTHORITY);
}
mOpenHelper = DatabaseHelper.createDatabaseHelper(
getContext(), arg, false /* forMigration */);
helper.close();
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app == null) return null;
app.getModel().forceReload();
return null;
}
}
return null;
@@ -492,7 +513,8 @@ public class LauncherProvider extends ContentProvider {
}
private void clearFlagEmptyDbCreated() {
Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit();
Utilities.getPrefs(getContext()).edit()
.remove(mOpenHelper.getKey(EMPTY_DATABASE_CREATED)).commit();
}
/**
@@ -505,7 +527,7 @@ public class LauncherProvider extends ContentProvider {
synchronized private void loadDefaultFavoritesIfNecessary() {
SharedPreferences sp = Utilities.getPrefs(getContext());
if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
Log.d(TAG, "loading default workspace");
AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
@@ -553,8 +575,13 @@ public class LauncherProvider extends ContentProvider {
*/
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
Context ctx = getContext();
String authority = Settings.Secure.getString(ctx.getContentResolver(),
"launcher3.layout.provider");
final String authority;
if (!TextUtils.isEmpty(mProviderAuthority)) {
authority = mProviderAuthority;
} else {
authority = Settings.Secure.getString(ctx.getContentResolver(),
"launcher3.layout.provider");
}
if (TextUtils.isEmpty(authority)) {
return null;
}
@@ -693,12 +720,26 @@ public class LauncherProvider extends ContentProvider {
}
}
/**
* Re-composite given key in respect to database. If the current db is
* {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
* given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
* string will be "EMPTY_DATABASE_CREATED@minimal.db".
*/
String getKey(final String key) {
if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
return key;
}
return key + "@" + getDatabaseName();
}
/**
* Overriden in tests.
*/
protected void onEmptyDbCreated() {
// Set the flag for empty DB
Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
Utilities.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true)
.commit();
}
public long getSerialNumberForUser(UserHandle user) {
@@ -354,14 +354,20 @@ public class LauncherSettings {
public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
public static final String METHOD_SWITCH_DATABASE = "switch_database";
public static final String EXTRA_VALUE = "value";
public static Bundle call(ContentResolver cr, String method) {
return call(cr, method, null);
return call(cr, method, null /* arg */);
}
public static Bundle call(ContentResolver cr, String method, String arg) {
return cr.call(CONTENT_URI, method, arg, null);
return call(cr, method, arg, null /* extras */);
}
public static Bundle call(ContentResolver cr, String method, String arg, Bundle extras) {
return cr.call(CONTENT_URI, method, arg, extras);
}
}
}
@@ -25,6 +25,7 @@ import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.text.TextUtils;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.pm.InstallSessionHelper;
/**
@@ -59,7 +60,8 @@ public class SessionCommitReceiver extends BroadcastReceiver {
return;
}
InstallShortcutReceiver.queueApplication(info.getAppPackageName(), user, context);
ItemInstallQueue.INSTANCE.get(context)
.queueItem(info.getAppPackageName(), user);
}
public static boolean isEnabled(Context context) {
+5 -1
View File
@@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_INACCESSIBLE;
import static com.android.launcher3.LauncherState.HINT_STATE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
@@ -943,7 +944,10 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
super.onScrollChanged(l, t, oldl, oldt);
// Update the page indicator progress.
boolean isTransitioning = mIsSwitchingState
// Unlike from other states, we show the page indicator when transitioning from HINT_STATE.
boolean isSwitchingState = mIsSwitchingState
&& mLauncher.getStateManager().getCurrentStableState() != HINT_STATE;
boolean isTransitioning = isSwitchingState
|| (getLayoutTransition() != null && getLayoutTransition().isRunning());
if (!isTransitioning) {
showPageIndicatorAtCurrentScroll();
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.allapps;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -527,6 +529,25 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
return mViewPager == null ? getActiveRecyclerView() : mViewPager;
}
/**
* Handles selection on focused view and returns success
*/
public boolean selectFocusedView(View v) {
ItemInfo itemInfo = getHighlightedItemInfo();
if (itemInfo != null) {
return mLauncher.startActivitySafely(v, itemInfo.getIntent(), itemInfo);
}
AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
if (focusedItem instanceof AdapterItemWithPayload) {
Runnable onSelection = ((AdapterItemWithPayload) focusedItem).getSelectionHandler();
if (onSelection != null) {
onSelection.run();
return true;
}
}
return false;
}
/**
* Returns the ItemInfo of a view that is in focus, ready to be launched by an IME.
*/
@@ -40,10 +40,10 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.HeroSearchResultView;
import java.util.List;
@@ -71,6 +71,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
public static final int VIEW_TYPE_SEARCH_HERO_APP = 1 << 6;
public static final int DETAIL_ROW_WITH_BUTTON = 1 << 7;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
@@ -85,6 +87,108 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
}
}
/**
* Info about a particular adapter item (can be either section or app)
*/
public static class AdapterItem {
/** Common properties */
// The index of this adapter item in the list
public int position;
// The type of this item
public int viewType;
/** App-only properties */
// The section name of this app. Note that there can be multiple items with different
// sectionNames in the same section
public String sectionName = null;
// The row that this item shows up on
public int rowIndex;
// The index of this app in the row
public int rowAppIndex;
// The associated AppInfo for the app
public AppInfo appInfo = null;
// The index of this app not including sections
public int appIndex = -1;
// Search section associated to result
public SearchSectionInfo searchSectionInfo = null;
/**
* Factory method for AppIcon AdapterItem
*/
public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
int appIndex) {
AdapterItem item = new AdapterItem();
item.viewType = VIEW_TYPE_ICON;
item.position = pos;
item.sectionName = sectionName;
item.appInfo = appInfo;
item.appIndex = appIndex;
return item;
}
/**
* Factory method for empty search results view
*/
public static AdapterItem asEmptySearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = VIEW_TYPE_EMPTY_SEARCH;
item.position = pos;
return item;
}
/**
* Factory method for a dividerView in AllAppsSearch
*/
public static AdapterItem asAllAppsDivider(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = VIEW_TYPE_ALL_APPS_DIVIDER;
item.position = pos;
return item;
}
/**
* Factory method for a market search button
*/
public static AdapterItem asMarketSearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = VIEW_TYPE_SEARCH_MARKET;
item.position = pos;
return item;
}
boolean isCountedForAccessibility() {
return viewType == VIEW_TYPE_ICON
|| viewType == VIEW_TYPE_SEARCH_HERO_APP
|| viewType == DETAIL_ROW_WITH_BUTTON;
}
}
/**
* Extension of AdapterItem that contains an extra payload specific to item
* @param <T> Play load Type
*/
public static class AdapterItemWithPayload<T> extends AdapterItem {
private T mPayload;
private Runnable mSelectionHandler;
public AdapterItemWithPayload(T payload, int type) {
mPayload = payload;
viewType = type;
}
public void setSelectionHandler(Runnable runnable) {
mSelectionHandler = runnable;
}
public Runnable getSelectionHandler() {
return mSelectionHandler;
}
public T getPayload() {
return mPayload;
}
}
/**
* A subclass of GridLayoutManager that overrides accessibility values during app search.
*/
@@ -286,6 +390,9 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
case VIEW_TYPE_SEARCH_HERO_APP:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_hero_app, parent, false));
case DETAIL_ROW_WITH_BUTTON:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_play_item, parent, false));
default:
throw new RuntimeException("Unexpected view type");
}
@@ -315,15 +422,11 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
}
break;
case VIEW_TYPE_SEARCH_CORPUS_TITLE:
TextView titleView = (TextView) holder.itemView;
titleView.setText(mApps.getAdapterItems().get(position).searchSectionInfo.getTitle(
titleView.getContext()));
break;
case DETAIL_ROW_WITH_BUTTON:
case VIEW_TYPE_SEARCH_HERO_APP:
HeroSearchResultView heroView = (HeroSearchResultView) holder.itemView;
heroView.prepareUsingAdapterItem(
(AlphabeticalAppsList.HeroAppAdapterItem) mApps.getAdapterItems().get(
position));
PayloadResultHandler payloadResultView = (PayloadResultHandler) holder.itemView;
payloadResultView.applyAdapterInfo(
(AdapterItemWithPayload) mApps.getAdapterItems().get(position));
break;
case VIEW_TYPE_ALL_APPS_DIVIDER:
// nothing to do
@@ -344,7 +447,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
@Override
public int getItemViewType(int position) {
AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
AdapterItem item = mApps.getAdapterItems().get(position);
return item.viewType;
}
@@ -278,7 +278,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
if (mApps == null) {
return;
}
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
// Skip early if there are no items or we haven't been measured
if (items.isEmpty() || mNumAppsPerRow == 0) {
@@ -352,7 +352,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
@Override
public int getCurrentScrollY() {
// Return early if there are no items or we haven't been measured
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
return -1;
}
@@ -368,14 +368,14 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
}
public int getCurrentScrollY(int position, int offset) {
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
AlphabeticalAppsList.AdapterItem posItem = position < items.size() ?
items.get(position) : null;
List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
AllAppsGridAdapter.AdapterItem posItem = position < items.size()
? items.get(position) : null;
int y = mCachedScrollPositions.get(position, -1);
if (y < 0) {
y = 0;
for (int i = 0; i < position; i++) {
AlphabeticalAppsList.AdapterItem item = items.get(i);
AllAppsGridAdapter.AdapterItem item = items.get(i);
if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
// Break once we reach the desired row
if (posItem != null && posItem.viewType == item.viewType &&
@@ -47,13 +47,13 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
// Since views in the same section will follow each other, we can skip to a last view in
// a section to get the bounds of the section without having to iterate on every item.
int itemCount = parent.getChildCount();
List<AlphabeticalAppsList.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
SectionDecorationHandler lastDecorationHandler = null;
int i = 0;
while (i < itemCount) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
AlphabeticalAppsList.AdapterItem adapterItem = adapterItems.get(position);
AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
if (adapterItem.searchSectionInfo != null) {
SearchSectionInfo sectionInfo = adapterItem.searchSectionInfo;
int endIndex = Math.min(i + sectionInfo.getPosEnd() - position, itemCount - 1);
@@ -19,10 +19,10 @@ package com.android.launcher3.allapps;
import android.content.Context;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LabelComparator;
@@ -62,101 +62,6 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
}
}
/**
* Info about a particular adapter item (can be either section or app)
*/
public static class AdapterItem {
/** Common properties */
// The index of this adapter item in the list
public int position;
// The type of this item
public int viewType;
/** App-only properties */
// The section name of this app. Note that there can be multiple items with different
// sectionNames in the same section
public String sectionName = null;
// The row that this item shows up on
public int rowIndex;
// The index of this app in the row
public int rowAppIndex;
// The associated AppInfo for the app
public AppInfo appInfo = null;
// The index of this app not including sections
public int appIndex = -1;
// Search section associated to result
public SearchSectionInfo searchSectionInfo = null;
public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
int appIndex) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_ICON;
item.position = pos;
item.sectionName = sectionName;
item.appInfo = appInfo;
item.appIndex = appIndex;
return item;
}
public static AdapterItem asEmptySearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH;
item.position = pos;
return item;
}
public static AdapterItem asAllAppsDivider(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER;
item.position = pos;
return item;
}
public static AdapterItem asMarketSearch(int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET;
item.position = pos;
return item;
}
/**
* Factory method for search section title AdapterItem
*/
public static AdapterItem asSearchTitle(SearchSectionInfo sectionInfo, int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_CORPUS_TITLE;
item.position = pos;
item.searchSectionInfo = sectionInfo;
return item;
}
boolean isCountedForAccessibility() {
return viewType == AllAppsGridAdapter.VIEW_TYPE_ICON
|| viewType == AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP;
}
}
/**
* Extension of AdapterItem that contains shortcut workspace items
*/
public static class HeroAppAdapterItem extends AdapterItem {
private ArrayList<WorkspaceItemInfo> mShortcutInfos;
public HeroAppAdapterItem(AppInfo info, ArrayList<WorkspaceItemInfo> shortcutInfos) {
viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP;
mShortcutInfos = shortcutInfos;
appInfo = info;
}
/**
* Returns list of shortcuts for appInfo
*/
public ArrayList<WorkspaceItemInfo> getShortcutInfos() {
return mShortcutInfos;
}
}
private final BaseDraggingActivity mLauncher;
@@ -396,7 +301,9 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
adapterItem.position = i;
mAdapterItems.add(adapterItem);
if (adapterItem.searchSectionInfo != lastSection) {
adapterItem.searchSectionInfo.setPosStart(i);
if (adapterItem.searchSectionInfo != null) {
adapterItem.searchSectionInfo.setPosStart(i);
}
if (lastSection != null) {
lastSection.setPosEnd(i - 1);
}
@@ -15,35 +15,26 @@
*/
package com.android.launcher3.allapps.search;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.PluginListener;
import java.util.ArrayList;
import java.util.List;
@@ -54,17 +45,14 @@ import java.util.function.Consumer;
*/
public class AllAppsSearchBarController
implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
OnFocusChangeListener, PluginListener<AllAppsSearchPlugin> {
OnFocusChangeListener {
private static final String TAG = "AllAppsSearchBarContoller";
protected BaseDraggingActivity mLauncher;
protected Callbacks mCb;
protected ExtendedEditText mInput;
protected String mQuery;
protected SearchAlgorithm mSearchAlgorithm;
private AllAppsSearchPlugin mPlugin;
private Consumer mPlubinCb;
public void setVisibility(int visibility) {
mInput.setVisibility(visibility);
@@ -85,18 +73,13 @@ public class AllAppsSearchBarController
mInput.setOnBackKeyListener(this);
mInput.setOnFocusChangeListener(this);
mSearchAlgorithm = searchAlgorithm;
PluginManagerWrapper.INSTANCE.get(launcher).addPluginListener(this,
AllAppsSearchPlugin.class);
mPlubinCb = secondaryCb;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (mPlugin != null) {
if (s.length() == 0) {
mPlugin.startedTyping();
}
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (mSearchAlgorithm instanceof PluginWrapper) {
((PluginWrapper) mSearchAlgorithm).runOnPluginIfConnected(
AllAppsSearchPlugin::startedTyping);
}
}
@@ -114,9 +97,6 @@ public class AllAppsSearchBarController
} else {
mSearchAlgorithm.cancel(false);
mSearchAlgorithm.doSearch(mQuery, mCb);
if (mPlugin != null) {
mPlugin.performSearch(mQuery, mPlubinCb);
}
}
}
@@ -133,10 +113,8 @@ public class AllAppsSearchBarController
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
ItemInfo info = Launcher.getLauncher(mLauncher).getAppsView()
.getHighlightedItemInfo();
if (info != null) {
return mLauncher.startActivitySafely(v, info.getIntent(), info);
if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) {
return true;
}
}
}
@@ -197,43 +175,14 @@ public class AllAppsSearchBarController
return mInput.isFocused();
}
@Override
public void onPluginConnected(AllAppsSearchPlugin allAppsSearchPlugin, Context context) {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
mPlugin = allAppsSearchPlugin;
checkCallPermission();
}
}
/**
* Check call permissions.
* A wrapper setup for running essential calls to plugin from search controller
*/
public void checkCallPermission() {
final String[] permission = {"android.permission.CALL_PHONE",
"android.permission.READ_CONTACTS"};
boolean request = false;
for (String p : permission) {
int permissionCheck = ContextCompat.checkSelfPermission(mLauncher, p);
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
request = true;
}
}
if (!request) return;
boolean rationale = false;
for (String p : permission) {
if (mLauncher.shouldShowRequestPermissionRationale(p)) {
rationale = true;
}
if (rationale) {
Log.e(TAG, p + " Show rationale");
Toast.makeText(mLauncher, "Requesting Permissions", Toast.LENGTH_SHORT).show();
} else {
ActivityCompat.requestPermissions(mLauncher, permission, 123);
Log.e(TAG, p + " request permission");
}
}
public interface PluginWrapper {
/**
* executes call if plugin is connected
*/
void runOnPluginIfConnected(Consumer<AllAppsSearchPlugin> plugin);
}
/**
@@ -246,11 +195,23 @@ public class AllAppsSearchBarController
*
* @param items sorted list of search result adapter items.
*/
void onSearchResult(String query, ArrayList<AlphabeticalAppsList.AdapterItem> items);
void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items);
/**
* Called when the search results should be cleared.
*/
void clearSearchResult();
}
/**
* An interface for supporting dynamic search results
*
* @param <T> Type of payload
*/
public interface PayloadResultHandler<T> {
/**
* Updates View using Adapter's payload
*/
void applyAdapterInfo(AdapterItemWithPayload<T> adapterItemWithPayload);
}
}
@@ -42,6 +42,7 @@ import com.android.launcher3.Insettable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
@@ -172,7 +173,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText
}
@Override
public void onSearchResult(String query, ArrayList<AlphabeticalAppsList.AdapterItem> items) {
public void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items) {
if (items != null) {
mApps.setSearchResults(items);
notifyResultChanged();
@@ -16,6 +16,7 @@
package com.android.launcher3.allapps.search;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
import static com.android.launcher3.allapps.AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP;
import android.content.Context;
import android.content.pm.ShortcutInfo;
@@ -23,9 +24,9 @@ import android.content.pm.ShortcutInfo;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.allapps.AlphabeticalAppsList.HeroAppAdapterItem;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseModelUpdateTask;
@@ -82,7 +83,7 @@ public class AppsSearchPipeline implements SearchPipeline {
/**
* Returns MAX_SHORTCUTS_COUNT shortcuts from local cache
* TODO: Shortcuts should be ranked based on relevancy
* TODO: Shortcuts should be ranked based on relevancy
*/
private ArrayList<WorkspaceItemInfo> getShortcutInfos(Context context, AppInfo appInfo) {
List<ShortcutInfo> shortcuts = new ShortcutRequest(context, appInfo.user)
@@ -126,7 +127,9 @@ public class AppsSearchPipeline implements SearchPipeline {
//hero app
AppInfo appInfo = apps.get(i);
ArrayList<WorkspaceItemInfo> shortcuts = getShortcutInfos(context, appInfo);
AdapterItem adapterItem = new HeroAppAdapterItem(appInfo, shortcuts);
AdapterItem adapterItem = new AllAppsGridAdapter.AdapterItemWithPayload(shortcuts,
VIEW_TYPE_SEARCH_HERO_APP);
adapterItem.appInfo = appInfo;
adapterItem.searchSectionInfo = mSearchSectionInfo;
adapterItems.add(adapterItem);
}
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.allapps.search;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -28,5 +28,5 @@ public interface SearchPipeline {
/**
* Perform query
*/
void performSearch(String query, Consumer<ArrayList<AlphabeticalAppsList.AdapterItem>> cb);
void performSearch(String query, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> cb);
}
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.allapps.search;
import android.content.Context;
import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
/**
@@ -24,7 +22,7 @@ import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHa
*/
public class SearchSectionInfo {
private final int mTitleResId;
private String mTitle;
private SectionDecorationHandler mDecorationHandler;
public int getPosStart() {
@@ -47,11 +45,11 @@ public class SearchSectionInfo {
private int mPosEnd;
public SearchSectionInfo() {
this(-1);
this(null);
}
public SearchSectionInfo(int titleResId) {
mTitleResId = titleResId;
public SearchSectionInfo(String title) {
mTitle = title;
}
public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) {
@@ -66,10 +64,7 @@ public class SearchSectionInfo {
/**
* Returns the section's title
*/
public String getTitle(Context context) {
if (mTitleResId == -1) {
return "";
}
return context.getString(mTitleResId);
public String getTitle() {
return mTitle == null ? "" : mTitle;
}
}
@@ -43,13 +43,13 @@ import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetHost;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.R;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -249,7 +249,7 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener
*/
public void onPlaceAutomaticallyClick(View v) {
if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) {
InstallShortcutReceiver.queueShortcut(mRequest.getShortcutInfo(), this);
ItemInstallQueue.INSTANCE.get(this).queueItem(mRequest.getShortcutInfo());
logCommand(Action.Command.CONFIRM);
mRequest.accept();
finish();
@@ -270,7 +270,8 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener
}
private void acceptWidget(int widgetId) {
InstallShortcutReceiver.queueWidget(mRequest.getAppWidgetProviderInfo(this), widgetId, this);
ItemInstallQueue.INSTANCE.get(this)
.queueItem(mRequest.getAppWidgetProviderInfo(this), widgetId);
mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
mRequest.accept(mWidgetOptions);
logCommand(Action.Command.CONFIRM);
@@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.OVERVIEW;
import android.graphics.Rect;
import android.util.FloatProperty;
import android.view.View;
import android.view.ViewGroup;
@@ -33,10 +34,25 @@ import androidx.annotation.Nullable;
*/
public class OverviewScrim extends Scrim {
public static final FloatProperty<OverviewScrim> SCRIM_MULTIPLIER =
new FloatProperty<OverviewScrim>("scrimMultiplier") {
@Override
public Float get(OverviewScrim scrim) {
return scrim.mScrimMultiplier;
}
@Override
public void setValue(OverviewScrim scrim, float v) {
scrim.setScrimMultiplier(v);
}
};
private @NonNull View mStableScrimmedView;
// Might be higher up if mStableScrimmedView is invisible.
private @Nullable View mCurrentScrimmedView;
private float mScrimMultiplier = 1f;
public OverviewScrim(View view) {
super(view);
mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
@@ -68,4 +84,16 @@ public class OverviewScrim extends Scrim {
public @Nullable View getScrimmedView() {
return mCurrentScrimmedView;
}
private void setScrimMultiplier(float scrimMultiplier) {
if (Float.compare(mScrimMultiplier, scrimMultiplier) != 0) {
mScrimMultiplier = scrimMultiplier;
invalidate();
}
}
@Override
protected int getScrimAlpha() {
return Math.round(super.getScrimAlpha() * mScrimMultiplier);
}
}
@@ -61,7 +61,11 @@ public class Scrim implements View.OnAttachStateChangeListener,
}
public void draw(Canvas canvas) {
canvas.drawColor(setColorAlphaBound(mScrimColor, mScrimAlpha));
canvas.drawColor(setColorAlphaBound(mScrimColor, getScrimAlpha()));
}
protected int getScrimAlpha() {
return mScrimAlpha;
}
private void setScrimProgress(float progress) {
@@ -31,7 +31,6 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Workspace;
@@ -298,7 +297,7 @@ public class BgDataModel {
.filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
.map(ShortcutKey::fromItemInfo),
// Pending shortcuts
InstallShortcutReceiver.getPendingShortcuts(context)
ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts()
.stream().filter(si -> si.user.equals(user)))
.collect(groupingBy(ShortcutKey::getPackageName,
mapping(ShortcutKey::getId, Collectors.toSet())));
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.launcher3;
package com.android.launcher3.model;
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
@@ -42,13 +42,19 @@ import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
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.Preconditions;
import org.json.JSONException;
@@ -63,16 +69,15 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class InstallShortcutReceiver {
/**
* Class to maintain a queue of pending items to be added to the workspace.
*/
public class ItemInstallQueue {
public static final int FLAG_ACTIVITY_PAUSED = 1;
public static final int FLAG_LOADER_RUNNING = 2;
public static final int FLAG_DRAG_AND_DROP = 4;
// Determines whether to defer installing shortcuts immediately until
// processAllPendingInstalls() is called.
private static int sInstallQueueDisabledFlags = 0;
private static final String TAG = "InstallShortcutReceiver";
private static final boolean DBG = false;
@@ -82,10 +87,23 @@ public class InstallShortcutReceiver {
public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
public static MainThreadInitializedObject<ItemInstallQueue> INSTANCE =
new MainThreadInitializedObject<>(ItemInstallQueue::new);
private final Context mContext;
// Determines whether to defer installing shortcuts immediately until
// processAllPendingInstalls() is called.
private int mInstallQueueDisabledFlags = 0;
private ItemInstallQueue(Context context) {
mContext = context;
}
@WorkerThread
private static void addToQueue(Context context, PendingInstallShortcutInfo info) {
String encoded = info.encodeToString(context);
SharedPreferences prefs = Utilities.getPrefs(context);
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);
@@ -93,14 +111,15 @@ public class InstallShortcutReceiver {
}
@WorkerThread
private static void flushQueueInBackground(Context context) {
if (Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null) {
private void flushQueueInBackground() {
Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
if (launcher == null) {
// Launcher not loaded
return;
}
ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
SharedPreferences prefs = Utilities.getPrefs(context);
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) {
@@ -108,29 +127,31 @@ public class InstallShortcutReceiver {
}
for (String encoded : strings) {
PendingInstallShortcutInfo info = decode(encoded, context);
PendingInstallShortcutInfo info = decode(encoded, mContext);
if (info == null) {
continue;
}
// Generate a shortcut info to add into the model
installQueue.add(info.getItemInfo(context));
installQueue.add(info.getItemInfo(mContext));
}
prefs.edit().remove(APPS_PENDING_INSTALL).apply();
if (!installQueue.isEmpty()) {
LauncherAppState.getInstance(context).getModel()
.addAndBindAddedWorkspaceItems(installQueue);
launcher.getModel().addAndBindAddedWorkspaceItems(installQueue);
}
}
public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
UserHandle user) {
/**
* Removes previously added items from the queue.
*/
@WorkerThread
public void removeFromInstallQueue(HashSet<String> packageNames, UserHandle user) {
if (packageNames.isEmpty()) {
return;
}
Preconditions.assertWorkerThread();
SharedPreferences sp = Utilities.getPrefs(context);
SharedPreferences sp = Utilities.getPrefs(mContext);
Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
if (DBG) {
Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
@@ -144,7 +165,7 @@ public class InstallShortcutReceiver {
while (newStringsIter.hasNext()) {
String encoded = newStringsIter.next();
try {
Decoder decoder = new Decoder(encoded, context);
Decoder decoder = new Decoder(encoded, mContext);
if (packageNames.contains(getIntentPackage(decoder.intent))
&& user.equals(decoder.user)) {
newStringsIter.remove();
@@ -157,30 +178,42 @@ public class InstallShortcutReceiver {
sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
}
public static void queueShortcut(ShortcutInfo info, Context context) {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(info), context);
/**
* Adds an item to the install queue
*/
public void queueItem(ShortcutInfo info) {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(info));
}
public static void queueWidget(AppWidgetProviderInfo info, int widgetId, Context context) {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId), context);
/**
* Adds an item to the install queue
*/
public void queueItem(AppWidgetProviderInfo info, int widgetId) {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId));
}
public static void queueApplication(
String packageName, UserHandle userHandle, Context context) {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(packageName, userHandle), context);
/**
* Adds an item to the install queue
*/
public void queueItem(String packageName, UserHandle userHandle) {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(packageName, userHandle));
}
public static HashSet<ShortcutKey> getPendingShortcuts(Context context) {
/**
* Returns all pending shorts in the queue
*/
@WorkerThread
public HashSet<ShortcutKey> getPendingShortcuts() {
HashSet<ShortcutKey> result = new HashSet<>();
Set<String> strings = Utilities.getPrefs(context).getStringSet(APPS_PENDING_INSTALL, null);
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, context);
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));
}
@@ -191,28 +224,35 @@ public class InstallShortcutReceiver {
return result;
}
private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
// Queue the item up for adding if launcher has not loaded properly yet
MODEL_EXECUTOR.post(() -> addToQueue(context, info));
flushInstallQueue(context);
MODEL_EXECUTOR.post(() -> addToQueue(info));
flushInstallQueue();
}
public static void enableInstallQueue(int flag) {
sInstallQueueDisabledFlags |= flag;
}
public static void disableAndFlushInstallQueue(int flag, Context context) {
sInstallQueueDisabledFlags &= ~flag;
flushInstallQueue(context);
/**
* Pauses the push-to-model flow until unpaused. All items are held in the queue and
* not added to the model.
*/
public void pauseModelPush(int flag) {
mInstallQueueDisabledFlags |= flag;
}
static void flushInstallQueue(Context context) {
if (sInstallQueueDisabledFlags != 0) {
/**
* Adds all the queue items to the model if the use is completely resumed.
*/
public void resumeModelPush(int flag) {
mInstallQueueDisabledFlags &= ~flag;
flushInstallQueue();
}
private void flushInstallQueue() {
if (mInstallQueueDisabledFlags != 0) {
return;
}
MODEL_EXECUTOR.post(() -> flushQueueInBackground(context));
MODEL_EXECUTOR.post(this::flushQueueInBackground);
}
private static class PendingInstallShortcutInfo extends ItemInfo {
final Intent intent;
@@ -28,7 +28,6 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.FeatureFlags;
@@ -320,7 +319,8 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
deleteAndBindComponentsRemoved(removeMatch);
// Remove any queued items from the install queue
InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
ItemInstallQueue.INSTANCE.get(context)
.removeFromInstallQueue(removedPackages, mUser);
}
if (mOp == OP_ADD) {
@@ -32,11 +32,11 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LooperExecutor;
@@ -213,8 +213,8 @@ public class InstallSessionHelper {
&& !mPromiseIconIds.contains(sessionInfo.getSessionId())
&& new PackageManagerHelper(mAppContext).getApplicationInfo(
sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
InstallShortcutReceiver.queueApplication(
sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), mAppContext);
ItemInstallQueue.INSTANCE.get(mAppContext)
.queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
mPromiseIconIds.add(sessionInfo.getSessionId());
updatePromiseIconPrefs();
@@ -30,7 +30,8 @@ import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.graphics.DragPreviewProvider;
@@ -47,7 +48,8 @@ import java.util.List;
/**
* A view representing a high confidence app search result that includes shortcuts
*/
public class HeroSearchResultView extends LinearLayout implements DragSource {
public class HeroSearchResultView extends LinearLayout implements DragSource,
AllAppsSearchBarController.PayloadResultHandler<List<WorkspaceItemInfo>> {
BubbleTextView mBubbleTextView;
View mIconView;
@@ -96,18 +98,18 @@ public class HeroSearchResultView extends LinearLayout implements DragSource {
/**
* Apply {@link ItemInfo} for appIcon and shortcut Icons
*/
public void prepareUsingAdapterItem(AlphabeticalAppsList.HeroAppAdapterItem adapterItem) {
@Override
public void applyAdapterInfo(AdapterItemWithPayload<List<WorkspaceItemInfo>> adapterItem) {
mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
mIconView.setBackground(mBubbleTextView.getIcon());
mIconView.setTag(adapterItem.appInfo);
List<WorkspaceItemInfo> shorcutInfos = adapterItem.getShortcutInfos();
List<WorkspaceItemInfo> shorcutInfos = adapterItem.getPayload();
for (int i = 0; i < mDeepShortcutTextViews.length; i++) {
mDeepShortcutTextViews[i].setVisibility(shorcutInfos.size() > i ? VISIBLE : GONE);
if (i < shorcutInfos.size()) {
mDeepShortcutTextViews[i].applyFromWorkspaceItem(shorcutInfos.get(i));
}
}
}
@Override
@@ -126,7 +128,6 @@ public class HeroSearchResultView extends LinearLayout implements DragSource {
mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE);
}
/**
* Drag and drop handler for popup items in Launcher activity
*/
@@ -0,0 +1,154 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.views;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import java.io.IOException;
import java.net.URL;
/**
* A View representing a PlayStore item.
*/
public class SearchResultPlayItem extends LinearLayout implements
AllAppsSearchBarController.PayloadResultHandler<Bundle> {
private final DeviceProfile mDeviceProfile;
private View mIconView;
private TextView mTitleView;
private TextView[] mDetailViews = new TextView[3];
private Button mPreviewButton;
private String mPackageName;
private boolean mIsInstantGame;
public SearchResultPlayItem(Context context) {
this(context, null, 0);
}
public SearchResultPlayItem(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultPlayItem(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDeviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mIconView = findViewById(R.id.icon);
mTitleView = findViewById(R.id.title_view);
mPreviewButton = findViewById(R.id.try_button);
mPreviewButton.setOnClickListener(view -> launchInstantGame());
mDetailViews[0] = findViewById(R.id.detail_0);
mDetailViews[1] = findViewById(R.id.detail_1);
mDetailViews[2] = findViewById(R.id.detail_2);
ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
iconParams.height = mDeviceProfile.allAppsIconSizePx;
iconParams.width = mDeviceProfile.allAppsIconSizePx;
setOnClickListener(view -> handleSelection());
}
@Override
public void applyAdapterInfo(AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
Bundle bundle = adapterItemWithPayload.getPayload();
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
if (bundle.getString("package", "").equals(mPackageName)) {
return;
}
mIsInstantGame = bundle.getBoolean("instant_game", false);
mPackageName = bundle.getString("package");
mPreviewButton.setVisibility(mIsInstantGame ? VISIBLE : GONE);
mTitleView.setText(bundle.getString("title"));
// TODO: Should use a generic type to get values b/165320033
showIfNecessary(mDetailViews[0], bundle.getString("price"));
showIfNecessary(mDetailViews[1], bundle.getString("rating"));
showIfNecessary(mDetailViews[2], bundle.getString("category"));
mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
UI_HELPER_EXECUTOR.execute(() -> {
try {
// TODO: Handle caching
URL url = new URL(bundle.getString("icon_url"));
Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),
Bitmap.createScaledBitmap(bitmap, mDeviceProfile.allAppsIconSizePx,
mDeviceProfile.allAppsIconSizePx, false));
mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
} catch (IOException e) {
e.printStackTrace();
}
});
}
private void showIfNecessary(TextView textView, @Nullable String string) {
if (string == null || string.isEmpty()) {
textView.setVisibility(GONE);
} else {
textView.setText(string);
textView.setVisibility(VISIBLE);
}
}
private void handleSelection() {
if (mPackageName == null) return;
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(
"https://play.google.com/store/apps/details?id="
+ mPackageName));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(i);
}
private void launchInstantGame() {
if (!mIsInstantGame) return;
Intent intent = new Intent(Intent.ACTION_VIEW);
String referrer = "Pixel_Launcher";
String id = mPackageName;
String deepLinkUrl = "market://details?id=" + id + "&launch=true&referrer=" + referrer;
intent.setPackage("com.android.vending");
intent.setData(Uri.parse(deepLinkUrl));
intent.putExtra("overlay", true);
intent.putExtra("callerId", getContext().getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2020 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.views;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
/**
* Header text view that shows a title for a given section in All apps search
*/
public class SearchSectionHeaderView extends TextView implements
AllAppsSearchBarController.PayloadResultHandler<String> {
public SearchSectionHeaderView(Context context) {
super(context);
}
public SearchSectionHeaderView(Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
public SearchSectionHeaderView(Context context, @Nullable AttributeSet attrs, int styleAttr) {
super(context, attrs, styleAttr);
}
@Override
public void applyAdapterInfo(AllAppsGridAdapter.AdapterItemWithPayload<String> adapterItem) {
String title = adapterItem.getPayload();
if (title == null || !title.isEmpty()) {
setText(title);
setVisibility(VISIBLE);
} else {
setVisibility(INVISIBLE);
}
}
}