import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.SystemClock;
import android.service.voice.IVoiceInteractionSession;
import android.util.SparseIntArray;
*
* @param reasons A map from stack id to a reason integer why the transition was started,, which
* must be one of the APP_TRANSITION_* values.
+ * @param timestamp The time at which the app transition started in
+ * {@link SystemClock#uptimeMillis()} timebase.
*/
- public abstract void notifyAppTransitionStarting(SparseIntArray reasons);
+ public abstract void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp);
/**
* Callback for window manager to let activity manager know that the app transition was
}
/**
+ * Return whether there are any messages or callbacks currently scheduled on this handler.
+ * @hide
+ */
+ public final boolean hasMessagesOrCallbacks() {
+ return mQueue.hasMessages(this);
+ }
+
+ /**
* Check if there are any pending posts of messages with code 'what' and
* whose obj is 'object' in the message queue.
*/
}
}
+ /**
+ * @hide
+ */
+ public final void dumpMine(Printer pw, String prefix) {
+ pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());
+ if (mLooper == null) {
+ pw.println(prefix + "looper uninitialized");
+ } else {
+ mLooper.dump(pw, prefix + " ", this);
+ }
+ }
+
@Override
public String toString() {
return "Handler (" + getClass().getName() + ") {"
*/
public void dump(@NonNull Printer pw, @NonNull String prefix) {
pw.println(prefix + toString());
- mQueue.dump(pw, prefix + " ");
+ mQueue.dump(pw, prefix + " ", null);
+ }
+
+ /**
+ * Dumps the state of the looper for debugging purposes.
+ *
+ * @param pw A printer to receive the contents of the dump.
+ * @param prefix A prefix to prepend to each line which is printed.
+ * @param handler Only dump messages for this Handler.
+ * @hide
+ */
+ public void dump(@NonNull Printer pw, @NonNull String prefix, Handler handler) {
+ pw.println(prefix + toString());
+ mQueue.dump(pw, prefix + " ", handler);
}
/** @hide */
}
}
+ boolean hasMessages(Handler h) {
+ if (h == null) {
+ return false;
+ }
+
+ synchronized (this) {
+ Message p = mMessages;
+ while (p != null) {
+ if (p.target == h) {
+ return true;
+ }
+ p = p.next;
+ }
+ return false;
+ }
+ }
+
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
}
}
- void dump(Printer pw, String prefix) {
+ void dump(Printer pw, String prefix, Handler h) {
synchronized (this) {
long now = SystemClock.uptimeMillis();
int n = 0;
for (Message msg = mMessages; msg != null; msg = msg.next) {
- pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+ if (h == null || h == msg.target) {
+ pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+ }
n++;
}
pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
* <pre>
* max_cached_processes (int)
* background_settle_time (long)
- * foreground_service_ui_min_time (long)
+ * fgservice_min_shown_time (long)
+ * fgservice_min_report_time (long)
+ * fgservice_screen_on_before_time (long)
+ * fgservice_screen_on_after_time (long)
* content_provider_retain_time (long)
* gc_timeout (long)
* gc_min_interval (long)
}
void JNIImageReaderContext::returnBufferItem(BufferItem* buffer) {
+ buffer->mGraphicBuffer = nullptr;
mBuffers.push_back(buffer);
}
private static final String XMLTAG_TIMEZONE = "timezone";
public static CharSequence getTimeZoneOffsetAndName(Context context, TimeZone tz, Date now) {
- final Locale locale = Locale.getDefault();
- final CharSequence gmtText = getGmtOffsetText(context, locale, tz, now);
- final TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
- final ZoneGetterData data = new ZoneGetterData(context);
-
- final boolean useExemplarLocationForLocalNames =
- shouldUseExemplarLocationForLocalNames(data, timeZoneNames);
- final CharSequence zoneName = getTimeZoneDisplayName(data, timeZoneNames,
- useExemplarLocationForLocalNames, tz, tz.getID());
- if (zoneName == null) {
+ Locale locale = Locale.getDefault();
+ CharSequence gmtText = getGmtOffsetText(context, locale, tz, now);
+ TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
+ String zoneNameString = getZoneLongName(timeZoneNames, tz, now);
+ if (zoneNameString == null) {
return gmtText;
}
// We don't use punctuation here to avoid having to worry about localizing that too!
- return TextUtils.concat(gmtText, " ", zoneName);
+ return TextUtils.concat(gmtText, " ", zoneNameString);
}
public static List<Map<String, Object>> getZonesList(Context context) {
@VisibleForTesting
Scanner mScanner;
+ private boolean mStaleScanResults = false;
public WifiTracker(Context context, WifiListener wifiListener,
boolean includeSaved, boolean includeScans) {
* Stop tracking wifi networks and scores.
*
* <p>This should always be called when done with a WifiTracker (if startTracking was called) to
- * ensure proper cleanup and prevent any further callbacks from occuring.
+ * ensure proper cleanup and prevent any further callbacks from occurring.
+ *
+ * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
+ * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
+ * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
*/
@MainThread
public void stopTracking() {
mWorkHandler.removePendingMessages();
mMainHandler.removePendingMessages();
}
+ mStaleScanResults = true;
}
private void unregisterAndClearScoreCache() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
+
+ if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
+ mStaleScanResults = false;
+ }
+
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN));
switch (msg.what) {
case MSG_UPDATE_ACCESS_POINTS:
- updateAccessPointsLocked();
+ if (!mStaleScanResults) {
+ updateAccessPointsLocked();
+ }
break;
case MSG_UPDATE_NETWORK_INFO:
updateNetworkInfo((NetworkInfo) msg.obj);
}
@Test
- public void getTimeZoneOffsetAndName_setLondon_returnLondon() {
- // Check it will ends with 'London', not 'British Summer Time' or sth else
- testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "London");
+ public void getTimeZoneOffsetAndName_setLondon_returnBritishSummerTime() {
+ // Check it will ends with 'British Summer Time', not 'London' or sth else
+ testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "British Summer Time");
}
@Test
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
verifyNoMoreInteractions(mockWifiListener);
}
+
+ @Test
+ public void stopTrackingShouldSetStaleBitWhichPreventsCallbacksUntilNextScanResult()
+ throws Exception {
+ WifiTracker tracker = createMockedWifiTracker();
+ startTracking(tracker);
+ tracker.stopTracking();
+
+ CountDownLatch latch1 = new CountDownLatch(1);
+ tracker.mMainHandler.post(() -> {
+ latch1.countDown();
+ });
+ assertTrue("Latch 1 timed out", latch1.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+
+ startTracking(tracker);
+
+ tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
+ tracker.mReceiver.onReceive(
+ mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION));
+ tracker.mReceiver.onReceive(
+ mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION));
+
+ CountDownLatch latch2 = new CountDownLatch(1);
+ tracker.mMainHandler.post(() -> {
+ latch2.countDown();
+ });
+ assertTrue("Latch 2 timed out", latch2.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+
+ verify(mockWifiListener, never()).onAccessPointsChanged();
+
+ sendScanResultsAndProcess(tracker); // verifies onAccessPointsChanged is invoked
+ }
}
<com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/full_button"
- android:layout_width="100dp"
+ android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content"
android:src="@drawable/ic_fullscreen_white_24dp"
android:text="@string/pip_fullscreen" />
<com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/close_button"
- android:layout_width="100dp"
+ android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content"
- android:layout_marginStart="-50dp"
+ android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
android:src="@drawable/ic_close_white"
android:text="@string/pip_close" />
<com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/play_pause_button"
- android:layout_width="100dp"
+ android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content"
- android:layout_marginStart="-50dp"
+ android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
android:src="@drawable/ic_pause_white"
android:text="@string/pip_pause"
android:visibility="gone" />
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2017, 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.
+*/
+-->
+<com.android.systemui.pip.tv.PipControlButtonView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/picture_in_picture_button_width"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" />
<fraction name="battery_subpixel_smoothing_right">10%</fraction>
<dimen name="battery_margin_bottom">1px</dimen>
+
+ <!-- The dimensions to user for picture-in-picture action buttons. -->
+ <dimen name="picture_in_picture_button_width">100dp</dimen>
+ <dimen name="picture_in_picture_button_start_margin">-50dp</dimen>
</resources>
} else {
actionsContainer.setVisibility(View.VISIBLE);
if (mActionsGroup != null) {
- // Hide extra views
- for (int i = mActions.size(); i < mActionsGroup.getChildCount(); i++) {
- mActionsGroup.getChildAt(i).setVisibility(View.GONE);
- }
- // Add needed views
+ // Ensure we have as many buttons as actions
final LayoutInflater inflater = LayoutInflater.from(this);
while (mActionsGroup.getChildCount() < mActions.size()) {
final ImageView actionView = (ImageView) inflater.inflate(
mActionsGroup.addView(actionView);
}
+ // Update the visibility of all views
+ for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+ mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
+ ? View.VISIBLE
+ : View.GONE);
+ }
+
// Recreate the layout
final boolean isLandscapePip = stackBounds != null &&
(stackBounds.width() > stackBounds.height());
Log.w(TAG, "Failed to send action", e);
}
});
- } else {
- actionView.setAlpha(DISABLED_ACTION_ALPHA);
}
actionView.setEnabled(action.isEnabled());
+ actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
// Update the margin between actions
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
import android.animation.AnimatorInflater;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
* A view containing PIP controls including fullscreen, close, and media controls.
*/
public class PipControlButtonView extends RelativeLayout {
+
private OnFocusChangeListener mFocusChangeListener;
private ImageView mIconImageView;
ImageView mButtonImageView;
}
/**
+ * Sets the drawable for the button with the given drawable.
+ */
+ public void setImageDrawable(Drawable d) {
+ mIconImageView.setImageDrawable(d);
+ }
+
+ /**
* Sets the drawable for the button with the given resource id.
*/
public void setImageResource(int resId) {
- mIconImageView.setImageResource(resId);
+ if (resId != 0) {
+ mIconImageView.setImageResource(resId);
+ }
+ }
+
+ /**
+ * Sets the text for description the with the given string.
+ */
+ public void setText(CharSequence text) {
+ mButtonImageView.setContentDescription(text);
+ mDescriptionTextView.setText(text);
}
/**
* Sets the text for description the with the given resource id.
*/
public void setText(int resId) {
- mButtonImageView.setContentDescription(getContext().getString(resId));
- mDescriptionTextView.setText(resId);
+ if (resId != 0) {
+ mButtonImageView.setContentDescription(getContext().getString(resId));
+ mDescriptionTextView.setText(resId);
+ }
}
private static void cancelAnimator(Animator animator) {
package com.android.systemui.pip.tv;
+import android.app.ActivityManager;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteAction;
import android.content.Context;
+import android.graphics.Color;
import android.media.session.MediaController;
import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.View;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.util.AttributeSet;
import static android.media.session.PlaybackState.ACTION_PAUSE;
import static android.media.session.PlaybackState.ACTION_PLAY;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A view containing PIP controls including fullscreen, close, and media controls.
*/
public class PipControlsView extends LinearLayout {
+
+ private static final String TAG = PipControlsView.class.getSimpleName();
+
+ private static final float DISABLED_ACTION_ALPHA = 0.54f;
+
/**
* An interface to listen user action.
*/
private MediaController mMediaController;
- final PipManager mPipManager = PipManager.getInstance();
- Listener mListener;
+ private final PipManager mPipManager = PipManager.getInstance();
+ private final LayoutInflater mLayoutInflater;
+ private final Handler mHandler;
+ private Listener mListener;
private PipControlButtonView mFullButtonView;
private PipControlButtonView mCloseButtonView;
private PipControlButtonView mPlayPauseButtonView;
+ private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>();
+ private List<RemoteAction> mCustomActions = new ArrayList<>();
private PipControlButtonView mFocusedChild;
private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
- updatePlayPauseView();
+ updateUserActions();
}
};
public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- LayoutInflater inflater = (LayoutInflater) getContext()
+ mLayoutInflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.tv_pip_controls, this);
+ mLayoutInflater.inflate(R.layout.tv_pip_controls, this);
+ mHandler = new Handler();
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
if (mMediaController != null) {
mMediaController.registerCallback(mMediaControllerCallback);
}
- updatePlayPauseView();
+ updateUserActions();
}
- private void updatePlayPauseView() {
- int state = mPipManager.getPlaybackState();
- if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
+ /**
+ * Updates the actions for the PIP. If there are no custom actions, then the media session
+ * actions are shown.
+ */
+ private void updateUserActions() {
+ if (!mCustomActions.isEmpty()) {
+ // Ensure we have as many buttons as actions
+ while (mCustomButtonViews.size() < mCustomActions.size()) {
+ PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate(
+ R.layout.tv_pip_custom_control, this, false);
+ addView(buttonView);
+ mCustomButtonViews.add(buttonView);
+ }
+
+ // Update the visibility of all views
+ for (int i = 0; i < mCustomButtonViews.size(); i++) {
+ mCustomButtonViews.get(i).setVisibility(i < mCustomActions.size()
+ ? View.VISIBLE
+ : View.GONE);
+ }
+
+ // Update the state and visibility of the action buttons, and hide the rest
+ for (int i = 0; i < mCustomActions.size(); i++) {
+ final RemoteAction action = mCustomActions.get(i);
+ PipControlButtonView actionView = mCustomButtonViews.get(i);
+
+ // TODO: Check if the action drawable has changed before we reload it
+ action.getIcon().loadDrawableAsync(getContext(), d -> {
+ d.setTint(Color.WHITE);
+ actionView.setImageDrawable(d);
+ }, mHandler);
+ actionView.setText(action.getContentDescription());
+ if (action.isEnabled()) {
+ actionView.setOnClickListener(v -> {
+ try {
+ action.getActionIntent().send();
+ } catch (CanceledException e) {
+ Log.w(TAG, "Failed to send action", e);
+ }
+ });
+ }
+ actionView.setEnabled(action.isEnabled());
+ actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
+ }
+
+ // Hide the media session buttons
mPlayPauseButtonView.setVisibility(View.GONE);
} else {
- mPlayPauseButtonView.setVisibility(View.VISIBLE);
- if (state == PipManager.PLAYBACK_STATE_PLAYING) {
- mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
- mPlayPauseButtonView.setText(R.string.pip_pause);
+ int state = mPipManager.getPlaybackState();
+ if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
+ mPlayPauseButtonView.setVisibility(View.GONE);
} else {
- mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
- mPlayPauseButtonView.setText(R.string.pip_play);
+ mPlayPauseButtonView.setVisibility(View.VISIBLE);
+ if (state == PipManager.PLAYBACK_STATE_PLAYING) {
+ mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
+ mPlayPauseButtonView.setText(R.string.pip_pause);
+ } else {
+ mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
+ mPlayPauseButtonView.setText(R.string.pip_play);
+ }
+ }
+
+ // Hide all the custom action buttons
+ for (int i = 0; i < mCustomButtonViews.size(); i++) {
+ mCustomButtonViews.get(i).setVisibility(View.GONE);
}
}
}
mCloseButtonView.reset();
mPlayPauseButtonView.reset();
mFullButtonView.requestFocus();
+ for (int i = 0; i < mCustomButtonViews.size(); i++) {
+ mCustomButtonViews.get(i).reset();
+ }
}
/**
}
/**
+ * Updates the set of activity-defined actions.
+ */
+ public void setActions(List<RemoteAction> actions) {
+ mCustomActions.clear();
+ mCustomActions.addAll(actions);
+ updateUserActions();
+ }
+
+ /**
* Returns the focused control button view to animate focused button.
*/
PipControlButtonView getFocusedButton() {
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
+import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
private MediaController mPipMediaController;
private String[] mLastPackagesResourceGranted;
private PipNotification mPipNotification;
+ private ParceledListSlice mCustomActions;
private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
}
@Override
- public void onActionsChanged(ParceledListSlice actions) {}
+ public void onActionsChanged(ParceledListSlice actions) {
+ mCustomActions = actions;
+ mHandler.post(() -> {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onPipMenuActionsChanged(mCustomActions);
+ }
+ });
+ }
}
private PipManager() { }
}
Intent intent = new Intent(mContext, PipMenuActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions);
mContext.startActivity(intent);
}
void onPipActivityClosed();
/** Invoked when the PIP menu gets shown. */
void onShowPipMenu();
+ /** Invoked when the PIP menu actions change. */
+ void onPipMenuActionsChanged(ParceledListSlice actions);
/** Invoked when the PIPed activity is about to return back to the fullscreen. */
void onMoveToFullscreen();
/** Invoked when we are above to start resizing the Pip. */
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import android.view.View;
import com.android.systemui.R;
+import java.util.Collections;
/**
* Activity to show the PIP menu to control PIP.
*/
public class PipMenuActivity extends Activity implements PipManager.Listener {
private static final String TAG = "PipMenuActivity";
+ static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
+
private final PipManager mPipManager = PipManager.getInstance();
private Animator mFadeInAnimation;
private Animator mFadeOutAnimation;
- private View mPipControlsView;
+ private PipControlsView mPipControlsView;
private boolean mRestorePipSizeWhenClose;
@Override
mFadeOutAnimation = AnimatorInflater.loadAnimator(
this, R.anim.tv_pip_menu_fade_out_animation);
mFadeOutAnimation.setTarget(mPipControlsView);
+
+ onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+
+ onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
}
private void restorePipAndFinish() {
}
@Override
+ public void onPipMenuActionsChanged(ParceledListSlice actions) {
+ boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
+ mPipControlsView.setActions(hasCustomActions ? actions.getList() : Collections.EMPTY_LIST);
+ }
+
+ @Override
public void onShowPipMenu() { }
@Override
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
}
@Override
+ public void onPipMenuActionsChanged(ParceledListSlice actions) {
+ // no-op.
+ }
+
+ @Override
public void onMoveToFullscreen() {
dismissPipNotification();
}
mPanel.setPanelScrimMinFraction((float) expandedHeight
/ mPanel.getMaxPanelHeight());
mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight);
+ mPanel.startExpandingFromPeek();
// This call needs to be after the expansion start otherwise we will get a
// flicker of one frame as it's not expanded yet.
mHeadsUpManager.unpinAll();
return Math.abs(yDiff) >= Math.abs(xDiff);
}
+ protected void startExpandingFromPeek() {
+ mStatusBar.handlePeekToExpandTransistion();
+ }
+
protected void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
mInitialOffsetOnTouch = expandedHeight;
// Tracks notifications currently visible in mNotificationStackScroller and
// emits visibility events via NoMan on changes.
- private final Runnable mVisibilityReporter = new Runnable() {
+ protected final Runnable mVisibilityReporter = new Runnable() {
private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
new ArraySet<>();
private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
}
}
+ void handlePeekToExpandTransistion() {
+ try {
+ // consider the transition from peek to expanded to be a panel open,
+ // but not one that clears notification effects.
+ int notificationLoad = mNotificationData.getActiveNotifications().size();
+ mBarService.onPanelRevealed(false, notificationLoad);
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ }
+
/**
* The LEDs are turned off when the notification panel is shown, even just a little bit.
* See also StatusBar.setPanelExpanded for another place where we attempt to do this.
int notificationLoad = mNotificationData.getActiveNotifications().size();
if (pinnedHeadsUp && isPanelFullyCollapsed()) {
notificationLoad = 1;
- } else {
- mMetricsLogger.histogram("note_load", notificationLoad);
}
mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
} else {
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
import android.app.Notification;
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IPowerManager;
-import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.test.filters.SmallTest;
import android.support.test.metricshelper.MetricsAsserts;
import android.support.test.runner.AndroidJUnit4;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.MessageHandler;
+import android.testing.TestableLooper.RunWithLooper;
import android.util.DisplayMetrics;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
public class StatusBarTest extends SysuiTestCase {
StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
UnlockMethodCache mUnlockMethodCache;
KeyguardIndicationController mKeyguardIndicationController;
NotificationStackScrollLayout mStackScroller;
- StatusBar mStatusBar;
+ TestableStatusBar mStatusBar;
FakeMetricsLogger mMetricsLogger;
HeadsUpManager mHeadsUpManager;
NotificationData mNotificationData;
PowerManager mPowerManager;
SystemServicesProxy mSystemServicesProxy;
NotificationPanelView mNotificationPanelView;
+ IStatusBarService mBarService;
+ ArrayList<Entry> mNotificationList;
private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
@Before
mNotificationData = mock(NotificationData.class);
mSystemServicesProxy = mock(SystemServicesProxy.class);
mNotificationPanelView = mock(NotificationPanelView.class);
+ mNotificationList = mock(ArrayList.class);
IPowerManager powerManagerService = mock(IPowerManager.class);
HandlerThread handlerThread = new HandlerThread("TestThread");
handlerThread.start();
mPowerManager = new PowerManager(mContext, powerManagerService,
new Handler(handlerThread.getLooper()));
when(powerManagerService.isInteractive()).thenReturn(true);
+ mBarService = mock(IStatusBarService.class);
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
- mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView);
-
+ mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView,
+ mBarService);
doAnswer(invocation -> {
OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
onDismissAction.onDismiss();
}).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
when(mStackScroller.getActivatedChild()).thenReturn(null);
+ TestableLooper.get(this).setMessageHandler(new MessageHandler() {
+ @Override
+ public boolean onMessageHandled(Message m) {
+ if (m.getCallback() == mStatusBar.mVisibilityReporter) {
+ return false;
+ }
+ return true;
+ }
+ });
}
@Test
assertFalse(mStatusBar.shouldPeek(entry, sbn));
}
+ @Test
+ public void testLogHidden() {
+ try {
+ mStatusBar.handleVisibleToUserChanged(false);
+ verify(mBarService, times(1)).onPanelHidden();
+ verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt());
+ } catch (RemoteException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testPanelOpenForPeek() {
+ when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
+ when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList);
+ when(mNotificationList.size()).thenReturn(5);
+ when(mNotificationPanelView.isFullyCollapsed()).thenReturn(true);
+ mStatusBar.setBarStateForTest(StatusBarState.SHADE);
+
+ try {
+ mStatusBar.handleVisibleToUserChanged(true);
+
+ verify(mBarService, never()).onPanelHidden();
+ verify(mBarService, times(1)).onPanelRevealed(false, 1);
+ } catch (RemoteException e) {
+ fail();
+ }
+ TestableLooper.get(this).processAllMessages();
+ }
+
+ @Test
+ public void testPanelOpenAndClear() {
+ when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
+ when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList);
+ when(mNotificationList.size()).thenReturn(5);
+ when(mNotificationPanelView.isFullyCollapsed()).thenReturn(false);
+ mStatusBar.setBarStateForTest(StatusBarState.SHADE);
+
+ try {
+ mStatusBar.handleVisibleToUserChanged(true);
+
+ verify(mBarService, never()).onPanelHidden();
+ verify(mBarService, times(1)).onPanelRevealed(true, 5);
+ } catch (RemoteException e) {
+ fail();
+ }
+ TestableLooper.get(this).processAllMessages();
+ }
+
+ @Test
+ public void testPanelOpenAndNoClear() {
+ when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
+ when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList);
+ when(mNotificationList.size()).thenReturn(5);
+ when(mNotificationPanelView.isFullyCollapsed()).thenReturn(false);
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+
+ try {
+ mStatusBar.handleVisibleToUserChanged(true);
+
+ verify(mBarService, never()).onPanelHidden();
+ verify(mBarService, times(1)).onPanelRevealed(false, 5);
+ } catch (RemoteException e) {
+ fail();
+ }
+ TestableLooper.get(this).processAllMessages();
+ }
+
static class TestableStatusBar extends StatusBar {
public TestableStatusBar(StatusBarKeyguardViewManager man,
UnlockMethodCache unlock, KeyguardIndicationController key,
NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd,
- PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView) {
+ PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView,
+ IStatusBarService barService) {
mStatusBarKeyguardViewManager = man;
mUnlockMethodCache = unlock;
mKeyguardIndicationController = key;
mPowerManager = pm;
mSystemServicesProxy = ssp;
mNotificationPanel = panelView;
+ mBarService = barService;
}
- @Override
- protected H createHandler() {
- return null;
+ public void setBarStateForTest(int state) {
+ mState = state;
}
}
}
\ No newline at end of file
import android.os.IDeviceIdleController;
import android.os.IInterface;
import android.os.Parcel;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
}
private static boolean isCallerSystem() {
- return getCallingUserId() == UserHandle.USER_SYSTEM;
+ return Binder.getCallingUid() == Process.SYSTEM_UID;
}
private ServiceConnection createServiceConnection(
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.EventLog;
+import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
long mStartVisibleTime;
long mEndTime;
int mNumActive;
+
+ // Temp output of foregroundAppShownEnoughLocked
+ long mHideTime;
}
/**
!= ActivityManager.APP_START_MODE_NORMAL) {
if (stopping == null) {
stopping = new ArrayList<>();
- String compName = service.name.flattenToShortString();
- EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
- StringBuilder sb = new StringBuilder(64);
- sb.append("Stopping service due to app idle: ");
- UserHandle.formatUid(sb, service.appInfo.uid);
- sb.append(" ");
- TimeUtils.formatDuration(service.createTime
- - SystemClock.elapsedRealtime(), sb);
- sb.append(" ");
- sb.append(compName);
- Slog.w(TAG, sb.toString());
- stopping.add(service);
}
+ String compName = service.name.flattenToShortString();
+ EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("Stopping service due to app idle: ");
+ UserHandle.formatUid(sb, service.appInfo.uid);
+ sb.append(" ");
+ TimeUtils.formatDuration(service.createTime
+ - SystemClock.elapsedRealtime(), sb);
+ sb.append(" ");
+ sb.append(compName);
+ Slog.w(TAG, sb.toString());
+ stopping.add(service);
}
}
}
}
}
+ boolean foregroundAppShownEnoughLocked(ActiveForegroundApp aa, long nowElapsed) {
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Shown enough: pkg=" + aa.mPackageName + ", uid="
+ + aa.mUid);
+ boolean canRemove = false;
+ aa.mHideTime = Long.MAX_VALUE;
+ if (aa.mShownWhileTop) {
+ // If the app was ever at the top of the screen while the foreground
+ // service was running, then we can always just immediately remove it.
+ canRemove = true;
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - shown while on top");
+ } else if (mScreenOn || aa.mShownWhileScreenOn) {
+ final long minTime = aa.mStartVisibleTime
+ + (aa.mStartTime != aa.mStartVisibleTime
+ ? mAm.mConstants.FGSERVICE_SCREEN_ON_AFTER_TIME
+ : mAm.mConstants.FGSERVICE_MIN_SHOWN_TIME);
+ if (nowElapsed >= minTime) {
+ // If shown while the screen is on, and it has been shown for
+ // at least the minimum show time, then we can now remove it.
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - shown long enough with screen on");
+ canRemove = true;
+ } else {
+ // This is when we will be okay to stop telling the user.
+ long reportTime = nowElapsed + mAm.mConstants.FGSERVICE_MIN_REPORT_TIME;
+ aa.mHideTime = reportTime > minTime ? reportTime : minTime;
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "NO -- wait " + (aa.mHideTime-nowElapsed)
+ + " with screen on");
+ }
+ } else {
+ final long minTime = aa.mEndTime
+ + mAm.mConstants.FGSERVICE_SCREEN_ON_BEFORE_TIME;
+ if (nowElapsed >= minTime) {
+ // If the foreground service has only run while the screen is
+ // off, but it has been gone now for long enough that we won't
+ // care to tell the user about it when the screen comes back on,
+ // then we can remove it now.
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - gone long enough with screen off");
+ canRemove = true;
+ } else {
+ // This is when we won't care about this old fg service.
+ aa.mHideTime = minTime;
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "NO -- wait " + (aa.mHideTime-nowElapsed)
+ + " with screen off");
+ }
+ }
+ return canRemove;
+ }
+
void updateForegroundApps(ServiceMap smap) {
// This is called from the handler without the lock held.
ArrayList<ActiveForegroundApp> active = null;
synchronized (mAm) {
final long now = SystemClock.elapsedRealtime();
- final long nowPlusMin = now + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME;
long nextUpdateTime = Long.MAX_VALUE;
if (smap != null) {
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Updating foreground apps for user "
+ + smap.mUserId);
for (int i = smap.mActiveForegroundApps.size()-1; i >= 0; i--) {
ActiveForegroundApp aa = smap.mActiveForegroundApps.valueAt(i);
- if (aa.mEndTime != 0 && (mScreenOn || aa.mShownWhileScreenOn)) {
- if (!aa.mShownWhileTop && aa.mEndTime < (aa.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME)) {
- // Check to see if this should still be displayed... we continue
- // until it has been shown for at least the timeout duration.
- if (nowPlusMin >= aa.mStartVisibleTime) {
- // All over!
- smap.mActiveForegroundApps.removeAt(i);
- smap.mActiveForegroundAppsChanged = true;
- continue;
- } else {
- long hideTime = aa.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME;
- if (hideTime < nextUpdateTime) {
- nextUpdateTime = hideTime;
- }
- }
- } else {
+ if (aa.mEndTime != 0) {
+ boolean canRemove = foregroundAppShownEnoughLocked(aa, now);
+ if (canRemove) {
// This was up for longer than the timeout, so just remove immediately.
smap.mActiveForegroundApps.removeAt(i);
smap.mActiveForegroundAppsChanged = true;
continue;
}
+ if (aa.mHideTime < nextUpdateTime) {
+ nextUpdateTime = aa.mHideTime;
+ }
}
if (!aa.mAppOnTop) {
if (active == null) {
active = new ArrayList<>();
}
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Adding active: pkg="
+ + aa.mPackageName + ", uid=" + aa.mUid);
active.add(aa);
}
}
smap.removeMessages(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
if (nextUpdateTime < Long.MAX_VALUE) {
- Message msg = smap.obtainMessage();
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Next update time in: "
+ + (nextUpdateTime-now));
+ Message msg = smap.obtainMessage(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
smap.sendMessageAtTime(msg, nextUpdateTime
+ SystemClock.uptimeMillis() - SystemClock.elapsedRealtime());
}
active.mNumActive--;
if (active.mNumActive <= 0) {
active.mEndTime = SystemClock.elapsedRealtime();
- if (active.mShownWhileTop || active.mEndTime >= (active.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME)) {
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Ended running of service");
+ if (foregroundAppShownEnoughLocked(active, active.mEndTime)) {
// Have been active for long enough that we will remove it immediately.
smap.mActiveForegroundApps.remove(r.packageName);
smap.mActiveForegroundAppsChanged = true;
requestUpdateActiveForegroundAppsLocked(smap, 0);
- } else {
- requestUpdateActiveForegroundAppsLocked(smap, active.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME);
+ } else if (active.mHideTime < Long.MAX_VALUE){
+ requestUpdateActiveForegroundAppsLocked(smap, active.mHideTime);
}
}
}
// services that were started while the screen was off.
if (screenOn) {
final long nowElapsed = SystemClock.elapsedRealtime();
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Screen turned on");
for (int i = mServiceMap.size()-1; i >= 0; i--) {
ServiceMap smap = mServiceMap.valueAt(i);
+ long nextUpdateTime = Long.MAX_VALUE;
boolean changed = false;
for (int j = smap.mActiveForegroundApps.size()-1; j >= 0; j--) {
ActiveForegroundApp active = smap.mActiveForegroundApps.valueAt(j);
- if (!active.mShownWhileScreenOn) {
- changed = true;
- active.mShownWhileScreenOn = mScreenOn;
- active.mStartVisibleTime = nowElapsed;
- if (active.mEndTime != 0) {
- active.mEndTime = nowElapsed;
+ if (active.mEndTime == 0) {
+ if (!active.mShownWhileScreenOn) {
+ active.mShownWhileScreenOn = true;
+ active.mStartVisibleTime = nowElapsed;
+ }
+ } else {
+ if (!active.mShownWhileScreenOn
+ && active.mStartVisibleTime == active.mStartTime) {
+ // If this was never shown while the screen was on, then we will
+ // count the time it started being visible as now, to tell the user
+ // about it now that they have a screen to look at.
+ active.mEndTime = active.mStartVisibleTime = nowElapsed;
+ }
+ if (foregroundAppShownEnoughLocked(active, nowElapsed)) {
+ // Have been active for long enough that we will remove it
+ // immediately.
+ smap.mActiveForegroundApps.remove(active.mPackageName);
+ smap.mActiveForegroundAppsChanged = true;
+ changed = true;
+ } else {
+ if (active.mHideTime < nextUpdateTime) {
+ nextUpdateTime = active.mHideTime;
+ }
}
}
}
if (changed) {
- requestUpdateActiveForegroundAppsLocked(smap,
- nowElapsed + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME);
- } else if (smap.mActiveForegroundApps.size() > 0) {
- // Just being paranoid.
+ // Need to immediately update.
requestUpdateActiveForegroundAppsLocked(smap, 0);
+ } else if (nextUpdateTime < Long.MAX_VALUE) {
+ requestUpdateActiveForegroundAppsLocked(smap, nextUpdateTime);
}
}
}
return true;
}
- // Is someone still bound to us keepign us running?
+ // Is someone still bound to us keeping us running?
if (!knowConn) {
hasConn = r.hasAutoCreateConnections();
}
pw.println();
}
}
+ if (smap.hasMessagesOrCallbacks()) {
+ if (needSep) {
+ pw.println();
+ }
+ printedAnything = true;
+ needSep = true;
+ pw.print(" Handler - user ");
+ pw.print(user);
+ pw.println(":");
+ smap.dumpMine(new PrintWriterPrinter(pw), " ");
+ }
}
}
// Key names stored in the settings value.
private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time";
- private static final String KEY_FOREGROUND_SERVICE_UI_MIN_TIME
- = "foreground_service_ui_min_time";
+ private static final String KEY_FGSERVICE_MIN_SHOWN_TIME
+ = "fgservice_min_shown_time";
+ private static final String KEY_FGSERVICE_MIN_REPORT_TIME
+ = "fgservice_min_report_time";
+ private static final String KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME
+ = "fgservice_screen_on_before_time";
+ private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
+ = "fgservice_screen_on_after_time";
private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
private static final String KEY_GC_TIMEOUT = "gc_timeout";
private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
- private static final long DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME = 30*1000;
+ private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
+ private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
+ private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
+ private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
private static final long DEFAULT_GC_TIMEOUT = 5*1000;
private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
// before we start restricting what it can do.
public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME;
- // The minimum time a foreground service will be shown as running in the notification UI.
- public long FOREGROUND_SERVICE_UI_MIN_TIME = DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME;
+ // The minimum time we allow a foreground service to run with a notification and the
+ // screen on without otherwise telling the user about it. (If it runs for less than this,
+ // it will still be reported to the user as a running app for at least this amount of time.)
+ public long FGSERVICE_MIN_SHOWN_TIME = DEFAULT_FGSERVICE_MIN_SHOWN_TIME;
+
+ // If a foreground service is shown for less than FGSERVICE_MIN_SHOWN_TIME, we will display
+ // the background app running notification about it for at least this amount of time (if it
+ // is larger than the remaining shown time).
+ public long FGSERVICE_MIN_REPORT_TIME = DEFAULT_FGSERVICE_MIN_REPORT_TIME;
+
+ // The minimum amount of time the foreground service needs to have remain being shown
+ // before the screen goes on for us to consider it not worth showing to the user. That is
+ // if an app has a foreground service that stops itself this amount of time or more before
+ // the user turns on the screen, we will just let it go without the user being told about it.
+ public long FGSERVICE_SCREEN_ON_BEFORE_TIME = DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME;
+
+ // The minimum amount of time a foreground service should remain reported to the user if
+ // it is stopped when the screen turns on. This is the time from when the screen turns
+ // on until we will stop reporting it.
+ public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
// How long we will retain processes hosting content providers in the "last activity"
// state before allowing them to drop down to the regular cached LRU list. This is
DEFAULT_MAX_CACHED_PROCESSES);
BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,
DEFAULT_BACKGROUND_SETTLE_TIME);
- FOREGROUND_SERVICE_UI_MIN_TIME = mParser.getLong(KEY_FOREGROUND_SERVICE_UI_MIN_TIME,
- DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME);
+ FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME,
+ DEFAULT_FGSERVICE_MIN_SHOWN_TIME);
+ FGSERVICE_MIN_REPORT_TIME = mParser.getLong(KEY_FGSERVICE_MIN_REPORT_TIME,
+ DEFAULT_FGSERVICE_MIN_REPORT_TIME);
+ FGSERVICE_SCREEN_ON_BEFORE_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME,
+ DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
+ FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
+ DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
pw.println(MAX_CACHED_PROCESSES);
pw.print(" "); pw.print(KEY_BACKGROUND_SETTLE_TIME); pw.print("=");
pw.println(BACKGROUND_SETTLE_TIME);
- pw.print(" "); pw.print(KEY_FOREGROUND_SERVICE_UI_MIN_TIME); pw.print("=");
- pw.println(FOREGROUND_SERVICE_UI_MIN_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_MIN_SHOWN_TIME); pw.print("=");
+ pw.println(FGSERVICE_MIN_SHOWN_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_MIN_REPORT_TIME); pw.print("=");
+ pw.println(FGSERVICE_MIN_REPORT_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME); pw.print("=");
+ pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
+ pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
pw.print(" "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
pw.println(CONTENT_PROVIDER_RETAIN_TIME);
pw.print(" "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
static final boolean DEBUG_SAVED_STATE = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_SCREENSHOTS = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
+ static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false;
static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false;
static final boolean DEBUG_STACK = DEBUG_ALL || false;
static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false;
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToFront: moving taskId=" + taskId);
synchronized(this) {
- moveTaskToFrontLocked(taskId, flags, bOptions);
+ moveTaskToFrontLocked(taskId, flags, bOptions, false /* fromRecents */);
}
}
- void moveTaskToFrontLocked(int taskId, int flags, Bundle bOptions) {
+ void moveTaskToFrontLocked(int taskId, int flags, Bundle bOptions, boolean fromRecents) {
ActivityOptions options = ActivityOptions.fromBundle(bOptions);
if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
// We are reshowing a task, use a starting window to hide the initial draw delay
// so the transition can start earlier.
topActivity.showStartingWindow(null /* prev */, false /* newTask */,
- true /* taskSwitch */);
+ true /* taskSwitch */, fromRecents);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
@Override
- public void notifyAppTransitionStarting(SparseIntArray reasons) {
+ public void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp) {
synchronized (ActivityManagerService.this) {
- mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(reasons);
+ mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(
+ reasons, timestamp);
}
}
/**
* Notifies the tracker that all windows of the app have been drawn.
*/
- void notifyWindowsDrawn(int stackId) {
+ void notifyWindowsDrawn(int stackId, long timestamp) {
final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
if (info == null || info.loggedWindowsDrawn) {
return;
}
- info.windowsDrawnDelayMs = calculateCurrentDelay();
+ info.windowsDrawnDelayMs = calculateDelay(timestamp);
info.loggedWindowsDrawn = true;
if (allStacksWindowsDrawn() && mLoggedTransitionStarting) {
reset(false /* abort */);
/**
* Notifies the tracker that the starting window was drawn.
*/
- void notifyStartingWindowDrawn(int stackId) {
+ void notifyStartingWindowDrawn(int stackId, long timestamp) {
final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
if (info == null || info.loggedStartingWindowDrawn) {
return;
}
info.loggedStartingWindowDrawn = true;
- info.startingWindowDelayMs = calculateCurrentDelay();
+ info.startingWindowDelayMs = calculateDelay(timestamp);
}
/**
* @param stackIdReasons A map from stack id to a reason integer, which must be on of
* ActivityManagerInternal.APP_TRANSITION_* reasons.
*/
- void notifyTransitionStarting(SparseIntArray stackIdReasons) {
+ void notifyTransitionStarting(SparseIntArray stackIdReasons, long timestamp) {
if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
return;
}
- mCurrentTransitionDelayMs = calculateCurrentDelay();
+ mCurrentTransitionDelayMs = calculateDelay(timestamp);
mLoggedTransitionStarting = true;
for (int index = stackIdReasons.size() - 1; index >= 0; index--) {
final int stackId = stackIdReasons.keyAt(index);
return (int) (SystemClock.uptimeMillis() - mCurrentTransitionStartTime);
}
+ private int calculateDelay(long timestamp) {
+ // Shouldn't take more than 25 days to launch an app, so int is fine here.
+ return (int) (timestamp - mCurrentTransitionStartTime);
+ }
+
private void logAppTransitionMultiEvents() {
for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) {
final StackTransitionInfo info = mStackTransitionInfo.valueAt(index);
}
@Override
- public void onStartingWindowDrawn() {
+ public void onStartingWindowDrawn(long timestamp) {
synchronized (service) {
- mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(getStackId());
+ mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(
+ getStackId(), timestamp);
}
}
@Override
- public void onWindowsDrawn() {
+ public void onWindowsDrawn(long timestamp) {
synchronized (service) {
- mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId());
+ mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId(), timestamp);
if (displayStartTime != 0) {
- reportLaunchTimeLocked(SystemClock.uptimeMillis());
+ reportLaunchTimeLocked(timestamp);
}
mStackSupervisor.sendWaitingVisibleReportLocked(this);
startTime = 0;
}
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
+ showStartingWindow(prev, newTask, taskSwitch, false /* fromRecents */);
+ }
+
+ void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
+ boolean fromRecents) {
if (mWindowContainerController == null) {
return;
}
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
allowTaskSnapshot(),
- state.ordinal() >= RESUMED.ordinal() && state.ordinal() <= STOPPED.ordinal());
+ state.ordinal() >= RESUMED.ordinal() && state.ordinal() <= STOPPED.ordinal(),
+ fromRecents);
if (shown) {
mStartingWindowState = STARTING_WINDOW_SHOWN;
}
&& task.getRootActivity() != null) {
mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */);
mActivityMetricsLogger.notifyActivityLaunching();
- mService.moveTaskToFrontLocked(task.taskId, 0, bOptions);
+ mService.moveTaskToFrontLocked(task.taskId, 0, bOptions, true /* fromRecents */);
mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT,
task.getTopActivity());
@Override
public void onPanelRevealed(boolean clearEffects, int items) {
MetricsLogger.visible(getContext(), MetricsEvent.NOTIFICATION_PANEL);
- MetricsLogger.histogram(getContext(), "notification_load", items);
+ MetricsLogger.histogram(getContext(), "note_load", items);
EventLogTags.writeNotificationPanelRevealed(items);
if (clearEffects) {
clearEffects();
mResolvedInstructionSets.add(archSubDir.getName());
List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
- if (!oatFiles.isEmpty()) {
- mResolvedInheritedFiles.addAll(oatFiles);
+
+ // Only add compiled files associated with the base.
+ // Once b/62269291 is resolved, we can add all compiled files again.
+ for (File oatFile : oatFiles) {
+ if (oatFile.getName().equals("base.art")
+ || oatFile.getName().equals("base.odex")
+ || oatFile.getName().equals("base.vdex")) {
+ mResolvedInheritedFiles.add(oatFile);
+ }
}
}
}
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps == null || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) {
- return true;
+ return false;
}
return mSettings.getBlockUninstallLPr(userId, packageName);
}
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.Trace;
import android.util.Slog;
+import android.view.DisplayInfo;
import android.view.IApplicationToken;
import android.view.WindowManagerPolicy.StartingSurface;
private final IApplicationToken mToken;
private final Handler mHandler;
- private final Runnable mOnStartingWindowDrawn = () -> {
- if (mListener == null) {
- return;
+ private final class H extends Handler {
+ public static final int NOTIFY_WINDOWS_DRAWN = 1;
+ public static final int NOTIFY_STARTING_WINDOW_DRAWN = 2;
+
+ public H(Looper looper) {
+ super(looper);
}
- if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
- + AppWindowContainerController.this.mToken);
- mListener.onStartingWindowDrawn();
- };
- private final Runnable mOnWindowsDrawn = () -> {
- if (mListener == null) {
- return;
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case NOTIFY_WINDOWS_DRAWN:
+ if (mListener == null) {
+ return;
+ }
+ if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+ + AppWindowContainerController.this.mToken);
+ mListener.onWindowsDrawn(msg.getWhen());
+ break;
+ case NOTIFY_STARTING_WINDOW_DRAWN:
+ if (mListener == null) {
+ return;
+ }
+ if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+ + AppWindowContainerController.this.mToken);
+ mListener.onStartingWindowDrawn(msg.getWhen());
+ break;
+ default:
+ break;
+ }
}
- if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
- + AppWindowContainerController.this.mToken);
- mListener.onWindowsDrawn();
- };
+ }
private final Runnable mOnWindowsVisible = () -> {
if (mListener == null) {
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
WindowManagerService service, Configuration overrideConfig, Rect bounds) {
super(listener, service);
- mHandler = new Handler(service.mH.getLooper());
+ mHandler = new H(service.mH.getLooper());
mToken = token;
synchronized(mWindowMap) {
AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
public boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
- boolean allowTaskSnapshot, boolean activityCreated) {
+ boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) {
synchronized(mWindowMap) {
if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken
+ " pkg=" + pkg + " transferFrom=" + transferFrom + " newTask=" + newTask
return false;
}
+ final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
+ mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
+ false /* restoreFromDisk */, false /* reducedResolution */);
final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
- allowTaskSnapshot, activityCreated);
+ allowTaskSnapshot, activityCreated, fromRecents, snapshot);
if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
- return createSnapshot();
+ return createSnapshot(snapshot);
}
// If this is a translucent window, then don't show a starting window -- the current
}
private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
- boolean allowTaskSnapshot, boolean activityCreated) {
+ boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents,
+ TaskSnapshot snapshot) {
if (mService.mAppTransition.getAppTransition() == TRANSIT_DOCK_TASK_FROM_RECENTS) {
// TODO(b/34099271): Remove this statement to add back the starting window and figure
// out why it causes flickering, the starting window appears over the thumbnail while
} else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) {
- return STARTING_WINDOW_TYPE_SNAPSHOT;
+ return snapshot == null ? STARTING_WINDOW_TYPE_NONE
+ : snapshotFillsWidth(snapshot) || fromRecents ? STARTING_WINDOW_TYPE_SNAPSHOT
+ : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else {
return STARTING_WINDOW_TYPE_NONE;
}
mService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
}
- private boolean createSnapshot() {
- final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
- mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
- false /* restoreFromDisk */, false /* reducedResolution */);
-
+ private boolean createSnapshot(TaskSnapshot snapshot) {
if (snapshot == null) {
return false;
}
return true;
}
+ private boolean snapshotFillsWidth(TaskSnapshot snapshot) {
+ if (snapshot == null) {
+ return false;
+ }
+ final Rect rect = new Rect(0, 0, snapshot.getSnapshot().getWidth(),
+ snapshot.getSnapshot().getHeight());
+ rect.inset(snapshot.getContentInsets());
+ final Rect taskBoundsWithoutInsets = new Rect();
+ mContainer.getTask().getBounds(taskBoundsWithoutInsets);
+ final DisplayInfo di = mContainer.getDisplayContent().getDisplayInfo();
+ final Rect displayBounds = new Rect(0, 0, di.logicalWidth, di.logicalHeight);
+ final Rect stableInsets = new Rect();
+ mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+ stableInsets);
+ displayBounds.inset(stableInsets);
+ return rect.width() >= displayBounds.width();
+ }
+
public void removeStartingWindow() {
synchronized (mWindowMap) {
if (mHandler.hasCallbacks(mRemoveStartingWindow)) {
}
void reportStartingWindowDrawn() {
- mHandler.post(mOnStartingWindowDrawn);
+ mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_STARTING_WINDOW_DRAWN));
}
void reportWindowsDrawn() {
- mHandler.post(mOnWindowsDrawn);
+ mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_WINDOWS_DRAWN));
}
void reportWindowsVisible() {
/** Interface used by the creator of the controller to listen to changes with the container. */
public interface AppWindowContainerListener extends WindowContainerListener {
/** Called when the windows associated app window container are drawn. */
- void onWindowsDrawn();
+ void onWindowsDrawn(long timestamp);
/** Called when the windows associated app window container are visible. */
void onWindowsVisible();
/** Called when the windows associated app window container are no longer visible. */
/**
* Called when the starting window for this container is drawn.
*/
- void onStartingWindowDrawn();
+ void onStartingWindowDrawn(long timestamp);
/**
* Called when the key dispatching to a window associated with the app window container
startingWindow.mPolicyVisibility = false;
startingWindow.mPolicyVisibilityAfterAnim = false;
}
+
+ // We are becoming visible, so better freeze the screen with the windows that are
+ // getting visible so we also wait for them.
+ forAllWindows(mService::makeWindowFreezingScreenIfNeededLocked, true);
}
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "setVisibility: " + this
return mPendingRelaunchCount > 0;
}
+ boolean shouldFreezeBounds() {
+ final Task task = getTask();
+
+ // For freeform windows, we can't freeze the bounds at the moment because this would make
+ // the resizing unresponsive.
+ if (task == null || task.inFreeformWorkspace()) {
+ return false;
+ }
+
+ // We freeze the bounds while drag resizing to deal with the time between
+ // the divider/drag handle being released, and the handling it's new
+ // configuration. If we are relaunched outside of the drag resizing state,
+ // we need to be careful not to do this.
+ return getTask().isDragResizing();
+ }
+
void startRelaunching() {
- if (canFreezeBounds()) {
+ if (shouldFreezeBounds()) {
freezeBounds();
}
}
void finishRelaunching() {
- if (canFreezeBounds()) {
- unfreezeBounds();
- }
+ unfreezeBounds();
+
if (mPendingRelaunchCount > 0) {
mPendingRelaunchCount--;
} else {
if (mPendingRelaunchCount == 0) {
return;
}
- if (canFreezeBounds()) {
- unfreezeBounds();
- }
+ unfreezeBounds();
mPendingRelaunchCount = 0;
}
}
}
- private boolean canFreezeBounds() {
- final Task task = getTask();
-
- // For freeform windows, we can't freeze the bounds at the moment because this would make
- // the resizing unresponsive.
- return task != null && !task.inFreeformWorkspace();
- }
-
/**
* Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
* freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
* Unfreezes the previously frozen bounds. See {@link #freezeBounds}.
*/
private void unfreezeBounds() {
- if (!mFrozenBounds.isEmpty()) {
- mFrozenBounds.remove();
+ if (mFrozenBounds.isEmpty()) {
+ return;
}
+ mFrozenBounds.remove();
if (!mFrozenMergedConfig.isEmpty()) {
mFrozenMergedConfig.remove();
}
private final Paint mBackgroundPaint = new Paint();
private final int mStatusBarColor;
@VisibleForTesting final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
+ private final int mOrientationOnCreation;
static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
TaskSnapshot snapshot) {
final int sysUiVis;
final int windowFlags;
final int windowPrivateFlags;
+ final int currentOrientation;
synchronized (service.mWindowMap) {
final WindowState mainWindow = token.findMainWindow();
if (mainWindow == null) {
} else {
taskBounds = null;
}
+ currentOrientation = mainWindow.getConfiguration().orientation;
}
try {
final int res = session.addToDisplay(window, window.mSeq, layoutParams,
}
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
surface, snapshot, layoutParams.getTitle(), backgroundColor, statusBarColor,
- navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds);
+ navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds,
+ currentOrientation);
window.setOuter(snapshotSurface);
try {
session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface,
TaskSnapshot snapshot, CharSequence title, int backgroundColor, int statusBarColor,
int navigationBarColor, int sysUiVis, int windowFlags, int windowPrivateFlags,
- Rect taskBounds) {
+ Rect taskBounds, int currentOrientation) {
mService = service;
mHandler = new Handler(mService.mH.getLooper());
mSession = WindowManagerGlobal.getWindowSession();
mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
windowPrivateFlags, sysUiVis, statusBarColor, navigationBarColor);
mStatusBarColor = statusBarColor;
+ mOrientationOnCreation = currentOrientation;
}
@Override
static class Window extends BaseIWindow {
private TaskSnapshotSurface mOuter;
+
public void setOuter(TaskSnapshotSurface outer) {
mOuter = outer;
}
Rect stableInsets, Rect outsets, boolean reportDraw,
MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
boolean alwaysConsumeNavBar, int displayId) {
+ if (mergedConfiguration != null && mOuter != null
+ && mOuter.mOrientationOnCreation
+ != mergedConfiguration.getMergedConfiguration().orientation) {
+
+ // The orientation of the screen is changing. We better remove the snapshot ASAP as
+ // we are going to wait on the new window in any case to unfreeze the screen, and
+ // the starting window is not needed anymore.
+ sHandler.post(mOuter::remove);
+ }
if (reportDraw) {
sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget();
}
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
if (transactionOpen) {
- mService.closeSurfaceTransaction();
+
+ // Do not hold window manager lock while closing the transaction, as this might be
+ // blocking until the next frame, which can lead to total lock starvation.
+ mService.closeSurfaceTransaction(false /* withLockHeld */);
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
}
}
}
void closeSurfaceTransaction() {
+ closeSurfaceTransaction(true /* withLockHeld */);
+ }
+
+ /**
+ * Closes a surface transaction.
+ *
+ * @param withLockHeld Whether to acquire the window manager while doing so. In some cases
+ * holding the lock my lead to starvation in WM in case closeTransaction
+ * blocks and we call it repeatedly, like we do for animations.
+ */
+ void closeSurfaceTransaction(boolean withLockHeld) {
synchronized (mWindowMap) {
if (mRoot.mSurfaceTraceEnabled) {
mRoot.mRemoteEventTrace.closeSurfaceTransaction();
}
+ if (withLockHeld) {
+ SurfaceControl.closeTransaction();
+ }
+ }
+ if (!withLockHeld) {
SurfaceControl.closeTransaction();
}
}
+ token + ". Aborting.");
return WindowManagerGlobal.ADD_APP_EXITING;
}
- if (rootType == TYPE_APPLICATION_STARTING
- && (attrs.privateFlags & PRIVATE_FLAG_TASK_SNAPSHOT) == 0
- && atoken.firstWindowDrawn) {
- // No need for this guy!
- if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v(
- TAG_WM, "**** NO NEED TO START: " + attrs.getTitle());
- return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
- }
} else if (rootType == TYPE_INPUT_METHOD) {
if (token.windowType != TYPE_INPUT_METHOD) {
Slog.w(TAG_WM, "Attempted to add input method window with bad token "
}
break;
case NOTIFY_APP_TRANSITION_STARTING: {
- mAmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj);
+ mAmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj,
+ msg.getWhen());
}
break;
case NOTIFY_APP_TRANSITION_CANCELLED: {
void prepareSurfaceLocked(final boolean recoveringMemory) {
final WindowState w = mWin;
if (!hasSurface()) {
- if (w.mOrientationChanging) {
+
+ // There is no need to wait for an animation change if our window is gone for layout
+ // already as we'll never be visible.
+ if (w.mOrientationChanging && w.isGoneForLayoutLw()) {
if (DEBUG_ORIENTATION) {
Slog.v(TAG, "Orientation change skips hidden " + w);
}
hide("prepareSurfaceLocked");
mWallpaperControllerLocked.hideWallpapers(w);
- // If we are waiting for this window to handle an
- // orientation change, well, it is hidden, so
- // doesn't really matter. Note that this does
- // introduce a potential glitch if the window
- // becomes unhidden before it has drawn for the
- // new orientation.
- if (w.mOrientationChanging) {
+ // If we are waiting for this window to handle an orientation change. If this window is
+ // really hidden (gone for layout), there is no point in still waiting for it.
+ // Note that this does introduce a potential glitch if the window becomes unhidden
+ // before it has drawn for the new orientation.
+ if (w.mOrientationChanging && w.isGoneForLayoutLw()) {
w.mOrientationChanging = false;
if (DEBUG_ORIENTATION) Slog.v(TAG,
"Orientation change skips hidden " + w);
displayed = true;
}
- if (displayed) {
- if (w.mOrientationChanging) {
- if (!w.isDrawnLw()) {
- mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
- mAnimator.mLastWindowFreezeSource = w;
- if (DEBUG_ORIENTATION) Slog.v(TAG,
- "Orientation continue waiting for draw in " + w);
- } else {
- w.mOrientationChanging = false;
- if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w);
- }
+ if (w.mOrientationChanging) {
+ if (!w.isDrawnLw()) {
+ mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
+ mAnimator.mLastWindowFreezeSource = w;
+ if (DEBUG_ORIENTATION) Slog.v(TAG,
+ "Orientation continue waiting for draw in " + w);
+ } else {
+ w.mOrientationChanging = false;
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w);
}
+ }
+
+ if (displayed) {
w.mToken.hasVisible = true;
}
}
createAppWindowController();
controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false);
+ false, false);
waitUntilHandlersIdle();
final AppWindowToken atoken = controller.getAppWindowToken(mDisplayContent);
assertHasStartingWindow(atoken);
createAppWindowController();
controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false);
+ false, false);
controller.removeStartingWindow();
waitUntilHandlersIdle();
assertNoStartingWindow(controller.getAppWindowToken(mDisplayContent));
createAppWindowController();
controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false);
+ false, false);
waitUntilHandlersIdle();
controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
- true, true, false, true, false);
+ true, true, false, true, false, false);
waitUntilHandlersIdle();
assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent));
assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent));
// Surprise, ...! Transfer window in the middle of the creation flow.
controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
- true, true, false, true, false);
+ true, true, false, true, false, false);
});
controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false);
+ false, false);
waitUntilHandlersIdle();
assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent));
assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent));
final TaskSnapshot snapshot = new TaskSnapshot(buffer,
ORIENTATION_PORTRAIT, contentInsets, false, 1.0f);
mSurface = new TaskSnapshotSurface(sWm, new Window(), new Surface(), snapshot, "Test",
- Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds);
+ Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds,
+ ORIENTATION_PORTRAIT);
}
private void setupSurface(int width, int height) {
if (lineCount == 2 && line.contains(SUCCESS_MESSAGE)) {
launchSuccess = true;
}
- if (launchSuccess && lineCount == 4) {
+ // Parse TotalTime which is the launch time
+ if (launchSuccess && lineCount == 5) {
String launchSplit[] = line.split(":");
launchTime = launchSplit[1].trim();
}