diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java new file mode 100644 index 0000000000000..f7ea7875c8dfc --- /dev/null +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -0,0 +1,128 @@ +package com.android.internal.util; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +public class ScreenshotHelper { + private static final String TAG = "ScreenshotHelper"; + + private static final String SYSUI_PACKAGE = "com.android.systemui"; + private static final String SYSUI_SCREENSHOT_SERVICE = + "com.android.systemui.screenshot.TakeScreenshotService"; + private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER = + "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver"; + + // Time until we give up on the screenshot & show an error instead. + private final int SCREENSHOT_TIMEOUT_MS = 10000; + + private final Object mScreenshotLock = new Object(); + private ServiceConnection mScreenshotConnection = null; + private final Context mContext; + + public ScreenshotHelper(Context context) { + mContext = context; + } + + public void takeScreenshot(final int screenshotType, final boolean hasStatus, + final boolean hasNav, Handler handler) { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + return; + } + final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE, + SYSUI_SCREENSHOT_SERVICE); + final Intent serviceIntent = new Intent(); + + final Runnable mScreenshotTimeout = new Runnable() { + @Override public void run() { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + notifyScreenshotError(); + } + } + } + }; + + serviceIntent.setComponent(serviceComponent); + ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != this) { + return; + } + Messenger messenger = new Messenger(service); + Message msg = Message.obtain(null, screenshotType); + final ServiceConnection myConn = this; + Handler h = new Handler(handler.getLooper()) { + @Override + public void handleMessage(Message msg) { + synchronized (mScreenshotLock) { + if (mScreenshotConnection == myConn) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + handler.removeCallbacks(mScreenshotTimeout); + } + } + } + }; + msg.replyTo = new Messenger(h); + msg.arg1 = hasStatus ? 1: 0; + msg.arg2 = hasNav ? 1: 0; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't take screenshot: " + e); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + handler.removeCallbacks(mScreenshotTimeout); + notifyScreenshotError(); + } + } + } + }; + if (mContext.bindServiceAsUser(serviceIntent, conn, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, + UserHandle.CURRENT)) { + mScreenshotConnection = conn; + handler.postDelayed(mScreenshotTimeout, SCREENSHOT_TIMEOUT_MS); + } + } + } + + /** + * Notifies the screenshot service to show an error. + */ + private void notifyScreenshotError() { + // If the service process is killed, then ask it to clean up after itself + final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE, + SYSUI_SCREENSHOT_ERROR_RECEIVER); + // Broadcast needs to have a valid action. We'll just pick + // a generic one, since the receiver here doesn't care. + Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT); + errorIntent.setComponent(errorComponent); + errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | + Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT); + } + +} diff --git a/core/res/res/drawable/ic_screenshot.xml b/core/res/res/drawable/ic_screenshot.xml new file mode 100644 index 0000000000000..3074b28497cf0 --- /dev/null +++ b/core/res/res/drawable/ic_screenshot.xml @@ -0,0 +1,33 @@ + + + + + + + diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f1e9662a54581..ed94f8471130d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2481,6 +2481,7 @@ power restart + screenshot logout bugreport users diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f671b6b8382f7..b2fa294f77be2 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -484,6 +484,9 @@ End session + + Screenshot + Take bug report @@ -4800,13 +4803,10 @@ Extreme battery saver - Uninstall Launch anyway Uninstall harmful app? - - diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 0dad20bbce58a..1251c11c4daa2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1722,6 +1722,7 @@ + @@ -2889,8 +2890,9 @@ - + + diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index a19917d916673..6ff239ebd2ee0 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -173,22 +173,25 @@ [CHAR LIMIT=25] --> Stretch to fill screen + + Screenshot + Saving screenshot\u2026 Saving screenshot\u2026 - Screenshot is being saved. + Screenshot is being saved - Screenshot captured. + Screenshot saved - Tap to view your screenshot. + Tap to view your screenshot - Couldn\'t capture screenshot. + Couldn\'t capture screenshot - Problem encountered while saving screenshot. + Problem encountered while saving screenshot - Can\'t save screenshot due to limited storage space. + Can\'t save screenshot due to limited storage space Taking screenshots isn\'t allowed by the app or your organization @@ -2038,4 +2041,5 @@ Because an app is obscuring a permission request, Settings can’t verify your response. + diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index e008148a25dea..0f34513bc40fb 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -24,10 +24,12 @@ import android.app.KeyguardManager; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.graphics.Point; @@ -36,7 +38,9 @@ import android.media.AudioManager; import android.net.ConnectivityManager; import android.os.Build; import android.os.Handler; +import android.os.IBinder; import android.os.Message; +import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -77,6 +81,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyProperties; import com.android.internal.util.EmergencyAffordanceManager; +import com.android.internal.util.ScreenshotHelper; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dependency; import com.android.systemui.HardwareUiLayout; @@ -117,6 +122,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; private static final String GLOBAL_ACTION_KEY_RESTART = "restart"; private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout"; + private static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; private final Context mContext; private final GlobalActionsManager mWindowManagerFuncs; @@ -143,6 +149,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, private boolean mHasLogoutButton; private final boolean mShowSilentToggle; private final EmergencyAffordanceManager mEmergencyAffordanceManager; + private final ScreenshotHelper mScreenshotHelper; /** * @param context everything needs a context :( @@ -183,6 +190,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, R.bool.config_useFixedVolume); mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); + mScreenshotHelper = new ScreenshotHelper(context); } /** @@ -340,6 +348,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, mItems.add(getAssistAction()); } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { mItems.add(new RestartAction()); + } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) { + mItems.add(new ScreenshotAction()); } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) { if (mDevicePolicyManager.isLogoutEnabled() && getCurrentUser().id != UserHandle.USER_SYSTEM) { @@ -458,6 +468,38 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, } + private class ScreenshotAction extends SinglePressAction { + public ScreenshotAction() { + super(R.drawable.ic_screenshot, R.string.global_action_screenshot); + } + + @Override + public void onPress() { + // Add a little delay before executing, to give the + // dialog a chance to go away before it takes a + // screenshot. + // TODO: instead, omit global action dialog layer + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mScreenshotHelper.takeScreenshot(1, true, true, mHandler); + MetricsLogger.action(mContext, + MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); + } + }, 500); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + } + private class BugReportAction extends SinglePressAction implements LongPressAction { public BugReportAction() { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index f3bae20e544aa..34b8bfe59e7ee 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -67,6 +67,8 @@ public class TakeScreenshotService extends Service { case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0); break; + default: + Log.d(TAG, "Invalid screenshot option: " + msg.what); } } }; diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 4f04d36ba2fcd..10a809aaae08c 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5129,6 +5129,12 @@ message MetricsEvent { // OS: P FUELGAUGE_SMART_BATTERY = 1281; + // ACTION: User tapped Screenshot in the power menu. + // CATEGORY: GLOBAL_SYSTEM_UI + // OS: P + ACTION_SCREENSHOT_POWER_MENU = 1282; + + // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index a1852fe501e3b..31dd67311aab3 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -269,6 +269,7 @@ import com.android.internal.policy.IShortcutService; import com.android.internal.policy.PhoneWindow; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.ScreenshotHelper; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.widget.PointerLocationView; import com.android.server.GestureLauncherService; @@ -460,6 +461,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { AccessibilityManager mAccessibilityManager; BurnInProtectionHelper mBurnInProtectionHelper; AppOpsManager mAppOpsManager; + private ScreenshotHelper mScreenshotHelper; private boolean mHasFeatureWatch; private boolean mHasFeatureLeanback; @@ -1713,7 +1715,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void run() { - takeScreenshot(mScreenshotType); + mScreenshotHelper.takeScreenshot(mScreenshotType, + mStatusBar != null && mStatusBar.isVisibleLw(), + mNavigationBar != null && mNavigationBar.isVisibleLw(), mHandler); } } @@ -2191,6 +2195,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerFuncs.notifyKeyguardTrustedChanged(); } }); + mScreenshotHelper = new ScreenshotHelper(mContext); } /** @@ -5766,100 +5771,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { setHdmiPlugged(!mHdmiPlugged); } - final Object mScreenshotLock = new Object(); - ServiceConnection mScreenshotConnection = null; - - final Runnable mScreenshotTimeout = new Runnable() { - @Override public void run() { - synchronized (mScreenshotLock) { - if (mScreenshotConnection != null) { - mContext.unbindService(mScreenshotConnection); - mScreenshotConnection = null; - notifyScreenshotError(); - } - } - } - }; - - // Assume this is called from the Handler thread. - private void takeScreenshot(final int screenshotType) { - synchronized (mScreenshotLock) { - if (mScreenshotConnection != null) { - return; - } - final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE, - SYSUI_SCREENSHOT_SERVICE); - final Intent serviceIntent = new Intent(); - serviceIntent.setComponent(serviceComponent); - ServiceConnection conn = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mScreenshotLock) { - if (mScreenshotConnection != this) { - return; - } - Messenger messenger = new Messenger(service); - Message msg = Message.obtain(null, screenshotType); - final ServiceConnection myConn = this; - Handler h = new Handler(mHandler.getLooper()) { - @Override - public void handleMessage(Message msg) { - synchronized (mScreenshotLock) { - if (mScreenshotConnection == myConn) { - mContext.unbindService(mScreenshotConnection); - mScreenshotConnection = null; - mHandler.removeCallbacks(mScreenshotTimeout); - } - } - } - }; - msg.replyTo = new Messenger(h); - msg.arg1 = msg.arg2 = 0; - if (mStatusBar != null && mStatusBar.isVisibleLw()) - msg.arg1 = 1; - if (mNavigationBar != null && mNavigationBar.isVisibleLw()) - msg.arg2 = 1; - try { - messenger.send(msg); - } catch (RemoteException e) { - } - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - synchronized (mScreenshotLock) { - if (mScreenshotConnection != null) { - mContext.unbindService(mScreenshotConnection); - mScreenshotConnection = null; - mHandler.removeCallbacks(mScreenshotTimeout); - notifyScreenshotError(); - } - } - } - }; - if (mContext.bindServiceAsUser(serviceIntent, conn, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, - UserHandle.CURRENT)) { - mScreenshotConnection = conn; - mHandler.postDelayed(mScreenshotTimeout, 10000); - } - } - } - - /** - * Notifies the screenshot service to show an error. - */ - private void notifyScreenshotError() { - // If the service process is killed, then ask it to clean up after itself - final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE, - SYSUI_SCREENSHOT_ERROR_RECEIVER); - Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT); - errorIntent.setComponent(errorComponent); - errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | - Intent.FLAG_RECEIVER_FOREGROUND); - mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT); - } /** {@inheritDoc} */ @Override