9f226dff72
Instead of opening the popup again when the status of AppSharing changes, updating the UI in-place. Bug: 321179015 Test: Presubmit Flag: N/A Change-Id: I6d7bd8cdf2014fd881324347e3e01b8e7e84ef1e
252 lines
10 KiB
Java
252 lines
10 KiB
Java
/*
|
|
* 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;
|
|
|
|
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP;
|
|
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.net.Uri;
|
|
import android.os.Process;
|
|
import android.os.UserHandle;
|
|
import android.os.UserManager;
|
|
import android.provider.Settings;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
import android.widget.ImageView;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
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 com.android.launcher3.views.ActivityContext;
|
|
|
|
import java.io.File;
|
|
import java.util.Collections;
|
|
import java.util.Set;
|
|
import java.util.WeakHashMap;
|
|
|
|
/**
|
|
* Defines the Share system shortcut and its factory.
|
|
* This shortcut can be added to the app long-press menu on the home screen.
|
|
* Clicking the button will initiate peer-to-peer sharing of the app.
|
|
*/
|
|
public final class AppSharing {
|
|
/**
|
|
* This flag enables this feature. It is defined here rather than in launcher3's FeatureFlags
|
|
* 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 = true;
|
|
|
|
private static final String TAG = "AppSharing";
|
|
private static final String FILE_PROVIDER_SUFFIX = ".overview.fileprovider";
|
|
private static final String APP_EXTENSION = ".apk";
|
|
private static final String APP_MIME_TYPE = "application/application";
|
|
|
|
private final String mSharingComponent;
|
|
private AppShareabilityManager mShareabilityMgr;
|
|
|
|
private AppSharing(Launcher launcher) {
|
|
String sharingComponent = Settings.Secure.getString(launcher.getContentResolver(),
|
|
Settings.Secure.NEARBY_SHARING_COMPONENT);
|
|
mSharingComponent = TextUtils.isEmpty(sharingComponent) ? launcher.getText(
|
|
R.string.app_sharing_component).toString() : sharingComponent;
|
|
}
|
|
|
|
private Uri getShareableUri(Context context, String path, String displayName) {
|
|
String authority = BuildConfig.APPLICATION_ID + FILE_PROVIDER_SUFFIX;
|
|
File pathFile = new File(path);
|
|
return FileProvider.getUriForFile(context, authority, pathFile, displayName);
|
|
}
|
|
|
|
private SystemShortcut<Launcher> getShortcut(Launcher launcher, ItemInfo info,
|
|
View originalView) {
|
|
if (TextUtils.isEmpty(mSharingComponent)) {
|
|
return null;
|
|
}
|
|
return new Share(launcher, info, originalView);
|
|
}
|
|
|
|
/**
|
|
* 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 final PopupDataProvider mPopupDataProvider;
|
|
private final boolean mSharingEnabledForUser;
|
|
|
|
private final Set<View> mBoundViews = Collections.newSetFromMap(new WeakHashMap<>());
|
|
private boolean mIsEnabled = true;
|
|
|
|
public Share(Launcher target, ItemInfo itemInfo, View originalView) {
|
|
super(R.drawable.ic_share, R.string.app_share_drop_target_label, target, itemInfo,
|
|
originalView);
|
|
mPopupDataProvider = target.getPopupDataProvider();
|
|
|
|
mSharingEnabledForUser = bluetoothSharingEnabled(target);
|
|
if (!mSharingEnabledForUser) {
|
|
setEnabled(false);
|
|
} else if (ENABLE_SHAREABILITY_CHECK) {
|
|
mShareabilityMgr =
|
|
AppShareabilityManager.INSTANCE.get(target.getApplicationContext());
|
|
checkShareability(/* requestUpdateIfUnknown */ true);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setIconAndLabelFor(View iconView, TextView labelView) {
|
|
super.setIconAndLabelFor(iconView, labelView);
|
|
mBoundViews.add(iconView);
|
|
mBoundViews.add(labelView);
|
|
}
|
|
|
|
@Override
|
|
public void setIconAndContentDescriptionFor(ImageView view) {
|
|
super.setIconAndContentDescriptionFor(view);
|
|
mBoundViews.add(view);
|
|
}
|
|
|
|
@Override
|
|
public void onClick(View view) {
|
|
ActivityContext.lookupContext(view.getContext())
|
|
.getStatsLogManager().logger().log(LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP);
|
|
if (!mIsEnabled) {
|
|
showCannotShareToast(view.getContext());
|
|
return;
|
|
}
|
|
|
|
Intent sendIntent = new Intent();
|
|
sendIntent.setAction(Intent.ACTION_SEND);
|
|
|
|
ComponentName targetComponent = mItemInfo.getTargetComponent();
|
|
if (targetComponent == null) {
|
|
Log.e(TAG, "Item missing target component");
|
|
return;
|
|
}
|
|
String packageName = targetComponent.getPackageName();
|
|
PackageManager packageManager = view.getContext().getPackageManager();
|
|
String sourceDir, appLabel;
|
|
try {
|
|
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
|
|
sourceDir = packageInfo.applicationInfo.sourceDir;
|
|
appLabel = packageManager.getApplicationLabel(packageInfo.applicationInfo)
|
|
+ APP_EXTENSION;
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Could not find info for package \"" + packageName + "\"");
|
|
return;
|
|
}
|
|
Uri uri = getShareableUri(view.getContext(), sourceDir, appLabel);
|
|
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
|
|
sendIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
|
|
|
|
sendIntent.setType(APP_MIME_TYPE);
|
|
sendIntent.setComponent(ComponentName.unflattenFromString(mSharingComponent));
|
|
|
|
UserHandle user = mItemInfo.user;
|
|
if (user != null && !user.equals(Process.myUserHandle())) {
|
|
mTarget.startActivityAsUser(sendIntent, user);
|
|
} else {
|
|
mTarget.startActivitySafely(view, sendIntent, mItemInfo);
|
|
}
|
|
|
|
AbstractFloatingView.closeAllOpenViews(mTarget);
|
|
}
|
|
|
|
private void onStatusUpdated(boolean success) {
|
|
if (!success) {
|
|
// Something went wrong. Specific error logged in AppShareabilityManager.
|
|
return;
|
|
}
|
|
checkShareability(/* requestUpdateIfUnknown */ false);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
private boolean bluetoothSharingEnabled(Context context) {
|
|
return !context.getSystemService(UserManager.class)
|
|
.hasUserRestriction(UserManager.DISALLOW_BLUETOOTH_SHARING, mItemInfo.user);
|
|
}
|
|
|
|
private void showCannotShareToast(Context context) {
|
|
ActivityContext activityContext = ActivityContext.lookupContext(context);
|
|
String blockedByMessage = activityContext.getStringCache() != null
|
|
? activityContext.getStringCache().disabledByAdminMessage
|
|
: context.getString(R.string.blocked_by_policy);
|
|
|
|
CharSequence text = (mSharingEnabledForUser)
|
|
? context.getText(R.string.toast_p2p_app_not_shareable)
|
|
: blockedByMessage;
|
|
int duration = Toast.LENGTH_SHORT;
|
|
Toast.makeText(context, text, duration).show();
|
|
}
|
|
|
|
public void setEnabled(boolean isEnabled) {
|
|
if (mIsEnabled != isEnabled) {
|
|
mIsEnabled = isEnabled;
|
|
mBoundViews.forEach(v -> v.setEnabled(isEnabled));
|
|
}
|
|
}
|
|
|
|
public boolean isEnabled() {
|
|
return mIsEnabled;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shortcut factory for generating the Share App button
|
|
*/
|
|
public static final SystemShortcut.Factory<Launcher> SHORTCUT_FACTORY =
|
|
(launcher, itemInfo, originalView) ->
|
|
(new AppSharing(launcher)).getShortcut(launcher, itemInfo, originalView);
|
|
}
|