• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3;
18 
19 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP;
20 
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.net.Uri;
27 import android.os.Process;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.provider.Settings;
31 import android.text.TextUtils;
32 import android.util.Log;
33 import android.view.View;
34 import android.widget.ImageView;
35 import android.widget.TextView;
36 import android.widget.Toast;
37 
38 import androidx.core.content.FileProvider;
39 
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.launcher3.logging.StatsLogManager;
42 import com.android.launcher3.model.AppShareabilityChecker;
43 import com.android.launcher3.model.AppShareabilityJobService;
44 import com.android.launcher3.model.AppShareabilityManager;
45 import com.android.launcher3.model.AppShareabilityManager.ShareabilityStatus;
46 import com.android.launcher3.model.data.ItemInfo;
47 import com.android.launcher3.popup.SystemShortcut;
48 import com.android.launcher3.views.ActivityContext;
49 
50 import java.io.File;
51 import java.util.Collections;
52 import java.util.Set;
53 import java.util.WeakHashMap;
54 
55 /**
56  * Defines the Share system shortcut and its factory.
57  * This shortcut can be added to the app long-press menu on the home screen.
58  * Clicking the button will initiate peer-to-peer sharing of the app.
59  */
60 public final class AppSharing {
61     /**
62      * This flag enables this feature. It is defined here rather than in launcher3's FeatureFlags
63      * because it is unique to Go and not toggleable at runtime.
64      */
65     public static final boolean ENABLE_APP_SHARING = true;
66     /**
67      * With this flag enabled, the Share App button will be dynamically enabled/disabled based
68      * on each app's shareability status.
69      */
70     public static final boolean ENABLE_SHAREABILITY_CHECK = true;
71 
72     private static final String TAG = "AppSharing";
73     private static final String FILE_PROVIDER_SUFFIX = ".overview.fileprovider";
74     private static final String APP_EXTENSION = ".apk";
75     private static final String APP_MIME_TYPE = "application/application";
76 
77     private final String mSharingComponent;
78     private AppShareabilityManager mShareabilityMgr;
79 
AppSharing(Launcher launcher)80     private AppSharing(Launcher launcher) {
81         String sharingComponent = Settings.Secure.getString(launcher.getContentResolver(),
82                 Settings.Secure.NEARBY_SHARING_COMPONENT);
83         mSharingComponent = TextUtils.isEmpty(sharingComponent) ? launcher.getText(
84                 R.string.app_sharing_component).toString() : sharingComponent;
85     }
86 
getShareableUri(Context context, String path, String displayName)87     private Uri getShareableUri(Context context, String path, String displayName) {
88         String authority = BuildConfig.APPLICATION_ID + FILE_PROVIDER_SUFFIX;
89         File pathFile = new File(path);
90         return FileProvider.getUriForFile(context, authority, pathFile, displayName);
91     }
92 
getShortcut(Launcher launcher, ItemInfo info, View originalView)93     private SystemShortcut<Launcher> getShortcut(Launcher launcher, ItemInfo info,
94             View originalView) {
95         if (TextUtils.isEmpty(mSharingComponent)) {
96             return null;
97         }
98         return new Share(launcher, info, originalView);
99     }
100 
101     /**
102      * Instantiates AppShareabilityManager, which then reads app shareability data from disk
103      * Also schedules a job to update those data
104      * @param context The application context
105      * @param checker An implementation of AppShareabilityChecker to perform the actual checks
106      *                when updating the data
107      */
setUpShareabilityCache(Context context, AppShareabilityChecker checker)108     public static void setUpShareabilityCache(Context context, AppShareabilityChecker checker) {
109         AppShareabilityManager shareMgr = AppShareabilityManager.INSTANCE.get(context);
110         shareMgr.setShareabilityChecker(checker);
111         AppShareabilityJobService.schedule(context);
112     }
113 
114     /**
115      * The Share App system shortcut, used to initiate p2p sharing of a given app
116      */
117     public final class Share extends SystemShortcut<Launcher> {
118         private final boolean mSharingEnabledForUser;
119 
120         private final Set<View> mBoundViews = Collections.newSetFromMap(new WeakHashMap<>());
121         private boolean mIsEnabled = true;
122         private StatsLogManager mStatsLogManager;
123 
Share(Launcher target, ItemInfo itemInfo, View originalView)124         public Share(Launcher target, ItemInfo itemInfo, View originalView) {
125             super(R.drawable.ic_share, R.string.app_share_drop_target_label, target, itemInfo,
126                     originalView);
127             mStatsLogManager = ActivityContext.lookupContext(originalView.getContext())
128                     .getStatsLogManager();
129             mSharingEnabledForUser = bluetoothSharingEnabled(target);
130             if (!mSharingEnabledForUser) {
131                 setEnabled(false);
132             } else if (ENABLE_SHAREABILITY_CHECK) {
133                 mShareabilityMgr =
134                         AppShareabilityManager.INSTANCE.get(target.getApplicationContext());
135                 checkShareability(/* requestUpdateIfUnknown */ true);
136             }
137         }
138 
139         @Override
setIconAndLabelFor(View iconView, TextView labelView)140         public void setIconAndLabelFor(View iconView, TextView labelView) {
141             super.setIconAndLabelFor(iconView, labelView);
142             mBoundViews.add(iconView);
143             mBoundViews.add(labelView);
144         }
145 
146         @Override
setIconAndContentDescriptionFor(ImageView view)147         public void setIconAndContentDescriptionFor(ImageView view) {
148             super.setIconAndContentDescriptionFor(view);
149             mBoundViews.add(view);
150         }
151 
152         @Override
onClick(View view)153         public void onClick(View view) {
154             mStatsLogManager.logger().log(LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP);
155             if (!mIsEnabled) {
156                 showCannotShareToast(view.getContext());
157                 return;
158             }
159 
160             Intent sendIntent = new Intent();
161             sendIntent.setAction(Intent.ACTION_SEND);
162 
163             ComponentName targetComponent = mItemInfo.getTargetComponent();
164             if (targetComponent == null) {
165                 Log.e(TAG, "Item missing target component");
166                 return;
167             }
168             String packageName = targetComponent.getPackageName();
169             PackageManager packageManager = view.getContext().getPackageManager();
170             String sourceDir, appLabel;
171             try {
172                 PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
173                 sourceDir = packageInfo.applicationInfo.sourceDir;
174                 appLabel = packageManager.getApplicationLabel(packageInfo.applicationInfo)
175                         + APP_EXTENSION;
176             } catch (Exception e) {
177                 Log.e(TAG, "Could not find info for package \"" + packageName + "\"");
178                 return;
179             }
180             Uri uri = getShareableUri(view.getContext(), sourceDir, appLabel);
181             sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
182             sendIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
183 
184             sendIntent.setType(APP_MIME_TYPE);
185             sendIntent.setComponent(ComponentName.unflattenFromString(mSharingComponent));
186 
187             UserHandle user = mItemInfo.user;
188             if (user != null && !user.equals(Process.myUserHandle())) {
189                 mTarget.startActivityAsUser(sendIntent, user);
190             } else {
191                 mTarget.startActivitySafely(view, sendIntent, mItemInfo);
192             }
193 
194             AbstractFloatingView.closeAllOpenViews(mTarget);
195         }
196 
onStatusUpdated(boolean success)197         private void onStatusUpdated(boolean success) {
198             if (!success) {
199                 // Something went wrong. Specific error logged in AppShareabilityManager.
200                 return;
201             }
202             checkShareability(/* requestUpdateIfUnknown */ false);
203         }
204 
checkShareability(boolean requestUpdateIfUnknown)205         private void checkShareability(boolean requestUpdateIfUnknown) {
206             String packageName = mItemInfo.getTargetComponent().getPackageName();
207             @ShareabilityStatus int status = mShareabilityMgr.getStatus(packageName);
208             setEnabled(status == ShareabilityStatus.SHAREABLE);
209 
210             if (requestUpdateIfUnknown && status == ShareabilityStatus.UNKNOWN) {
211                 mShareabilityMgr.requestAppStatusUpdate(packageName, this::onStatusUpdated);
212             }
213         }
214 
bluetoothSharingEnabled(Context context)215         private boolean bluetoothSharingEnabled(Context context) {
216             return !context.getSystemService(UserManager.class)
217                     .hasUserRestriction(UserManager.DISALLOW_BLUETOOTH_SHARING, mItemInfo.user);
218         }
219 
showCannotShareToast(Context context)220         private void showCannotShareToast(Context context) {
221             ActivityContext activityContext = ActivityContext.lookupContext(context);
222             String blockedByMessage = activityContext.getStringCache() != null
223                     ? activityContext.getStringCache().disabledByAdminMessage
224                     : context.getString(R.string.blocked_by_policy);
225 
226             CharSequence text = (mSharingEnabledForUser)
227                     ? context.getText(R.string.toast_p2p_app_not_shareable)
228                     : blockedByMessage;
229             int duration = Toast.LENGTH_SHORT;
230             Toast.makeText(context, text, duration).show();
231         }
232 
setEnabled(boolean isEnabled)233         public void setEnabled(boolean isEnabled) {
234             if (mIsEnabled != isEnabled) {
235                 mIsEnabled = isEnabled;
236                 mBoundViews.forEach(v -> v.setEnabled(isEnabled));
237             }
238         }
239 
isEnabled()240         public boolean isEnabled() {
241             return mIsEnabled;
242         }
243 
244         @VisibleForTesting
setStatsLogManager(StatsLogManager statsLogManager)245         void setStatsLogManager(StatsLogManager statsLogManager) {
246             mStatsLogManager = statsLogManager;
247         }
248     }
249 
250     /**
251      * Shortcut factory for generating the Share App button
252      */
253     public static final SystemShortcut.Factory<Launcher> SHORTCUT_FACTORY =
254             (launcher, itemInfo, originalView) ->
255                     (new AppSharing(launcher)).getShortcut(launcher, itemInfo, originalView);
256 }
257