summaryrefslogtreecommitdiff
path: root/go
diff options
context:
space:
mode:
Diffstat (limited to 'go')
-rw-r--r--go/AndroidManifest.xml4
-rw-r--r--go/quickstep/res/values/integers.xml20
-rw-r--r--go/quickstep/src/com/android/launcher3/AppSharing.java67
-rw-r--r--go/quickstep/src/com/android/launcher3/model/AppShareabilityChecker.java38
-rw-r--r--go/quickstep/src/com/android/launcher3/model/AppShareabilityDatabase.java60
-rw-r--r--go/quickstep/src/com/android/launcher3/model/AppShareabilityJobService.java77
-rw-r--r--go/quickstep/src/com/android/launcher3/model/AppShareabilityManager.java211
-rw-r--r--go/quickstep/src/com/android/launcher3/model/AppShareabilityStatus.java39
8 files changed, 503 insertions, 13 deletions
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index 2671604f94..728b3262f0 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -54,6 +54,10 @@
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:enabled="false"
tools:node="replace" />
+
+ <service
+ android:name="com.android.launcher3.model.AppShareabilityJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" />
</application>
</manifest>
diff --git a/go/quickstep/res/values/integers.xml b/go/quickstep/res/values/integers.xml
new file mode 100644
index 0000000000..e6e8111eab
--- /dev/null
+++ b/go/quickstep/res/values/integers.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 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.
+ -->
+<resources>
+ <!-- Job IDs must be integers unique within their package -->
+ <integer name="app_shareability_job_id">200</integer>
+</resources> \ No newline at end of file
diff --git a/go/quickstep/src/com/android/launcher3/AppSharing.java b/go/quickstep/src/com/android/launcher3/AppSharing.java
index b72e71c2b5..fd5456c73b 100644
--- a/go/quickstep/src/com/android/launcher3/AppSharing.java
+++ b/go/quickstep/src/com/android/launcher3/AppSharing.java
@@ -28,7 +28,12 @@ import android.view.View;
import androidx.core.content.FileProvider;
+import com.android.launcher3.model.AppShareabilityChecker;
+import com.android.launcher3.model.AppShareabilityJobService;
+import com.android.launcher3.model.AppShareabilityManager;
+import com.android.launcher3.model.AppShareabilityManager.ShareabilityStatus;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
import java.io.File;
@@ -44,6 +49,11 @@ public final class AppSharing {
* because it is unique to Go and not toggleable at runtime.
*/
public static final boolean ENABLE_APP_SHARING = true;
+ /**
+ * With this flag enabled, the Share App button will be dynamically enabled/disabled based
+ * on each app's shareability status.
+ */
+ public static final boolean ENABLE_SHAREABILITY_CHECK = false;
private static final String TAG = "AppSharing";
private static final String FILE_PROVIDER_SUFFIX = ".overview.fileprovider";
@@ -51,22 +61,12 @@ public final class AppSharing {
private static final String APP_MIME_TYPE = "application/application";
private final String mSharingComponent;
+ private AppShareabilityManager mShareabilityMgr;
private AppSharing(Launcher launcher) {
mSharingComponent = launcher.getText(R.string.app_sharing_component).toString();
}
- private boolean canShare(ItemInfo info) {
- /**
- * TODO: Implement once b/168831749 has been resolved
- * The implementation should check the validity of the app.
- * It should also check whether the app is free or paid, returning false in the latter case.
- * For now, all checks occur in the sharing app.
- * So, we simply check whether the sharing app is defined.
- */
- return !TextUtils.isEmpty(mSharingComponent);
- }
-
private Uri getShareableUri(Context context, String path, String displayName) {
String authority = BuildConfig.APPLICATION_ID + FILE_PROVIDER_SUFFIX;
File pathFile = new File(path);
@@ -74,19 +74,39 @@ public final class AppSharing {
}
private SystemShortcut<Launcher> getShortcut(Launcher launcher, ItemInfo info) {
- if (!canShare(info)) {
+ if (TextUtils.isEmpty(mSharingComponent)) {
return null;
}
-
return new Share(launcher, info);
}
/**
+ * Instantiates AppShareabilityManager, which then reads app shareability data from disk
+ * Also schedules a job to update those data
+ * @param context The application context
+ * @param checker An implementation of AppShareabilityChecker to perform the actual checks
+ * when updating the data
+ */
+ public static void setUpShareabilityCache(Context context, AppShareabilityChecker checker) {
+ AppShareabilityManager shareMgr = AppShareabilityManager.INSTANCE.get(context);
+ shareMgr.setShareabilityChecker(checker);
+ AppShareabilityJobService.schedule(context);
+ }
+
+ /**
* The Share App system shortcut, used to initiate p2p sharing of a given app
*/
public final class Share extends SystemShortcut<Launcher> {
+ private PopupDataProvider mPopupDataProvider;
+
public Share(Launcher target, ItemInfo itemInfo) {
super(R.drawable.ic_share, R.string.app_share_drop_target_label, target, itemInfo);
+ mPopupDataProvider = target.getPopupDataProvider();
+
+ if (ENABLE_SHAREABILITY_CHECK) {
+ mShareabilityMgr = AppShareabilityManager.INSTANCE.get(target);
+ checkShareability(/* requestUpdateIfUnknown */ true);
+ }
}
@Override
@@ -122,6 +142,27 @@ public final class AppSharing {
AbstractFloatingView.closeAllOpenViews(mTarget);
}
+
+ private void onStatusUpdated(boolean success) {
+ if (!success) {
+ // Something went wrong. Specific error logged in AppShareabilityManager.
+ return;
+ }
+ checkShareability(/* requestUpdateIfUnknown */ false);
+ mTarget.runOnUiThread(() -> {
+ mPopupDataProvider.redrawSystemShortcuts();
+ });
+ }
+
+ private void checkShareability(boolean requestUpdateIfUnknown) {
+ String packageName = mItemInfo.getTargetComponent().getPackageName();
+ @ShareabilityStatus int status = mShareabilityMgr.getStatus(packageName);
+ setEnabled(status == ShareabilityStatus.SHAREABLE);
+
+ if (requestUpdateIfUnknown && status == ShareabilityStatus.UNKNOWN) {
+ mShareabilityMgr.requestAppStatusUpdate(packageName, this::onStatusUpdated);
+ }
+ }
}
/**
diff --git a/go/quickstep/src/com/android/launcher3/model/AppShareabilityChecker.java b/go/quickstep/src/com/android/launcher3/model/AppShareabilityChecker.java
new file mode 100644
index 0000000000..0a82904670
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/model/AppShareabilityChecker.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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.model;
+
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Interface for checking apps' shareability. Implementations need to be able to determine whether
+ * apps are shareable given their package names.
+ */
+public interface AppShareabilityChecker {
+ /**
+ * Checks the shareability of the provided apps. Once the check is complete, updates the
+ * provided manager with the results and calls the (optionally) provided callback.
+ * @param packageNames The apps to check
+ * @param shareMgr The manager to receive the results
+ * @param callback Optional callback to be invoked when the check is finished
+ */
+ void checkApps(List<String> packageNames, AppShareabilityManager shareMgr,
+ @Nullable Consumer<Boolean> callback);
+}
diff --git a/go/quickstep/src/com/android/launcher3/model/AppShareabilityDatabase.java b/go/quickstep/src/com/android/launcher3/model/AppShareabilityDatabase.java
new file mode 100644
index 0000000000..03eed7eccc
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/model/AppShareabilityDatabase.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 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.model;
+
+import androidx.room.Dao;
+import androidx.room.Database;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.RoomDatabase;
+import androidx.room.Update;
+
+import java.util.List;
+
+/**
+ * This database maintains a collection of AppShareabilityStatus items
+ * In its intended use case, there will be one entry for each app installed on the device
+ */
+@Database(entities = {AppShareabilityStatus.class}, exportSchema = false, version = 1)
+public abstract class AppShareabilityDatabase extends RoomDatabase {
+ /**
+ * Data Access Object for this database
+ */
+ @Dao
+ public interface ShareabilityDao {
+ /** Add an AppShareabilityStatus to the database */
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ void insertAppStatus(AppShareabilityStatus status);
+
+ /** Add a collection of AppShareabilityStatus objects to the database */
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ void insertAppStatuses(AppShareabilityStatus... statuses);
+
+ /**
+ * Update an AppShareabilityStatus in the database
+ * @return The number of entries successfully updated
+ */
+ @Update
+ int updateAppStatus(AppShareabilityStatus status);
+
+ /** Retrieve all entries from the database */
+ @Query("SELECT * FROM AppShareabilityStatus")
+ List<AppShareabilityStatus> getAllEntries();
+ }
+
+ protected abstract ShareabilityDao shareabilityDao();
+}
diff --git a/go/quickstep/src/com/android/launcher3/model/AppShareabilityJobService.java b/go/quickstep/src/com/android/launcher3/model/AppShareabilityJobService.java
new file mode 100644
index 0000000000..60bea53c70
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/model/AppShareabilityJobService.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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.model;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.launcher3.R;
+
+/**
+ * A job to request AppShareabilityManager to update its shareability data
+ * The shareability status of an app is not expected to change often, so this job is only
+ * run periodically.
+ */
+public final class AppShareabilityJobService extends JobService {
+
+ private static final String TAG = "AppShareabilityJobService";
+ // Run this job once a week
+ private static final int RECURRENCE_INTERVAL_MILLIS = 604800000;
+
+ @Override
+ public boolean onStartJob(final JobParameters params) {
+ Context context = getApplicationContext();
+ AppShareabilityManager.INSTANCE.get(context).requestFullUpdate();
+ return false; // Job is finished
+ }
+
+ @Override
+ public boolean onStopJob(final JobParameters params) {
+ Log.d(TAG, "App shareability data update job stopped; id=" + params.getJobId()
+ + ", reason="
+ + JobParameters.getInternalReasonCodeDescription(params.getStopReason()));
+ return true; // Reschedule the job
+ }
+
+ /**
+ * Creates and schedules the job.
+ * Does not schedule a duplicate job if one is already pending.
+ * @param context The application context
+ */
+ public static void schedule(Context context) {
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+
+ final JobInfo pendingJob = jobScheduler.getPendingJob(R.integer.app_shareability_job_id);
+ if (pendingJob != null) {
+ // Don't schedule duplicate jobs
+ return;
+ }
+
+ final JobInfo newJob = new JobInfo.Builder(R.integer.app_shareability_job_id,
+ new ComponentName(context, AppShareabilityJobService.class))
+ .setPeriodic(RECURRENCE_INTERVAL_MILLIS)
+ .setPersisted(true)
+ .setRequiresBatteryNotLow(true)
+ .build();
+ jobScheduler.schedule(newJob);
+ }
+}
diff --git a/go/quickstep/src/com/android/launcher3/model/AppShareabilityManager.java b/go/quickstep/src/com/android/launcher3/model/AppShareabilityManager.java
new file mode 100644
index 0000000000..cf80c35972
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/model/AppShareabilityManager.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 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.model;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+import androidx.room.Room;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.launcher3.model.AppShareabilityDatabase.ShareabilityDao;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * This class maintains the shareability status of installed apps.
+ * Each app's status is retrieved from the Play Store's API. Statuses are cached in order
+ * to limit extraneous calls to that API (which can be time-consuming).
+ */
+public final class AppShareabilityManager {
+ @Retention(SOURCE)
+ @IntDef({
+ ShareabilityStatus.UNKNOWN,
+ ShareabilityStatus.NOT_SHAREABLE,
+ ShareabilityStatus.SHAREABLE
+ })
+ public @interface ShareabilityStatus {
+ int UNKNOWN = 0;
+ int NOT_SHAREABLE = 1;
+ int SHAREABLE = 2;
+ }
+
+ private static final String TAG = "AppShareabilityManager";
+ private static final String DB_NAME = "shareabilityDatabase";
+ public static MainThreadInitializedObject<AppShareabilityManager> INSTANCE =
+ new MainThreadInitializedObject<>(AppShareabilityManager::new);
+
+ private final Context mContext;
+ // Local map to store the data in memory for quick access
+ private final Map<String, Integer> mDataMap;
+ // Database to persist the data across reboots
+ private AppShareabilityDatabase mDatabase;
+ // Data Access Object for the database
+ private ShareabilityDao mDao;
+ // Class to perform shareability checks
+ private AppShareabilityChecker mShareChecker;
+
+ private AppShareabilityManager(Context context) {
+ mContext = context;
+ mDataMap = new ArrayMap<>();
+ mDatabase = Room.databaseBuilder(mContext, AppShareabilityDatabase.class, DB_NAME).build();
+ mDao = mDatabase.shareabilityDao();
+ MODEL_EXECUTOR.post(this::readFromDB);
+ }
+
+ /**
+ * Set the shareability checker. The checker determines whether given apps are shareable.
+ * This must be set before the manager can update its data.
+ * @param checker Implementation of AppShareabilityChecker to perform the checks
+ */
+ public void setShareabilityChecker(AppShareabilityChecker checker) {
+ mShareChecker = checker;
+ }
+
+ /**
+ * Retrieve the ShareabilityStatus of an app from the local map
+ * This does not interact with the saved database
+ * @param packageName The app's package name
+ * @return The status as a ShareabilityStatus integer
+ */
+ public synchronized @ShareabilityStatus int getStatus(String packageName) {
+ @ShareabilityStatus int status = ShareabilityStatus.UNKNOWN;
+ if (mDataMap.containsKey(packageName)) {
+ status = mDataMap.get(packageName);
+ }
+ return status;
+ }
+
+ /**
+ * Set the status of a given app. This updates the local map as well as the saved database.
+ */
+ public synchronized void setStatus(String packageName, @ShareabilityStatus int status) {
+ mDataMap.put(packageName, status);
+
+ // Write to the database on a separate thread
+ MODEL_EXECUTOR.post(() ->
+ mDao.insertAppStatus(new AppShareabilityStatus(packageName, status)));
+ }
+
+ /**
+ * Set the statuses of given apps. This updates the local map as well as the saved database.
+ */
+ public synchronized void setStatuses(List<AppShareabilityStatus> statuses) {
+ for (int i = 0, size = statuses.size(); i < size; i++) {
+ AppShareabilityStatus entry = statuses.get(i);
+ mDataMap.put(entry.packageName, entry.status);
+ }
+
+ // Write to the database on a separate thread
+ MODEL_EXECUTOR.post(() ->
+ mDao.insertAppStatuses(statuses.toArray(new AppShareabilityStatus[0])));
+ }
+
+ /**
+ * Request a status update for a specific app
+ * @param packageName The app's package name
+ * @param callback Optional callback to be called when the update is complete. The received
+ * Boolean denotes whether the update was successful.
+ */
+ public void requestAppStatusUpdate(String packageName, @Nullable Consumer<Boolean> callback) {
+ MODEL_EXECUTOR.post(() -> updateCache(packageName, callback));
+ }
+
+ /**
+ * Request a status update for all apps
+ */
+ public void requestFullUpdate() {
+ MODEL_EXECUTOR.post(this::updateCache);
+ }
+
+ /**
+ * Update the cached shareability data for all installed apps
+ */
+ @WorkerThread
+ private void updateCache() {
+ updateCache(/* packageName */ null, /* callback */ null);
+ }
+
+ /**
+ * Update the cached shareability data
+ * @param packageName A specific package to update. If null, all installed apps will be updated.
+ * @param callback Optional callback to be called when the update is complete. The received
+ * Boolean denotes whether the update was successful.
+ */
+ @WorkerThread
+ private void updateCache(@Nullable String packageName, @Nullable Consumer<Boolean> callback) {
+ if (mShareChecker == null) {
+ Log.e(TAG, "AppShareabilityChecker not set");
+ return;
+ }
+
+ List<String> packageNames = new ArrayList<>();
+ if (packageName != null) {
+ packageNames.add(packageName);
+ } else {
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ List<LauncherActivityInfo> installedApps =
+ launcherApps.getActivityList(/* packageName */ null, Process.myUserHandle());
+ for (int i = 0, size = installedApps.size(); i < size; i++) {
+ packageNames.add(installedApps.get(i).getApplicationInfo().packageName);
+ }
+ }
+
+ mShareChecker.checkApps(packageNames, this, callback);
+ }
+
+ @WorkerThread
+ private synchronized void readFromDB() {
+ mDataMap.clear();
+ List<AppShareabilityStatus> entries = mDao.getAllEntries();
+ for (int i = 0, size = entries.size(); i < size; i++) {
+ AppShareabilityStatus entry = entries.get(i);
+ mDataMap.put(entry.packageName, entry.status);
+ }
+ }
+
+ /**
+ * Provides a testable instance of this class
+ * This instance allows database queries on the main thread
+ * @hide */
+ @VisibleForTesting
+ public static AppShareabilityManager getTestInstance(Context context) {
+ AppShareabilityManager manager = new AppShareabilityManager(context);
+ manager.mDatabase.close();
+ manager.mDatabase = Room.inMemoryDatabaseBuilder(context, AppShareabilityDatabase.class)
+ .allowMainThreadQueries()
+ .build();
+ manager.mDao = manager.mDatabase.shareabilityDao();
+ return manager;
+ }
+}
diff --git a/go/quickstep/src/com/android/launcher3/model/AppShareabilityStatus.java b/go/quickstep/src/com/android/launcher3/model/AppShareabilityStatus.java
new file mode 100644
index 0000000000..61018c6b25
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/model/AppShareabilityStatus.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 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.model;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import com.android.launcher3.model.AppShareabilityManager.ShareabilityStatus;
+
+/**
+ * Database entry to hold the shareability status of a single app
+ */
+@Entity
+public class AppShareabilityStatus {
+ @PrimaryKey
+ @NonNull
+ public String packageName;
+
+ public @ShareabilityStatus int status;
+
+ public AppShareabilityStatus(@NonNull String packageName, @ShareabilityStatus int status) {
+ this.packageName = packageName;
+ this.status = status;
+ }
+}