1 /*
2  * Copyright (C) 2023 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.systemui.screenshot.appclips;
18 
19 import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
20 import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
21 import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
22 import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
23 
24 import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
25 
26 import android.app.Activity;
27 import android.app.Service;
28 import android.app.StatusBarManager;
29 import android.app.admin.DevicePolicyManager;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.Intent.CaptureContentForNoteStatusCodes;
34 import android.content.res.Resources;
35 import android.os.IBinder;
36 
37 import androidx.annotation.Nullable;
38 
39 import com.android.internal.statusbar.IAppClipsService;
40 import com.android.systemui.R;
41 import com.android.systemui.dagger.qualifiers.Application;
42 import com.android.systemui.flags.FeatureFlags;
43 import com.android.wm.shell.bubbles.Bubbles;
44 
45 import java.util.Optional;
46 
47 import javax.inject.Inject;
48 
49 /**
50  * A service that communicates with {@link StatusBarManager} to support the
51  * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} API. Also used by
52  * {@link AppClipsTrampolineActivity} to query if an app should be allowed to user App Clips.
53  *
54  * <p>Note: This service always runs in the SysUI process running on the system user irrespective of
55  * which user started the service. This is required so that the correct instance of {@link Bubbles}
56  * instance is injected. This is set via attribute {@code android:singleUser=”true”} in
57  * AndroidManifest.
58  */
59 public class AppClipsService extends Service {
60 
61     @Application private final Context mContext;
62     private final FeatureFlags mFeatureFlags;
63     private final Optional<Bubbles> mOptionalBubbles;
64     private final DevicePolicyManager mDevicePolicyManager;
65     private final boolean mAreTaskAndTimeIndependentPrerequisitesMet;
66 
67     @Inject
AppClipsService(@pplication Context context, FeatureFlags featureFlags, Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager)68     public AppClipsService(@Application Context context, FeatureFlags featureFlags,
69             Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) {
70         mContext = context;
71         mFeatureFlags = featureFlags;
72         mOptionalBubbles = optionalBubbles;
73         mDevicePolicyManager = devicePolicyManager;
74 
75         mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables();
76     }
77 
checkIndependentVariables()78     private boolean checkIndependentVariables() {
79         if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
80             return false;
81         }
82 
83         if (mOptionalBubbles.isEmpty()) {
84             return false;
85         }
86 
87         return isComponentValid();
88     }
89 
isComponentValid()90     private boolean isComponentValid() {
91         ComponentName componentName;
92         try {
93             componentName = ComponentName.unflattenFromString(
94                     mContext.getString(R.string.config_screenshotAppClipsActivityComponent));
95         } catch (Resources.NotFoundException e) {
96             return false;
97         }
98 
99         return componentName != null
100                 && !componentName.getPackageName().isEmpty()
101                 && !componentName.getClassName().isEmpty();
102     }
103 
104     @Nullable
105     @Override
onBind(Intent intent)106     public IBinder onBind(Intent intent) {
107         return new IAppClipsService.Stub() {
108             @Override
109             public boolean canLaunchCaptureContentActivityForNote(int taskId) {
110                 return canLaunchCaptureContentActivityForNoteInternal(taskId)
111                         == CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
112             }
113 
114             @Override
115             @CaptureContentForNoteStatusCodes
116             public int canLaunchCaptureContentActivityForNoteInternal(int taskId) {
117                 if (!mAreTaskAndTimeIndependentPrerequisitesMet) {
118                     return CAPTURE_CONTENT_FOR_NOTE_FAILED;
119                 }
120 
121                 if (!mOptionalBubbles.get().isAppBubbleTaskId(taskId)) {
122                     return CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
123                 }
124 
125                 return mDevicePolicyManager.getScreenCaptureDisabled(null)
126                         ? CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN
127                         : CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
128             }
129         };
130     }
131 }
132