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