Merge "Return null value if getActiveAdminUncheckedLocked returns null." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index c416044..6a1bb02 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -416,9 +416,11 @@
field public static final int contentAuthority = 16843408; // 0x1010290
field public static final int contentDescription = 16843379; // 0x1010273
field public static final int contentInsetEnd = 16843860; // 0x1010454
+ field public static final int contentInsetEndWithActions = 16844070; // 0x1010526
field public static final int contentInsetLeft = 16843861; // 0x1010455
field public static final int contentInsetRight = 16843862; // 0x1010456
field public static final int contentInsetStart = 16843859; // 0x1010453
+ field public static final int contentInsetStartWithNavigation = 16844069; // 0x1010525
field public static final int contextClickable = 16844007; // 0x10104e7
field public static final int contextPopupMenuStyle = 16844034; // 0x1010502
field public static final int controlX1 = 16843772; // 0x10103fc
@@ -3423,7 +3425,7 @@
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
- method public void enterPictureInPicture();
+ method public void enterPictureInPictureMode();
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -3463,15 +3465,16 @@
method public android.view.Window getWindow();
method public android.view.WindowManager getWindowManager();
method public boolean hasWindowFocus();
- method public boolean inMultiWindow();
- method public boolean inPictureInPicture();
method public void invalidateOptionsMenu();
method public boolean isChangingConfigurations();
method public final boolean isChild();
method public boolean isDestroyed();
method public boolean isFinishing();
method public boolean isImmersive();
+ method public boolean isInMultiWindowMode();
+ method public boolean isInPictureInPictureMode();
method public boolean isLocalVoiceInteractionSupported();
+ method public boolean isOverlayWithDecorCaptionEnabled();
method public boolean isTaskRoot();
method public boolean isVoiceInteraction();
method public boolean isVoiceInteractionRoot();
@@ -3518,7 +3521,7 @@
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
- method public void onMultiWindowChanged(boolean);
+ method public void onMultiWindowModeChanged(boolean);
method public boolean onNavigateUp();
method public boolean onNavigateUpFromChild(android.app.Activity);
method protected void onNewIntent(android.content.Intent);
@@ -3526,7 +3529,7 @@
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPanelClosed(int, android.view.Menu);
method protected void onPause();
- method public void onPictureInPictureChanged(boolean);
+ method public void onPictureInPictureModeChanged(boolean);
method protected void onPostCreate(android.os.Bundle);
method public void onPostCreate(android.os.Bundle, android.os.PersistableBundle);
method protected void onPostResume();
@@ -3564,7 +3567,6 @@
method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
method public void openContextMenu(android.view.View);
method public void openOptionsMenu();
- method public void overlayWithDecorCaption(boolean);
method public void overridePendingTransition(int, int);
method public void postponeEnterTransition();
method public void recreate();
@@ -3593,6 +3595,7 @@
method public void setImmersive(boolean);
method public void setIntent(android.content.Intent);
method public final void setMediaController(android.media.session.MediaController);
+ method public void setOverlayWithDecorCaptionEnabled(boolean);
method public final deprecated void setProgress(int);
method public final deprecated void setProgressBarIndeterminate(boolean);
method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -4435,11 +4438,11 @@
method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle);
method public void onLowMemory();
- method public void onMultiWindowChanged(boolean);
+ method public void onMultiWindowModeChanged(boolean);
method public boolean onOptionsItemSelected(android.view.MenuItem);
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPause();
- method public void onPictureInPictureChanged(boolean);
+ method public void onPictureInPictureModeChanged(boolean);
method public void onPrepareOptionsMenu(android.view.Menu);
method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
method public void onResume();
@@ -4520,11 +4523,11 @@
method public void dispatchDestroy();
method public void dispatchDestroyView();
method public void dispatchLowMemory();
- method public void dispatchMultiWindowChanged(boolean);
+ method public void dispatchMultiWindowModeChanged(boolean);
method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
method public void dispatchOptionsMenuClosed(android.view.Menu);
method public void dispatchPause();
- method public void dispatchPictureInPictureChanged(boolean);
+ method public void dispatchPictureInPictureModeChanged(boolean);
method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
method public void dispatchResume();
method public void dispatchStart();
@@ -5853,7 +5856,7 @@
method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
- method public java.lang.String getDeviceOwnerLockScreenInfo();
+ method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
method public java.lang.String getLongSupportMessage(android.content.ComponentName);
@@ -5891,7 +5894,7 @@
method public boolean hasGrantedPolicy(android.content.ComponentName, int);
method public boolean installCaCert(android.content.ComponentName, byte[]);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
- method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String, boolean);
+ method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -5924,7 +5927,7 @@
method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
- method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
+ method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
@@ -9999,6 +10002,7 @@
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
method public java.lang.String getPackageName();
+ method public java.lang.String getText();
method public java.lang.String getTitle();
method public int getWeight();
method public boolean hasIconFile();
@@ -10026,6 +10030,7 @@
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
+ method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setWeight(int);
}
@@ -29125,6 +29130,7 @@
field public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1; // 0x1
field public static final deprecated int SCREEN_BRIGHT_WAKE_LOCK = 10; // 0xa
field public static final deprecated int SCREEN_DIM_WAKE_LOCK = 6; // 0x6
+ field public static final int SUSTAINED_PERFORMANCE_WAKE_LOCK = 256; // 0x100
}
public final class PowerManager.WakeLock {
@@ -30368,7 +30374,7 @@
method public final boolean isDestroyed();
method public final boolean isPrinterDiscoveryStarted();
method public abstract void onDestroy();
- method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.printservice.CustomPrinterIconCallback);
+ method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.os.CancellationSignal, android.printservice.CustomPrinterIconCallback);
method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
method public abstract void onStartPrinterStateTracking(android.print.PrinterId);
method public abstract void onStopPrinterDiscovery();
@@ -44644,6 +44650,7 @@
ctor public BaseInputConnection(android.view.View, boolean);
method public boolean beginBatchEdit();
method public boolean clearMetaKeyStates(int);
+ method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
@@ -44811,6 +44818,7 @@
public abstract interface InputConnection {
method public abstract boolean beginBatchEdit();
method public abstract boolean clearMetaKeyStates(int);
+ method public abstract void closeConnection();
method public abstract boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public abstract boolean commitText(java.lang.CharSequence, int);
@@ -44843,6 +44851,7 @@
ctor public InputConnectionWrapper(android.view.inputmethod.InputConnection, boolean);
method public boolean beginBatchEdit();
method public boolean clearMetaKeyStates(int);
+ method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
@@ -48290,9 +48299,15 @@
method public void collapseActionView();
method public void dismissPopupMenus();
method public int getContentInsetEnd();
+ method public int getContentInsetEndWithActions();
method public int getContentInsetLeft();
method public int getContentInsetRight();
method public int getContentInsetStart();
+ method public int getContentInsetStartWithNavigation();
+ method public int getCurrentContentInsetEnd();
+ method public int getCurrentContentInsetLeft();
+ method public int getCurrentContentInsetRight();
+ method public int getCurrentContentInsetStart();
method public android.graphics.drawable.Drawable getLogo();
method public java.lang.CharSequence getLogoDescription();
method public android.view.Menu getMenu();
@@ -48311,6 +48326,8 @@
method public void inflateMenu(int);
method public boolean isOverflowMenuShowing();
method protected void onLayout(boolean, int, int, int, int);
+ method public void setContentInsetEndWithActions(int);
+ method public void setContentInsetStartWithNavigation(int);
method public void setContentInsetsAbsolute(int, int);
method public void setContentInsetsRelative(int, int);
method public void setLogo(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index e372d98..54f3834 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -511,9 +511,11 @@
field public static final int contentAuthority = 16843408; // 0x1010290
field public static final int contentDescription = 16843379; // 0x1010273
field public static final int contentInsetEnd = 16843860; // 0x1010454
+ field public static final int contentInsetEndWithActions = 16844070; // 0x1010526
field public static final int contentInsetLeft = 16843861; // 0x1010455
field public static final int contentInsetRight = 16843862; // 0x1010456
field public static final int contentInsetStart = 16843859; // 0x1010453
+ field public static final int contentInsetStartWithNavigation = 16844069; // 0x1010525
field public static final int contextClickable = 16844007; // 0x10104e7
field public static final int contextPopupMenuStyle = 16844034; // 0x1010502
field public static final int controlX1 = 16843772; // 0x10103fc
@@ -3538,7 +3540,7 @@
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
- method public void enterPictureInPicture();
+ method public void enterPictureInPictureMode();
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -3578,8 +3580,6 @@
method public android.view.Window getWindow();
method public android.view.WindowManager getWindowManager();
method public boolean hasWindowFocus();
- method public boolean inMultiWindow();
- method public boolean inPictureInPicture();
method public void invalidateOptionsMenu();
method public boolean isBackgroundVisibleBehind();
method public boolean isChangingConfigurations();
@@ -3587,7 +3587,10 @@
method public boolean isDestroyed();
method public boolean isFinishing();
method public boolean isImmersive();
+ method public boolean isInMultiWindowMode();
+ method public boolean isInPictureInPictureMode();
method public boolean isLocalVoiceInteractionSupported();
+ method public boolean isOverlayWithDecorCaptionEnabled();
method public boolean isTaskRoot();
method public boolean isVoiceInteraction();
method public boolean isVoiceInteractionRoot();
@@ -3635,7 +3638,7 @@
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
- method public void onMultiWindowChanged(boolean);
+ method public void onMultiWindowModeChanged(boolean);
method public boolean onNavigateUp();
method public boolean onNavigateUpFromChild(android.app.Activity);
method protected void onNewIntent(android.content.Intent);
@@ -3643,7 +3646,7 @@
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPanelClosed(int, android.view.Menu);
method protected void onPause();
- method public void onPictureInPictureChanged(boolean);
+ method public void onPictureInPictureModeChanged(boolean);
method protected void onPostCreate(android.os.Bundle);
method public void onPostCreate(android.os.Bundle, android.os.PersistableBundle);
method protected void onPostResume();
@@ -3681,7 +3684,6 @@
method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
method public void openContextMenu(android.view.View);
method public void openOptionsMenu();
- method public void overlayWithDecorCaption(boolean);
method public void overridePendingTransition(int, int);
method public void postponeEnterTransition();
method public void recreate();
@@ -3710,6 +3712,7 @@
method public void setImmersive(boolean);
method public void setIntent(android.content.Intent);
method public final void setMediaController(android.media.session.MediaController);
+ method public void setOverlayWithDecorCaptionEnabled(boolean);
method public final deprecated void setProgress(int);
method public final deprecated void setProgressBarIndeterminate(boolean);
method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -4567,11 +4570,11 @@
method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle);
method public void onLowMemory();
- method public void onMultiWindowChanged(boolean);
+ method public void onMultiWindowModeChanged(boolean);
method public boolean onOptionsItemSelected(android.view.MenuItem);
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPause();
- method public void onPictureInPictureChanged(boolean);
+ method public void onPictureInPictureModeChanged(boolean);
method public void onPrepareOptionsMenu(android.view.Menu);
method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
method public void onResume();
@@ -4652,11 +4655,11 @@
method public void dispatchDestroy();
method public void dispatchDestroyView();
method public void dispatchLowMemory();
- method public void dispatchMultiWindowChanged(boolean);
+ method public void dispatchMultiWindowModeChanged(boolean);
method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
method public void dispatchOptionsMenuClosed(android.view.Menu);
method public void dispatchPause();
- method public void dispatchPictureInPictureChanged(boolean);
+ method public void dispatchPictureInPictureModeChanged(boolean);
method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
method public void dispatchResume();
method public void dispatchStart();
@@ -5993,7 +5996,7 @@
method public deprecated java.lang.String getDeviceInitializerApp();
method public deprecated android.content.ComponentName getDeviceInitializerComponent();
method public java.lang.String getDeviceOwner();
- method public java.lang.String getDeviceOwnerLockScreenInfo();
+ method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
method public java.lang.String getDeviceOwnerNameOnAnyUser();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
@@ -6037,7 +6040,7 @@
method public boolean hasGrantedPolicy(android.content.ComponentName, int);
method public boolean installCaCert(android.content.ComponentName, byte[]);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
- method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String, boolean);
+ method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -6072,7 +6075,7 @@
method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
- method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
+ method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
@@ -10397,6 +10400,7 @@
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
method public java.lang.String getPackageName();
+ method public java.lang.String getText();
method public java.lang.String getTitle();
method public int getWeight();
method public boolean hasIconFile();
@@ -10424,6 +10428,7 @@
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
+ method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setWeight(int);
}
@@ -26815,7 +26820,9 @@
method public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings);
method public boolean getScanResults();
method public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener);
+ method public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource);
method public void startScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener);
+ method public void startScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource);
method public void startTrackingBssids(android.net.wifi.WifiScanner.BssidInfo[], int, android.net.wifi.WifiScanner.BssidListener);
method public void startTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
method public void stopBackgroundScan(android.net.wifi.WifiScanner.ScanListener);
@@ -31370,6 +31377,7 @@
field public static final deprecated int SCREEN_BRIGHT_WAKE_LOCK = 10; // 0xa
field public static final deprecated int SCREEN_DIM_WAKE_LOCK = 6; // 0x6
field public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; // 0x3
+ field public static final int SUSTAINED_PERFORMANCE_WAKE_LOCK = 256; // 0x100
field public static final int USER_ACTIVITY_EVENT_BUTTON = 1; // 0x1
field public static final int USER_ACTIVITY_EVENT_OTHER = 0; // 0x0
field public static final int USER_ACTIVITY_EVENT_TOUCH = 2; // 0x2
@@ -32683,7 +32691,7 @@
method public final boolean isDestroyed();
method public final boolean isPrinterDiscoveryStarted();
method public abstract void onDestroy();
- method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.printservice.CustomPrinterIconCallback);
+ method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.os.CancellationSignal, android.printservice.CustomPrinterIconCallback);
method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
method public abstract void onStartPrinterStateTracking(android.print.PrinterId);
method public abstract void onStopPrinterDiscovery();
@@ -47373,6 +47381,7 @@
ctor public BaseInputConnection(android.view.View, boolean);
method public boolean beginBatchEdit();
method public boolean clearMetaKeyStates(int);
+ method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
@@ -47540,6 +47549,7 @@
public abstract interface InputConnection {
method public abstract boolean beginBatchEdit();
method public abstract boolean clearMetaKeyStates(int);
+ method public abstract void closeConnection();
method public abstract boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public abstract boolean commitText(java.lang.CharSequence, int);
@@ -47572,6 +47582,7 @@
ctor public InputConnectionWrapper(android.view.inputmethod.InputConnection, boolean);
method public boolean beginBatchEdit();
method public boolean clearMetaKeyStates(int);
+ method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
@@ -51354,9 +51365,15 @@
method public void collapseActionView();
method public void dismissPopupMenus();
method public int getContentInsetEnd();
+ method public int getContentInsetEndWithActions();
method public int getContentInsetLeft();
method public int getContentInsetRight();
method public int getContentInsetStart();
+ method public int getContentInsetStartWithNavigation();
+ method public int getCurrentContentInsetEnd();
+ method public int getCurrentContentInsetLeft();
+ method public int getCurrentContentInsetRight();
+ method public int getCurrentContentInsetStart();
method public android.graphics.drawable.Drawable getLogo();
method public java.lang.CharSequence getLogoDescription();
method public android.view.Menu getMenu();
@@ -51375,6 +51392,8 @@
method public void inflateMenu(int);
method public boolean isOverflowMenuShowing();
method protected void onLayout(boolean, int, int, int, int);
+ method public void setContentInsetEndWithActions(int);
+ method public void setContentInsetStartWithNavigation(int);
method public void setContentInsetsAbsolute(int, int);
method public void setContentInsetsRelative(int, int);
method public void setLogo(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index e4153f1..3d03d5b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -416,9 +416,11 @@
field public static final int contentAuthority = 16843408; // 0x1010290
field public static final int contentDescription = 16843379; // 0x1010273
field public static final int contentInsetEnd = 16843860; // 0x1010454
+ field public static final int contentInsetEndWithActions = 16844070; // 0x1010526
field public static final int contentInsetLeft = 16843861; // 0x1010455
field public static final int contentInsetRight = 16843862; // 0x1010456
field public static final int contentInsetStart = 16843859; // 0x1010453
+ field public static final int contentInsetStartWithNavigation = 16844069; // 0x1010525
field public static final int contextClickable = 16844007; // 0x10104e7
field public static final int contextPopupMenuStyle = 16844034; // 0x1010502
field public static final int controlX1 = 16843772; // 0x10103fc
@@ -3423,7 +3425,7 @@
method public boolean dispatchTouchEvent(android.view.MotionEvent);
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
- method public void enterPictureInPicture();
+ method public void enterPictureInPictureMode();
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -3463,15 +3465,16 @@
method public android.view.Window getWindow();
method public android.view.WindowManager getWindowManager();
method public boolean hasWindowFocus();
- method public boolean inMultiWindow();
- method public boolean inPictureInPicture();
method public void invalidateOptionsMenu();
method public boolean isChangingConfigurations();
method public final boolean isChild();
method public boolean isDestroyed();
method public boolean isFinishing();
method public boolean isImmersive();
+ method public boolean isInMultiWindowMode();
+ method public boolean isInPictureInPictureMode();
method public boolean isLocalVoiceInteractionSupported();
+ method public boolean isOverlayWithDecorCaptionEnabled();
method public boolean isTaskRoot();
method public boolean isVoiceInteraction();
method public boolean isVoiceInteractionRoot();
@@ -3518,7 +3521,7 @@
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
- method public void onMultiWindowChanged(boolean);
+ method public void onMultiWindowModeChanged(boolean);
method public boolean onNavigateUp();
method public boolean onNavigateUpFromChild(android.app.Activity);
method protected void onNewIntent(android.content.Intent);
@@ -3526,7 +3529,7 @@
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPanelClosed(int, android.view.Menu);
method protected void onPause();
- method public void onPictureInPictureChanged(boolean);
+ method public void onPictureInPictureModeChanged(boolean);
method protected void onPostCreate(android.os.Bundle);
method public void onPostCreate(android.os.Bundle, android.os.PersistableBundle);
method protected void onPostResume();
@@ -3564,7 +3567,6 @@
method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
method public void openContextMenu(android.view.View);
method public void openOptionsMenu();
- method public void overlayWithDecorCaption(boolean);
method public void overridePendingTransition(int, int);
method public void postponeEnterTransition();
method public void recreate();
@@ -3593,6 +3595,7 @@
method public void setImmersive(boolean);
method public void setIntent(android.content.Intent);
method public final void setMediaController(android.media.session.MediaController);
+ method public void setOverlayWithDecorCaptionEnabled(boolean);
method public final deprecated void setProgress(int);
method public final deprecated void setProgressBarIndeterminate(boolean);
method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -4435,11 +4438,11 @@
method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle);
method public void onLowMemory();
- method public void onMultiWindowChanged(boolean);
+ method public void onMultiWindowModeChanged(boolean);
method public boolean onOptionsItemSelected(android.view.MenuItem);
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPause();
- method public void onPictureInPictureChanged(boolean);
+ method public void onPictureInPictureModeChanged(boolean);
method public void onPrepareOptionsMenu(android.view.Menu);
method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
method public void onResume();
@@ -4520,11 +4523,11 @@
method public void dispatchDestroy();
method public void dispatchDestroyView();
method public void dispatchLowMemory();
- method public void dispatchMultiWindowChanged(boolean);
+ method public void dispatchMultiWindowModeChanged(boolean);
method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
method public void dispatchOptionsMenuClosed(android.view.Menu);
method public void dispatchPause();
- method public void dispatchPictureInPictureChanged(boolean);
+ method public void dispatchPictureInPictureModeChanged(boolean);
method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
method public void dispatchResume();
method public void dispatchStart();
@@ -5857,7 +5860,7 @@
method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
- method public java.lang.String getDeviceOwnerLockScreenInfo();
+ method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
method public java.lang.String getLongSupportMessage(android.content.ComponentName);
@@ -5895,7 +5898,7 @@
method public boolean hasGrantedPolicy(android.content.ComponentName, int);
method public boolean installCaCert(android.content.ComponentName, byte[]);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
- method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String, boolean);
+ method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -5928,7 +5931,7 @@
method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
- method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
+ method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
@@ -10009,6 +10012,7 @@
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
method public java.lang.String getPackageName();
+ method public java.lang.String getText();
method public java.lang.String getTitle();
method public int getWeight();
method public boolean hasIconFile();
@@ -10036,6 +10040,7 @@
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
+ method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setWeight(int);
}
@@ -29190,6 +29195,7 @@
field public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1; // 0x1
field public static final deprecated int SCREEN_BRIGHT_WAKE_LOCK = 10; // 0xa
field public static final deprecated int SCREEN_DIM_WAKE_LOCK = 6; // 0x6
+ field public static final int SUSTAINED_PERFORMANCE_WAKE_LOCK = 256; // 0x100
}
public final class PowerManager.WakeLock {
@@ -30437,7 +30443,7 @@
method public final boolean isDestroyed();
method public final boolean isPrinterDiscoveryStarted();
method public abstract void onDestroy();
- method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.printservice.CustomPrinterIconCallback);
+ method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.os.CancellationSignal, android.printservice.CustomPrinterIconCallback);
method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
method public abstract void onStartPrinterStateTracking(android.print.PrinterId);
method public abstract void onStopPrinterDiscovery();
@@ -44718,6 +44724,7 @@
ctor public BaseInputConnection(android.view.View, boolean);
method public boolean beginBatchEdit();
method public boolean clearMetaKeyStates(int);
+ method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
@@ -44885,6 +44892,7 @@
public abstract interface InputConnection {
method public abstract boolean beginBatchEdit();
method public abstract boolean clearMetaKeyStates(int);
+ method public abstract void closeConnection();
method public abstract boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public abstract boolean commitText(java.lang.CharSequence, int);
@@ -44917,6 +44925,7 @@
ctor public InputConnectionWrapper(android.view.inputmethod.InputConnection, boolean);
method public boolean beginBatchEdit();
method public boolean clearMetaKeyStates(int);
+ method public void closeConnection();
method public boolean commitCompletion(android.view.inputmethod.CompletionInfo);
method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
method public boolean commitText(java.lang.CharSequence, int);
@@ -48364,9 +48373,15 @@
method public void collapseActionView();
method public void dismissPopupMenus();
method public int getContentInsetEnd();
+ method public int getContentInsetEndWithActions();
method public int getContentInsetLeft();
method public int getContentInsetRight();
method public int getContentInsetStart();
+ method public int getContentInsetStartWithNavigation();
+ method public int getCurrentContentInsetEnd();
+ method public int getCurrentContentInsetLeft();
+ method public int getCurrentContentInsetRight();
+ method public int getCurrentContentInsetStart();
method public android.graphics.drawable.Drawable getLogo();
method public java.lang.CharSequence getLogoDescription();
method public android.view.Menu getMenu();
@@ -48385,6 +48400,8 @@
method public void inflateMenu(int);
method public boolean isOverflowMenuShowing();
method protected void onLayout(boolean, int, int, int, int);
+ method public void setContentInsetEndWithActions(int);
+ method public void setContentInsetStartWithNavigation(int);
method public void setContentInsetsAbsolute(int, int);
method public void setContentInsetsRelative(int, int);
method public void setLogo(int);
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 4025553..d44a1df 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -930,7 +930,7 @@
// In non-split user mode, userId can only be SYSTEM
int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
info = mUm.createRestrictedProfile(name, parentUserId);
- mAm.addSharedAccountsFromParentUser(userId, parentUserId);
+ mAm.addSharedAccountsFromParentUser(parentUserId, userId);
} else if (userId < 0) {
info = mUm.createUser(name, flags);
} else {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 887932a..0410a6e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -51,12 +51,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.ShapeDrawable;
import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.media.session.MediaController;
@@ -121,7 +116,6 @@
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ToolbarActionBar;
import com.android.internal.app.WindowDecorActionBar;
-import com.android.internal.policy.DecorView;
import com.android.internal.policy.PhoneWindow;
import java.io.FileDescriptor;
@@ -1855,15 +1849,15 @@
* visa-versa.
* @see android.R.attr#resizeableActivity
*
- * @param inMultiWindow True if the activity is in multi-window mode.
+ * @param isInMultiWindowMode True if the activity is in multi-window mode.
*/
@CallSuper
- public void onMultiWindowChanged(boolean inMultiWindow) {
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
if (DEBUG_LIFECYCLE) Slog.v(TAG,
- "onMultiWindowChanged " + this + ": " + inMultiWindow);
- mFragments.dispatchMultiWindowChanged(inMultiWindow);
+ "onMultiWindowModeChanged " + this + ": " + isInMultiWindowMode);
+ mFragments.dispatchMultiWindowModeChanged(isInMultiWindowMode);
if (mWindow != null) {
- mWindow.onMultiWindowChanged();
+ mWindow.onMultiWindowModeChanged();
}
}
@@ -1873,9 +1867,9 @@
*
* @return True if the activity is in multi-window mode.
*/
- public boolean inMultiWindow() {
+ public boolean isInMultiWindowMode() {
try {
- return ActivityManagerNative.getDefault().inMultiWindow(mToken);
+ return ActivityManagerNative.getDefault().isInMultiWindowMode(mToken);
} catch (RemoteException e) {
}
return false;
@@ -1885,13 +1879,13 @@
* Called by the system when the activity changes to and from picture-in-picture mode.
* @see android.R.attr#supportsPictureInPicture
*
- * @param inPictureInPicture True if the activity is in picture-in-picture mode.
+ * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
*/
@CallSuper
- public void onPictureInPictureChanged(boolean inPictureInPicture) {
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
if (DEBUG_LIFECYCLE) Slog.v(TAG,
- "onPictureInPictureChanged " + this + ": " + inPictureInPicture);
- mFragments.dispatchPictureInPictureChanged(inPictureInPicture);
+ "onPictureInPictureModeChanged " + this + ": " + isInPictureInPictureMode);
+ mFragments.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
}
/**
@@ -1900,9 +1894,9 @@
*
* @return True if the activity is in picture-in-picture mode.
*/
- public boolean inPictureInPicture() {
+ public boolean isInPictureInPictureMode() {
try {
- return ActivityManagerNative.getDefault().inPictureInPicture(mToken);
+ return ActivityManagerNative.getDefault().isInPictureInPictureMode(mToken);
} catch (RemoteException e) {
}
return false;
@@ -1912,9 +1906,9 @@
* Puts the activity in picture-in-picture mode.
* @see android.R.attr#supportsPictureInPicture
*/
- public void enterPictureInPicture() {
+ public void enterPictureInPictureMode() {
try {
- ActivityManagerNative.getDefault().enterPictureInPicture(mToken);
+ ActivityManagerNative.getDefault().enterPictureInPictureMode(mToken);
} catch (RemoteException e) {
}
}
@@ -5920,6 +5914,9 @@
* @return true if this is the topmost, non-finishing activity in its task.
*/
private boolean isTopOfTask() {
+ if (mToken == null || mWindow == null || !mWindowAdded) {
+ return false;
+ }
try {
return ActivityManagerNative.getDefault().isTopOfTask(mToken);
} catch (RemoteException e) {
@@ -6912,14 +6909,25 @@
}
/**
+ * Check whether the caption on freeform windows is displayed directly on the content.
+ *
+ * @return True if caption is displayed on content, false if it pushes the content down.
+ *
+ * @see {@link #setOverlayWithDecorCaptionEnabled(boolean)}
+ */
+ public boolean isOverlayWithDecorCaptionEnabled() {
+ return mWindow.isOverlayWithDecorCaptionEnabled();
+ }
+
+ /**
* Set whether the caption should displayed directly on the content rather than push it down.
*
* This affects only freeform windows since they display the caption and only the main
* window of the activity. The caption is used to drag the window around and also shows
* maximize and close action buttons.
*/
- public void overlayWithDecorCaption(boolean overlay) {
- mWindow.setOverlayDecorCaption(overlay);
+ public void setOverlayWithDecorCaptionEnabled(boolean enabled) {
+ mWindow.setOverlayWithDecorCaptionEnabled(enabled);
}
/**
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4bf48a3..33621852 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2890,7 +2890,7 @@
case IN_MULTI_WINDOW_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final IBinder token = data.readStrongBinder();
- final boolean inMultiWindow = inMultiWindow(token);
+ final boolean inMultiWindow = isInMultiWindowMode(token);
reply.writeNoException();
reply.writeInt(inMultiWindow ? 1 : 0);
return true;
@@ -2898,7 +2898,7 @@
case IN_PICTURE_IN_PICTURE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final IBinder token = data.readStrongBinder();
- final boolean inPip = inPictureInPicture(token);
+ final boolean inPip = isInPictureInPictureMode(token);
reply.writeNoException();
reply.writeInt(inPip ? 1 : 0);
return true;
@@ -2906,7 +2906,7 @@
case ENTER_PICTURE_IN_PICTURE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final IBinder token = data.readStrongBinder();
- enterPictureInPicture(token);
+ enterPictureInPictureMode(token);
reply.writeNoException();
return true;
}
@@ -6837,7 +6837,7 @@
}
@Override
- public boolean inMultiWindow(IBinder token) throws RemoteException {
+ public boolean isInMultiWindowMode(IBinder token) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -6851,7 +6851,7 @@
}
@Override
- public boolean inPictureInPicture(IBinder token) throws RemoteException {
+ public boolean isInPictureInPictureMode(IBinder token) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -6865,7 +6865,7 @@
}
@Override
- public void enterPictureInPicture(IBinder token) throws RemoteException {
+ public void enterPictureInPictureMode(IBinder token) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index dbc6c84..5b94696 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1284,15 +1284,15 @@
}
@Override
- public void scheduleMultiWindowChanged(IBinder token, boolean inMultiWindow)
+ public void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode)
throws RemoteException {
- sendMessage(H.MULTI_WINDOW_CHANGED, token, inMultiWindow ? 1 : 0);
+ sendMessage(H.MULTI_WINDOW_MODE_CHANGED, token, isInMultiWindowMode ? 1 : 0);
}
@Override
- public void schedulePictureInPictureChanged(IBinder token, boolean inPip)
+ public void schedulePictureInPictureModeChanged(IBinder token, boolean isInPipMode)
throws RemoteException {
- sendMessage(H.PICTURE_IN_PICTURE_CHANGED, token, inPip ? 1 : 0);
+ sendMessage(H.PICTURE_IN_PICTURE_MODE_CHANGED, token, isInPipMode ? 1 : 0);
}
@Override
@@ -1364,8 +1364,8 @@
public static final int ENTER_ANIMATION_COMPLETE = 149;
public static final int START_BINDER_TRACKING = 150;
public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
- public static final int MULTI_WINDOW_CHANGED = 152;
- public static final int PICTURE_IN_PICTURE_CHANGED = 153;
+ public static final int MULTI_WINDOW_MODE_CHANGED = 152;
+ public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
String codeToString(int code) {
@@ -1420,8 +1420,8 @@
case CANCEL_VISIBLE_BEHIND: return "CANCEL_VISIBLE_BEHIND";
case BACKGROUND_VISIBLE_BEHIND_CHANGED: return "BACKGROUND_VISIBLE_BEHIND_CHANGED";
case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
- case MULTI_WINDOW_CHANGED: return "MULTI_WINDOW_CHANGED";
- case PICTURE_IN_PICTURE_CHANGED: return "PICTURE_IN_PICTURE_CHANGED";
+ case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED";
+ case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
}
}
@@ -1666,11 +1666,11 @@
case STOP_BINDER_TRACKING_AND_DUMP:
handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj);
break;
- case MULTI_WINDOW_CHANGED:
- handleMultiWindowChanged((IBinder) msg.obj, msg.arg1 == 1);
+ case MULTI_WINDOW_MODE_CHANGED:
+ handleMultiWindowModeChanged((IBinder) msg.obj, msg.arg1 == 1);
break;
- case PICTURE_IN_PICTURE_CHANGED:
- handlePictureInPictureChanged((IBinder) msg.obj, msg.arg1 == 1);
+ case PICTURE_IN_PICTURE_MODE_CHANGED:
+ handlePictureInPictureModeChanged((IBinder) msg.obj, msg.arg1 == 1);
break;
case LOCAL_VOICE_INTERACTION_STARTED:
handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
@@ -2924,17 +2924,17 @@
}
}
- private void handleMultiWindowChanged(IBinder token, boolean inMultiWindow) {
+ private void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode) {
final ActivityClientRecord r = mActivities.get(token);
if (r != null) {
- r.activity.onMultiWindowChanged(inMultiWindow);
+ r.activity.onMultiWindowModeChanged(isInMultiWindowMode);
}
}
- private void handlePictureInPictureChanged(IBinder token, boolean inPip) {
+ private void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode) {
final ActivityClientRecord r = mActivities.get(token);
if (r != null) {
- r.activity.onPictureInPictureChanged(inPip);
+ r.activity.onPictureInPictureModeChanged(isInPipMode);
}
}
@@ -5077,26 +5077,6 @@
*/
TimeZone.setDefault(null);
- synchronized (mResourcesManager) {
- /*
- * Initialize the default locales in this process for the reasons we set the time zone.
- *
- * We do this through ResourcesManager, since we need to do locale negotiation.
- */
- mResourcesManager.setDefaultLocalesLocked(data.config.getLocales());
-
- /*
- * Update the system configuration since its preloaded and might not
- * reflect configuration changes. The configuration object passed
- * in AppBindData can be safely assumed to be up to date
- */
- mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
- mCurDefaultDisplayDpi = data.config.densityDpi;
-
- // This calls mResourcesManager so keep it within the synchronized block.
- applyCompatConfiguration(mCurDefaultDisplayDpi);
- }
-
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
/**
@@ -5221,6 +5201,26 @@
}
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
+ synchronized (mResourcesManager) {
+ /*
+ * Initialize the default locales in this process for the reasons we set the time zone.
+ *
+ * We do this through ResourcesManager, since we need to do locale negotiation.
+ */
+ mResourcesManager.setDefaultLocalesLocked(data.config.getLocales());
+
+ /*
+ * Update the system configuration since its preloaded and might not
+ * reflect configuration changes. The configuration object passed
+ * in AppBindData can be safely assumed to be up to date
+ */
+ mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
+ mCurDefaultDisplayDpi = data.config.densityDpi;
+
+ // This calls mResourcesManager so keep it within the synchronized block.
+ applyCompatConfiguration(mCurDefaultDisplayDpi);
+ }
+
if (!Process.isIsolated() && !"android".equals(appContext.getPackageName())) {
// This cache location probably points at credential-encrypted
// storage which may not be accessible yet; assign it anyway instead
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 744ddf7..ea86dd0 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -736,7 +736,7 @@
data.enforceInterface(IApplicationThread.descriptor);
final IBinder b = data.readStrongBinder();
final boolean inMultiWindow = data.readInt() != 0;
- scheduleMultiWindowChanged(b, inMultiWindow);
+ scheduleMultiWindowModeChanged(b, inMultiWindow);
return true;
}
@@ -745,7 +745,7 @@
data.enforceInterface(IApplicationThread.descriptor);
final IBinder b = data.readStrongBinder();
final boolean inPip = data.readInt() != 0;
- schedulePictureInPictureChanged(b, inPip);
+ schedulePictureInPictureModeChanged(b, inPip);
return true;
}
@@ -1498,24 +1498,24 @@
}
@Override
- public final void scheduleMultiWindowChanged(
- IBinder token, boolean inMultiWindow) throws RemoteException {
+ public final void scheduleMultiWindowModeChanged(
+ IBinder token, boolean isInMultiWindowMode) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
- data.writeInt(inMultiWindow ? 1 : 0);
+ data.writeInt(isInMultiWindowMode ? 1 : 0);
mRemote.transact(SCHEDULE_MULTI_WINDOW_CHANGED_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
@Override
- public final void schedulePictureInPictureChanged(IBinder token, boolean inPip)
+ public final void schedulePictureInPictureModeChanged(IBinder token, boolean isInPipMode)
throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
- data.writeInt(inPip ? 1 : 0);
+ data.writeInt(isInPipMode ? 1 : 0);
mRemote.transact(SCHEDULE_PICTURE_IN_PICTURE_CHANGED_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d28f1fb..6bb853a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1993,7 +1993,7 @@
ContextImpl context = new ContextImpl(null, mainThread,
packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
- context.mResourcesManager.getDisplayMetricsLocked());
+ context.mResourcesManager.getDisplayMetrics());
return context;
}
@@ -2065,16 +2065,34 @@
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
- resources = mResourcesManager.getResources(
- activityToken,
- packageInfo.getResDir(),
- packageInfo.getSplitResDirs(),
- packageInfo.getOverlayDirs(),
- packageInfo.getApplicationInfo().sharedLibraryFiles,
- displayId,
- overrideConfiguration,
- compatInfo,
- packageInfo.getClassLoader());
+
+ if (container != null) {
+ // This is a nested Context, so it can't be a base Activity context.
+ // Just create a regular Resources object associated with the Activity.
+ resources = mResourcesManager.getResources(
+ activityToken,
+ packageInfo.getResDir(),
+ packageInfo.getSplitResDirs(),
+ packageInfo.getOverlayDirs(),
+ packageInfo.getApplicationInfo().sharedLibraryFiles,
+ displayId,
+ overrideConfiguration,
+ compatInfo,
+ packageInfo.getClassLoader());
+ } else {
+ // This is not a nested Context, so it must be the root Activity context.
+ // All other nested Contexts will inherit the configuration set here.
+ resources = mResourcesManager.createBaseActivityResources(
+ activityToken,
+ packageInfo.getResDir(),
+ packageInfo.getSplitResDirs(),
+ packageInfo.getOverlayDirs(),
+ packageInfo.getApplicationInfo().sharedLibraryFiles,
+ displayId,
+ overrideConfiguration,
+ compatInfo,
+ packageInfo.getClassLoader());
+ }
}
}
mResources = resources;
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index f7a4557..bb3c719 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -1582,21 +1582,21 @@
/**
* Called when the Fragment's activity changes from fullscreen mode to multi-window mode and
- * visa-versa. This is generally tied to {@link Activity#onMultiWindowChanged} of the containing
- * Activity.
+ * visa-versa. This is generally tied to {@link Activity#onMultiWindowModeChanged} of the
+ * containing Activity.
*
- * @param inMultiWindow True if the activity is in multi-window mode.
+ * @param isInMultiWindowMode True if the activity is in multi-window mode.
*/
- public void onMultiWindowChanged(boolean inMultiWindow) {
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
}
/**
* Called by the system when the activity changes to and from picture-in-picture mode. This is
- * generally tied to {@link Activity#onPictureInPictureChanged} of the containing Activity.
+ * generally tied to {@link Activity#onPictureInPictureModeChanged} of the containing Activity.
*
- * @param inPictureInPicture True if the activity is in picture-in-picture mode.
+ * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
*/
- public void onPictureInPictureChanged(boolean inPictureInPicture) {
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
}
public void onConfigurationChanged(Configuration newConfig) {
@@ -2334,17 +2334,17 @@
}
}
- void performMultiWindowChanged(boolean inMultiWindow) {
- onMultiWindowChanged(inMultiWindow);
+ void performMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ onMultiWindowModeChanged(isInMultiWindowMode);
if (mChildFragmentManager != null) {
- mChildFragmentManager.dispatchMultiWindowChanged(inMultiWindow);
+ mChildFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode);
}
}
- void performPictureInPictureChanged(boolean inPictureInPicture) {
- onPictureInPictureChanged(inPictureInPicture);
+ void performPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ onPictureInPictureModeChanged(isInPictureInPictureMode);
if (mChildFragmentManager != null) {
- mChildFragmentManager.dispatchPictureInPictureChanged(inPictureInPicture);
+ mChildFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
}
}
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index 57b0ff1..b3d2df5 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -247,10 +247,10 @@
* the activity changed.
* <p>Call when the multi-window mode of the activity changed.
*
- * @see Fragment#onMultiWindowChanged
+ * @see Fragment#onMultiWindowModeChanged
*/
- public void dispatchMultiWindowChanged(boolean inMultiWindow) {
- mHost.mFragmentManager.dispatchMultiWindowChanged(inMultiWindow);
+ public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ mHost.mFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode);
}
/**
@@ -258,10 +258,10 @@
* mode of the activity changed.
* <p>Call when the picture-in-picture mode of the activity changed.
*
- * @see Fragment#onPictureInPictureChanged
+ * @see Fragment#onPictureInPictureModeChanged
*/
- public void dispatchPictureInPictureChanged(boolean inPictureInPicture) {
- mHost.mFragmentManager.dispatchPictureInPictureChanged(inPictureInPicture);
+ public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ mHost.mFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
}
/**
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 2852baf..8369f17 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -2069,26 +2069,26 @@
mParent = null;
}
- public void dispatchMultiWindowChanged(boolean inMultiWindow) {
+ public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
if (mAdded == null) {
return;
}
for (int i = mAdded.size() - 1; i >= 0; --i) {
final Fragment f = mAdded.get(i);
if (f != null) {
- f.performMultiWindowChanged(inMultiWindow);
+ f.performMultiWindowModeChanged(isInMultiWindowMode);
}
}
}
- public void dispatchPictureInPictureChanged(boolean inPictureInPicture) {
+ public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
if (mAdded == null) {
return;
}
for (int i = mAdded.size() - 1; i >= 0; --i) {
final Fragment f = mAdded.get(i);
if (f != null) {
- f.performPictureInPictureChanged(inPictureInPicture);
+ f.performPictureInPictureModeChanged(isInPictureInPictureMode);
}
}
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 417c0679..8ee6fd0 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -623,11 +623,11 @@
public int getAppStartMode(int uid, String packageName) throws RemoteException;
- public boolean inMultiWindow(IBinder token) throws RemoteException;
+ public boolean isInMultiWindowMode(IBinder token) throws RemoteException;
- public boolean inPictureInPicture(IBinder token) throws RemoteException;
+ public boolean isInPictureInPictureMode(IBinder token) throws RemoteException;
- public void enterPictureInPicture(IBinder token) throws RemoteException;
+ public void enterPictureInPictureMode(IBinder token) throws RemoteException;
public int setVrMode(IBinder token, boolean enabled, ComponentName packageName)
throws RemoteException;
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 628bde0..a3b2638 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -158,8 +158,8 @@
void notifyCleartextNetwork(byte[] firstPacket) throws RemoteException;
void startBinderTracking() throws RemoteException;
void stopBinderTrackingAndDump(FileDescriptor fd) throws RemoteException;
- void scheduleMultiWindowChanged(IBinder token, boolean multiWindowMode) throws RemoteException;
- void schedulePictureInPictureChanged(IBinder token, boolean multiWindowMode) throws RemoteException;
+ void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode) throws RemoteException;
+ void schedulePictureInPictureModeChanged(IBinder token, boolean isInPictureInPictureMode) throws RemoteException;
void scheduleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor voiceInteractor) throws RemoteException;
String descriptor = "android.app.IApplicationThread";
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 54d813d..3f22385 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -66,7 +66,7 @@
}
};
- private String[] mSystemLocales = {};
+ private String[] mSystemLocales = null;
private final HashSet<String> mNonSystemLocales = new HashSet<>();
private boolean mHasNonSystemLocales = false;
@@ -94,9 +94,18 @@
private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
/**
- * Each Activity may have only one Resources object.
+ * Resources and base configuration override associated with an Activity.
*/
- private final WeakHashMap<IBinder, WeakReference<Resources>> mActivityResourceReferences =
+ private static class ActivityResources {
+ public final Configuration overrideConfig = new Configuration();
+ public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
+ }
+
+ /**
+ * Each Activity may has a base override configuration that is applied to each Resources object,
+ * which in turn may have their own override configuration specified.
+ */
+ private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
new WeakHashMap<>();
/**
@@ -115,18 +124,20 @@
}
public Configuration getConfiguration() {
- return mResConfiguration;
+ synchronized (this) {
+ return mResConfiguration;
+ }
}
- DisplayMetrics getDisplayMetricsLocked() {
- return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
+ DisplayMetrics getDisplayMetrics() {
+ return getDisplayMetrics(Display.DEFAULT_DISPLAY);
}
/**
* Protected so that tests can override and returns something a fixed value.
*/
@VisibleForTesting
- protected DisplayMetrics getDisplayMetricsLocked(int displayId) {
+ protected DisplayMetrics getDisplayMetrics(int displayId) {
DisplayMetrics dm = new DisplayMetrics();
final Display display =
getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
@@ -272,10 +283,9 @@
return config;
}
-
private ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
AssetManager assets = createAssetManager(key);
- DisplayMetrics dm = getDisplayMetricsLocked(key.mDisplayId);
+ DisplayMetrics dm = getDisplayMetrics(key.mDisplayId);
Configuration config = generateConfig(key, dm);
ResourcesImpl impl = new ResourcesImpl(assets, dm, config, key.mCompatInfo);
if (DEBUG) {
@@ -290,7 +300,7 @@
* @param key The key to match.
* @return a ResourcesImpl if the key matches a cache entry, null otherwise.
*/
- private ResourcesImpl findResourcesImplForKey(@NonNull ResourcesKey key) {
+ private ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
if (impl != null && impl.getAssets().isUpToDate()) {
@@ -303,7 +313,7 @@
* Find the ResourcesKey that this ResourcesImpl object is associated with.
* @return the ResourcesKey or null if none was found.
*/
- private ResourcesKey findKeyForResourceImpl(@NonNull ResourcesImpl resourceImpl) {
+ private ResourcesKey findKeyForResourceImplLocked(@NonNull ResourcesImpl resourceImpl) {
final int refCount = mResourceImpls.size();
for (int i = 0; i < refCount; i++) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
@@ -315,36 +325,46 @@
return null;
}
+ private ActivityResources getOrCreateActivityResourcesStructLocked(
+ @NonNull IBinder activityToken) {
+ ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
+ if (activityResources == null) {
+ activityResources = new ActivityResources();
+ mActivityResourceReferences.put(activityToken, activityResources);
+ }
+ return activityResources;
+ }
+
/**
* Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
* or the class loader is different.
*/
private Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
- // This is a request tied to an Activity, meaning we will need to update all
- // Activity related Resources to match this configuration.
- WeakReference<Resources> weakResourceRef = mActivityResourceReferences.get(activityToken);
- Resources resources = weakResourceRef != null ? weakResourceRef.get() : null;
- if (resources == null || !Objects.equals(resources.getClassLoader(), classLoader)) {
- resources = new Resources(classLoader);
- mActivityResourceReferences.put(activityToken, new WeakReference<>(resources));
- if (DEBUG) {
- Slog.d(TAG, "- creating new ref=" + resources);
- }
- } else {
- if (DEBUG) {
- Slog.d(TAG, "- using existing ref=" + resources);
+ final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ activityToken);
+
+ final int refCount = activityResources.activityResources.size();
+ for (int i = 0; i < refCount; i++) {
+ WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
+ Resources resources = weakResourceRef.get();
+
+ if (resources != null
+ && Objects.equals(resources.getClassLoader(), classLoader)
+ && resources.getImpl() == impl) {
+ if (DEBUG) {
+ Slog.d(TAG, "- using existing ref=" + resources);
+ }
+ return resources;
}
}
- if (resources.getImpl() != impl) {
- if (DEBUG) {
- Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
- }
-
- // Setting an impl is expensive because we update all ThemeImpl references.
- // too.
- resources.setImpl(impl);
+ Resources resources = new Resources(classLoader);
+ resources.setImpl(impl);
+ activityResources.activityResources.add(new WeakReference<>(resources));
+ if (DEBUG) {
+ Slog.d(TAG, "- creating new ref=" + resources);
+ Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
@@ -359,7 +379,7 @@
final int refCount = mResourceReferences.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
- Resources resources = weakResourceRef != null ? weakResourceRef.get() : null;
+ Resources resources = weakResourceRef.get();
if (resources != null &&
Objects.equals(resources.getClassLoader(), classLoader) &&
resources.getImpl() == impl) {
@@ -382,6 +402,177 @@
}
/**
+ * Creates base resources for an Activity. Calls to
+ * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
+ * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override
+ * configurations merged with the one specified here.
+ *
+ * @param activityToken Represents an Activity.
+ * @param resDir The base resource path. Can be null (only framework resources will be loaded).
+ * @param splitResDirs An array of split resource paths. Can be null.
+ * @param overlayDirs An array of overlay paths. Can be null.
+ * @param libDirs An array of resource library paths. Can be null.
+ * @param displayId The ID of the display for which to create the resources.
+ * @param overrideConfig The configuration to apply on top of the base configuration. Can be
+ * null. This provides the base override for this Activity.
+ * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
+ * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
+ * @param classLoader The class loader to use when inflating Resources. If null, the
+ * {@link ClassLoader#getSystemClassLoader()} is used.
+ * @return a Resources object from which to access resources.
+ */
+ public Resources createBaseActivityResources(@NonNull IBinder activityToken,
+ @Nullable String resDir,
+ @Nullable String[] splitResDirs,
+ @Nullable String[] overlayDirs,
+ @Nullable String[] libDirs,
+ int displayId,
+ @Nullable Configuration overrideConfig,
+ @NonNull CompatibilityInfo compatInfo,
+ @Nullable ClassLoader classLoader) {
+ final ResourcesKey key = new ResourcesKey(
+ resDir,
+ splitResDirs,
+ overlayDirs,
+ libDirs,
+ displayId,
+ overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
+ compatInfo);
+ classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
+
+ synchronized (this) {
+ final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ activityToken);
+
+ if (overrideConfig != null) {
+ activityResources.overrideConfig.setTo(overrideConfig);
+ } else {
+ activityResources.overrideConfig.setToDefaults();
+ }
+ }
+
+ // Update any existing Activity Resources references.
+ updateResourcesForActivity(activityToken, overrideConfig);
+
+ // Now request an actual Resources object.
+ return getOrCreateResources(activityToken, key, classLoader);
+ }
+
+ /**
+ * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
+ * or creates one if it doesn't exist.
+ *
+ * @param activityToken The Activity this Resources object should be associated with.
+ * @param key The key describing the parameters of the ResourcesImpl object.
+ * @param classLoader The classloader to use for the Resources object.
+ * If null, {@link ClassLoader#getSystemClassLoader()} is used.
+ * @return A Resources object that gets updated when
+ * {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
+ * is called.
+ */
+ private Resources getOrCreateResources(@Nullable IBinder activityToken,
+ @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
+ final boolean findSystemLocales;
+ final boolean hasNonSystemLocales;
+ synchronized (this) {
+ findSystemLocales = (mSystemLocales == null || mSystemLocales.length == 0);
+ hasNonSystemLocales = mHasNonSystemLocales;
+
+ if (DEBUG) {
+ Throwable here = new Throwable();
+ here.fillInStackTrace();
+ Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
+ }
+
+ if (activityToken != null) {
+ final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ activityToken);
+
+ // Clean up any dead references so they don't pile up.
+ ArrayUtils.unstableRemoveIf(activityResources.activityResources,
+ sEmptyReferencePredicate);
+
+ // Rebase the key's override config on top of the Activity's base override.
+ if (key.hasOverrideConfiguration()
+ && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
+ final Configuration temp = new Configuration(activityResources.overrideConfig);
+ temp.updateFrom(key.mOverrideConfiguration);
+ key.mOverrideConfiguration.setTo(temp);
+ }
+
+ ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
+ if (resourcesImpl != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "- using existing impl=" + resourcesImpl);
+ }
+ return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
+ resourcesImpl);
+ }
+
+ // We will create the ResourcesImpl object outside of holding this lock.
+
+ } else {
+ // Clean up any dead references so they don't pile up.
+ ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
+
+ // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
+ ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
+ if (resourcesImpl != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "- using existing impl=" + resourcesImpl);
+ }
+ return getOrCreateResourcesLocked(classLoader, resourcesImpl);
+ }
+
+ // We will create the ResourcesImpl object outside of holding this lock.
+ }
+ }
+
+ // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
+ ResourcesImpl resourcesImpl = createResourcesImpl(key);
+
+ final String[] systemLocales = findSystemLocales
+ ? AssetManager.getSystem().getLocales() : null;
+ final String[] nonSystemLocales = resourcesImpl.getAssets().getNonSystemLocales();
+ // Avoid checking for non-pseudo-locales if we already know there were some from a previous
+ // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter,
+ // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be
+ // able to affect mHasNonSystemLocales.
+ final boolean isPseudoLocalesOnly = hasNonSystemLocales ||
+ LocaleList.isPseudoLocalesOnly(nonSystemLocales);
+
+ synchronized (this) {
+ if (mSystemLocales == null || mSystemLocales.length == 0) {
+ mSystemLocales = systemLocales;
+ }
+ mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales));
+ mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly;
+
+ ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
+ if (existingResourcesImpl != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
+ + " new impl=" + resourcesImpl);
+ }
+ resourcesImpl.getAssets().close();
+ resourcesImpl = existingResourcesImpl;
+ } else {
+ // Add this ResourcesImpl to the cache.
+ mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
+ }
+
+ final Resources resources;
+ if (activityToken != null) {
+ resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
+ resourcesImpl);
+ } else {
+ resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
+ }
+ return resources;
+ }
+ }
+
+ /**
* Gets or creates a new Resources object associated with the IBinder token. References returned
* by this method live as long as the Activity, meaning they can be cached and used by the
* Activity even after a configuration change. If any other parameter is changed
@@ -425,92 +616,8 @@
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
-
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
-
- final boolean findSystemLocales;
- final boolean hasNonSystemLocales;
- synchronized (this) {
- findSystemLocales = (mSystemLocales.length == 0);
- hasNonSystemLocales = mHasNonSystemLocales;
-
- if (DEBUG) {
- Throwable here = new Throwable();
- here.fillInStackTrace();
- Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
- }
-
- if (activityToken != null) {
- ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
- if (resourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing impl=" + resourcesImpl);
- }
- return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
- resourcesImpl);
- }
-
- // We will create the ResourcesImpl object outside of holding this lock.
-
- } else {
- // Clean up any dead references so they don't pile up.
- ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
-
- // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
- ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
- if (resourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing impl=" + resourcesImpl);
- }
- return getOrCreateResourcesLocked(classLoader, resourcesImpl);
- }
-
- // We will create the ResourcesImpl object outside of holding this lock.
- }
- }
-
- // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
- ResourcesImpl resourcesImpl = createResourcesImpl(key);
-
- final String[] systemLocales = findSystemLocales
- ? AssetManager.getSystem().getLocales() : null;
- final String[] nonSystemLocales = resourcesImpl.getAssets().getNonSystemLocales();
- // Avoid checking for non-pseudo-locales if we already know there were some from a previous
- // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter,
- // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be
- // able to affect mHasNonSystemLocales.
- final boolean isPseudoLocalesOnly = hasNonSystemLocales ||
- LocaleList.isPseudoLocalesOnly(nonSystemLocales);
-
- synchronized (this) {
- if (mSystemLocales.length == 0) {
- mSystemLocales = systemLocales;
- }
- mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales));
- mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly;
-
- ResourcesImpl existingResourcesImpl = findResourcesImplForKey(key);
- if (existingResourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
- + " new impl=" + resourcesImpl);
- }
- resourcesImpl.getAssets().close();
- resourcesImpl = existingResourcesImpl;
- } else {
- // Add this ResourcesImpl to the cache.
- mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
- }
-
- final Resources resources;
- if (activityToken != null) {
- resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
- resourcesImpl);
- } else {
- resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
- }
- return resources;
- }
+ return getOrCreateResources(activityToken, key, classLoader);
}
/**
@@ -524,34 +631,84 @@
*/
public void updateResourcesForActivity(@NonNull IBinder activityToken,
@Nullable Configuration overrideConfig) {
- final ClassLoader classLoader;
- final ResourcesKey oldKey;
synchronized (this) {
- // Extract the ResourcesKey that was last used to create the Resources for this
- // activity.
- WeakReference<Resources> weakResRef = mActivityResourceReferences.get(activityToken);
- final Resources resources = weakResRef != null ? weakResRef.get() : null;
- if (resources == null) {
- Slog.e(TAG, "can't update resources for uncached activity " + activityToken);
+ final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ activityToken);
+
+ if (Objects.equals(activityResources.overrideConfig, overrideConfig)) {
+ // They are the same, no work to do.
return;
}
- classLoader = resources.getClassLoader();
- oldKey = findKeyForResourceImpl(resources.getImpl());
- if (oldKey == null) {
- Slog.e(TAG, "can't find ResourcesKey for resources impl=" + resources.getImpl());
- return;
+ // Grab a copy of the old configuration so we can create the delta's of each
+ // Resources object associated with this Activity.
+ final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
+
+ // Update the Activity's base override.
+ if (overrideConfig != null) {
+ activityResources.overrideConfig.setTo(overrideConfig);
+ } else {
+ activityResources.overrideConfig.setToDefaults();
+ }
+
+ final boolean activityHasOverrideConfig =
+ !activityResources.overrideConfig.equals(Configuration.EMPTY);
+
+ // Rebase each Resources associated with this Activity.
+ final int refCount = activityResources.activityResources.size();
+ for (int i = 0; i < refCount; i++) {
+ WeakReference<Resources> weakResRef = activityResources.activityResources.get(i);
+ Resources resources = weakResRef.get();
+ if (resources == null) {
+ continue;
+ }
+
+ // Extract the ResourcesKey that was last used to create the Resources for this
+ // activity.
+ final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
+ if (oldKey == null) {
+ Slog.e(TAG, "can't find ResourcesKey for resources impl="
+ + resources.getImpl());
+ continue;
+ }
+
+ // Build the new override configuration for this ResourcesKey.
+ final Configuration rebasedOverrideConfig = new Configuration();
+ if (overrideConfig != null) {
+ rebasedOverrideConfig.setTo(overrideConfig);
+ }
+
+ if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
+ // Generate a delta between the old base Activity override configuration and
+ // the actual final override configuration that was used to figure out the real
+ // delta this Resources object wanted.
+ Configuration overrideOverrideConfig = Configuration.generateDelta(
+ oldConfig, oldKey.mOverrideConfiguration);
+ rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
+ }
+
+ // Create the new ResourcesKey with the rebased override config.
+ final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir, oldKey.mSplitResDirs,
+ oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId,
+ rebasedOverrideConfig, oldKey.mCompatInfo);
+
+ ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
+ if (resourcesImpl == null) {
+ resourcesImpl = createResourcesImpl(newKey);
+ }
+
+ if (resourcesImpl != resources.getImpl()) {
+ // Set the ResourcesImpl, updating it for all users of this Resources object.
+ resources.setImpl(resourcesImpl);
+ }
}
}
-
- // Update the Resources object with the new override config and all of the existing
- // settings.
- getResources(activityToken, oldKey.mResDir, oldKey.mSplitResDirs, oldKey.mOverlayDirs,
- oldKey.mLibDirs, oldKey.mDisplayId, overrideConfig, oldKey.mCompatInfo,
- classLoader);
}
/* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) {
+ if (mSystemLocales == null) {
+ throw new RuntimeException("ResourcesManager is not ready to negotiate locales.");
+ }
final int bestLocale;
if (mHasNonSystemLocales) {
bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales);
@@ -575,7 +732,7 @@
int changes = mResConfiguration.updateFrom(config);
// Things might have changed in display manager, so clear the cached displays.
mDisplays.clear();
- DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked();
+ DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
if (compat != null && (mResCompatibilityInfo == null ||
!mResCompatibilityInfo.equals(compat))) {
@@ -629,7 +786,7 @@
}
tmpConfig.setTo(localeAdjustedConfig);
if (!isDefaultDisplay) {
- dm = getDisplayMetricsLocked(displayId);
+ dm = getDisplayMetrics(displayId);
applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
}
if (hasOverrideConfiguration) {
@@ -649,4 +806,4 @@
return changes != 0;
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 2987fbc..bdc4404 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -882,7 +882,12 @@
public final T getService(ContextImpl ctx) {
synchronized (StaticApplicationContextServiceFetcher.this) {
if (mCachedInstance == null) {
- mCachedInstance = createService(ctx.getApplicationContext());
+ Context appContext = ctx.getApplicationContext();
+ // If the application context is null, we're either in the system process or
+ // it's the application context very early in app initialization. In both these
+ // cases, the passed-in ContextImpl will not be freed, so it's safe to pass it
+ // to the service. http://b/27532714 .
+ mCachedInstance = createService(appContext != null ? appContext : ctx);
}
return mCachedInstance;
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 518fc4f..6b4771c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -71,6 +71,7 @@
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -2782,7 +2783,7 @@
* compromised, certificates it had already installed will be protected.
*
* <p>If the installer must have access to the credentials, call
- * {@link #installKeyPair(ComponentName, PrivateKey, Certificate, String, boolean)} instead.
+ * {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, boolean)} instead.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if calling from a delegated certificate installer.
@@ -2796,13 +2797,14 @@
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
@NonNull Certificate cert, @NonNull String alias) {
- return installKeyPair(admin, privKey, cert, alias, false);
+ return installKeyPair(admin, privKey, new Certificate[] {cert}, alias, false);
}
/**
* Called by a device or profile owner, or delegated certificate installer, to install a
- * certificate and corresponding private key. All apps within the profile will be able to access
- * the certificate and use the private key, given direct user approval.
+ * certificate chain and corresponding private key for the leaf certificate. All apps within the
+ * profile will be able to access the certificate chain and use the private key, given direct
+ * user approval.
*
* <p>The caller of this API may grant itself access to the certificate and private key
* immediately, without user approval. It is a best practice not to request this unless strictly
@@ -2811,7 +2813,9 @@
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if calling from a delegated certificate installer.
* @param privKey The private key to install.
- * @param cert The certificate to install.
+ * @param certs The certificate chain to install. The chain should start with the leaf
+ * certificate and include the chain of trust in order. This will be returned by
+ * {@link android.security.KeyChain#getCertificateChain}.
* @param alias The private key alias under which to install the certificate. If a certificate
* with that alias already exists, it will be overwritten.
* @param requestAccess {@code true} to request that the calling app be granted access to the
@@ -2820,14 +2824,20 @@
* @return {@code true} if the keys were installed, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
+ * @see android.security.KeyChain#getCertificateChain
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
- @NonNull Certificate cert, @NonNull String alias, boolean requestAccess) {
+ @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
try {
- final byte[] pemCert = Credentials.convertToPem(cert);
+ final byte[] pemCert = Credentials.convertToPem(certs[0]);
+ byte[] pemChain = null;
+ if (certs.length > 1) {
+ pemChain = Credentials.convertToPem(Arrays.copyOfRange(certs, 1, certs.length));
+ }
final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
.getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
- return mService.installKeyPair(admin, pkcs8Key, pemCert, alias, requestAccess);
+ return mService.installKeyPair(admin, pkcs8Key, pemCert, pemChain, alias,
+ requestAccess);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
@@ -3736,24 +3746,22 @@
*
* @param admin The name of the admin component to check.
* @param info Device owner information which will be displayed instead of the user owner info.
- * @return Whether the device owner information has been set.
* @throws SecurityException if {@code admin} is not a device owner.
*/
- public boolean setDeviceOwnerLockScreenInfo(@NonNull ComponentName admin, String info) {
+ public void setDeviceOwnerLockScreenInfo(@NonNull ComponentName admin, CharSequence info) {
if (mService != null) {
try {
- return mService.setDeviceOwnerLockScreenInfo(admin, info);
+ mService.setDeviceOwnerLockScreenInfo(admin, info);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
- return false;
}
/**
* @return The device owner information. If it is not set returns {@code null}.
*/
- public String getDeviceOwnerLockScreenInfo() {
+ public CharSequence getDeviceOwnerLockScreenInfo() {
if (mService != null) {
try {
return mService.getDeviceOwnerLockScreenInfo();
@@ -5934,7 +5942,7 @@
/**
* Called by a profile owner of a managed profile to set the color used for customization. This
* color is used as background color of the confirm credentials screen for that user. The
- * default color is {@link android.graphics.Color#GRAY}.
+ * default color is teal (#00796B).
* <p>
* The confirm credentials screen can be created using
* {@link android.app.KeyguardManager#createConfirmDeviceCredentialIntent}.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 39c3a44..c3d5ed9 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -135,8 +135,8 @@
void clearProfileOwner(in ComponentName who);
boolean hasUserSetupCompleted();
- boolean setDeviceOwnerLockScreenInfo(in ComponentName who, String deviceOwnerInfo);
- String getDeviceOwnerLockScreenInfo();
+ void setDeviceOwnerLockScreenInfo(in ComponentName who, CharSequence deviceOwnerInfo);
+ CharSequence getDeviceOwnerLockScreenInfo();
String[] setPackagesSuspended(in ComponentName admin, in String[] packageNames, boolean suspended);
boolean getPackageSuspended(in ComponentName admin, String packageName);
@@ -146,7 +146,7 @@
void enforceCanManageCaCerts(in ComponentName admin);
boolean installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer,
- String alias, boolean requestAccess);
+ in byte[] certChainBuffer, String alias, boolean requestAccess);
boolean removeKeyPair(in ComponentName who, String alias);
void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 68442ea..b8a40dc 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -422,7 +422,7 @@
try {
mAuthRetry = true;
mService.writeDescriptor(mClientIf, address, handle,
- descriptor.getCharacteristic().getWriteType(),
+ BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT,
AUTHENTICATION_MITM, descriptor.getValue());
return;
} catch (RemoteException e) {
@@ -545,7 +545,6 @@
/*package*/ BluetoothGattCharacteristic getCharacteristicById(BluetoothDevice device, int instanceId) {
for(BluetoothGattService svc : mServices) {
for(BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
- Log.w(TAG, "getCharacteristicById() comparing " + charac.getInstanceId() + " and " + instanceId);
if (charac.getInstanceId() == instanceId)
return charac;
}
@@ -944,7 +943,8 @@
try {
mService.writeDescriptor(mClientIf, device.getAddress(), descriptor.getInstanceId(),
- characteristic.getWriteType(), AUTHENTICATION_NONE, descriptor.getValue());
+ BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT, AUTHENTICATION_NONE,
+ descriptor.getValue());
} catch (RemoteException e) {
Log.e(TAG,"",e);
mDeviceBusy = false;
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index 736e55d..eab4c6f 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -40,7 +40,6 @@
"android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
private IBluetoothPbapClient mService;
- private BluetoothDevice mDevice;
private final Context mContext;
private ServiceListener mServiceListener;
private BluetoothAdapter mAdapter;
@@ -173,7 +172,6 @@
}
if (mService != null && isEnabled() && isValidDevice(device)) {
try {
- mDevice = device;
return mService.connect(device);
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
@@ -193,13 +191,13 @@
* @return false on error,
* true otherwise
*/
- public boolean disconnect() {
+ public boolean disconnect(BluetoothDevice device) {
if (DBG) {
- log("disconnect(" + mDevice + ")");
+ log("disconnect(" + device + ")" + new Exception() );
}
- if (mService != null && isEnabled() && isValidDevice(mDevice)) {
+ if (mService != null && isEnabled() && isValidDevice(device)) {
try {
- mService.disconnect(mDevice);
+ mService.disconnect(device);
return true;
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
@@ -328,4 +326,66 @@
}
return false;
}
+
+ /**
+ * Set priority of the profile
+ *
+ * <p> The device should already be paired.
+ * Priority can be one of {@link #PRIORITY_ON} or
+ * {@link #PRIORITY_OFF},
+ *
+ * @param device Paired bluetooth device
+ * @param priority
+ * @return true if priority is set, false on error
+ */
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ if (DBG) {
+ log("setPriority(" + device + ", " + priority + ")");
+ }
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ if (priority != BluetoothProfile.PRIORITY_OFF &&
+ priority != BluetoothProfile.PRIORITY_ON) {
+ return false;
+ }
+ try {
+ return mService.setPriority(device, priority);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ }
+ return false;
+ }
+
+ /**
+ * Get the priority of the profile.
+ *
+ * <p> The priority can be any of:
+ * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
+ * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+ *
+ * @param device Bluetooth device
+ * @return priority of the device
+ */
+ public int getPriority(BluetoothDevice device) {
+ if (VDBG) {
+ log("getPriority(" + device + ")");
+ }
+ if (mService != null && isEnabled() && isValidDevice(device)) {
+ try {
+ return mService.getPriority(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return PRIORITY_OFF;
+ }
+ }
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ }
+ return PRIORITY_OFF;
+ }
}
diff --git a/core/java/android/bluetooth/IBluetoothPbapClient.aidl b/core/java/android/bluetooth/IBluetoothPbapClient.aidl
index b26ea29..6d4c5a6 100644
--- a/core/java/android/bluetooth/IBluetoothPbapClient.aidl
+++ b/core/java/android/bluetooth/IBluetoothPbapClient.aidl
@@ -29,4 +29,6 @@
List<BluetoothDevice> getConnectedDevices();
List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
int getConnectionState(in BluetoothDevice device);
+ boolean setPriority(in BluetoothDevice device, int priority);
+ int getPriority(in BluetoothDevice device);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 66e0ada..10259be 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1581,14 +1581,6 @@
= "android.intent.extra.UNINSTALL_ALL_USERS";
/**
- * Specified when the uninstall confirmation dialog is not required to be shown.
- * Use with {@link #ACTION_UNINSTALL_PACKAGE}
- * @hide
- */
- public static final String EXTRA_SKIP_UNINSTALL_CONFIRMATION =
- "android.intent.extra.SKIP_UNINSTALL_CONFIRMATION";
-
- /**
* A string associated with a {@link #ACTION_UPGRADE_SETUP} activity
* describing the last run version of the platform that was setup.
* @hide
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index e3fb161..6fce36b 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -316,6 +316,12 @@
int getApplicationEnabledSetting(in String packageName, int userId);
/**
+ * Logs process start information (including APK hash) to the security log.
+ */
+ void logAppProcessStartIfNeeded(String processName, int uid, String seinfo, String apkFile,
+ int pid);
+
+ /**
* As per {@link android.content.pm.PackageManager#flushPackageRestrictionsAsUser}.
*/
void flushPackageRestrictionsAsUser(in int userId);
@@ -431,10 +437,9 @@
void performFstrimIfNeeded();
/**
- * Ask the package manager to extract packages if needed, to save
- * the VM unzipping the APK in memory during launch.
+ * Ask the package manager to update packages if needed.
*/
- void extractPackagesIfNeeded();
+ void updatePackagesIfNeeded();
/**
* Notify the package manager that a package is going to be used.
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 8f9dcfc..31d377b 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -47,4 +47,8 @@
int getIconMaxDimensions(String packageName, int userId);
void resetThrottling(); // system only API for developer opsions
+
+ byte[] getBackupPayload(int user);
+
+ void applyRestore(in byte[] payload, int user);
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index ae75e3f..fbe35a65 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -34,18 +34,20 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+// TODO Enhance javadoc
/**
- * TODO Enhance javadoc
*
- * Represents a shortcut form an application.
+ * Represents a shortcut from an application.
*
- * Notes...
- * - If an {@link Icon} is of a resource, then we'll just persist the package name and resource ID.
+ * <p>Notes about icons:
+ * <ul>
+ * <li>If an {@link Icon} is a resource, the system keeps the package name and the resource ID.
+ * Otherwise, the bitmap is fetched when it's registered to ShortcutManager,
+ * then shrunk if necessary, and persisted.
+ * <li>The system disallows byte[] icons, because they can easily go over the binder size limit.
+ * </ul>
*
- * Otherwise, the bitmap will be fetched when it's registered to ShortcutManager, then *shrunk*
- * if necessary, and persisted.
- *
- * We will disallow byte[] icons, because they can easily go over binder size limit.
+ * @see {@link ShortcutManager}.
*/
public class ShortcutInfo implements Parcelable {
/* @hide */
@@ -118,6 +120,9 @@
@NonNull
private String mTitle;
+ @Nullable
+ private String mText;
+
/**
* Intent *with extras removed*.
*/
@@ -157,6 +162,7 @@
mActivityComponent = b.mActivityComponent;
mIcon = b.mIcon;
mTitle = b.mTitle;
+ mText = b.mText;
mIntent = b.mIntent;
if (mIntent != null) {
final Bundle intentExtras = mIntent.getExtras();
@@ -176,6 +182,7 @@
* @hide
*/
public void enforceMandatoryFields() {
+ Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided");
Preconditions.checkStringNotEmpty(mTitle, "Shortcut title must be provided");
Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided");
}
@@ -195,16 +202,17 @@
if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
mIcon = source.mIcon;
mBitmapPath = source.mBitmapPath;
+ mIconResourceId = source.mIconResourceId;
}
mTitle = source.mTitle;
+ mText = source.mText;
if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
mIntent = source.mIntent;
mIntentPersistableExtras = source.mIntentPersistableExtras;
}
mWeight = source.mWeight;
mExtras = source.mExtras;
- mIconResourceId = source.mIconResourceId;
} else {
// Set this bit.
mFlags |= FLAG_KEY_FIELDS_ONLY;
@@ -244,6 +252,9 @@
if (source.mTitle != null) {
mTitle = source.mTitle;
}
+ if (source.mText != null) {
+ mText = source.mText;
+ }
if (source.mIntent != null) {
mIntent = source.mIntent;
mIntentPersistableExtras = source.mIntentPersistableExtras;
@@ -305,6 +316,8 @@
private String mTitle;
+ private String mText;
+
private Intent mIntent;
private int mWeight;
@@ -360,6 +373,9 @@
/**
* Sets the title of a shortcut. This is a mandatory field.
+ *
+ * <p>This field is intended for a concise description of a shortcut displayed under
+ * an icon. The recommend max length is 10 characters.
*/
@NonNull
public Builder setTitle(@NonNull String title) {
@@ -368,6 +384,18 @@
}
/**
+ * Sets the text of a shortcut. This is an optional field.
+ *
+ * <p>This field is intended to be more descriptive than the shortcut title.
+ * The recommend max length is 25 characters.
+ */
+ @NonNull
+ public Builder setText(@NonNull String text) {
+ mText = Preconditions.checkStringNotEmpty(text, "text");
+ return this;
+ }
+
+ /**
* Sets the intent of a shortcut. This is a mandatory field. The extras must only contain
* persistable information. (See {@link PersistableBundle}).
*/
@@ -457,6 +485,14 @@
}
/**
+ * Return the shortcut text.
+ */
+ @Nullable
+ public String getText() {
+ return mText;
+ }
+
+ /**
* Return the intent.
*
* <p>All shortcuts must have an intent, but this method will return null when
@@ -630,6 +666,7 @@
mActivityComponent = source.readParcelable(cl);
mIcon = source.readParcelable(cl);
mTitle = source.readString();
+ mText = source.readString();
mIntent = source.readParcelable(cl);
mIntentPersistableExtras = source.readParcelable(cl);
mWeight = source.readInt();
@@ -647,6 +684,7 @@
dest.writeParcelable(mActivityComponent, flags);
dest.writeParcelable(mIcon, flags);
dest.writeString(mTitle);
+ dest.writeString(mText);
dest.writeParcelable(mIntent, flags);
dest.writeParcelable(mIntentPersistableExtras, flags);
dest.writeInt(mWeight);
@@ -708,6 +746,9 @@
sb.append(", title=");
sb.append(secure ? "***" : mTitle);
+ sb.append(", text=");
+ sb.append(secure ? "***" : mText);
+
sb.append(", icon=");
sb.append(mIcon);
@@ -744,7 +785,8 @@
/** @hide */
public ShortcutInfo(String id, String packageName, ComponentName activityComponent,
- Icon icon, String title, Intent intent, PersistableBundle intentPersistableExtras,
+ Icon icon, String title, String text, Intent intent,
+ PersistableBundle intentPersistableExtras,
int weight, PersistableBundle extras, long lastChangedTimestamp,
int flags, int iconResId, String bitmapPath) {
mId = id;
@@ -752,6 +794,7 @@
mActivityComponent = activityComponent;
mIcon = icon;
mTitle = title;
+ mText = text;
mIntent = intent;
mIntentPersistableExtras = intentPersistableExtras;
mWeight = weight;
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index b247f65..e4a98b5 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -19,15 +19,13 @@
import android.content.Context;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
+// TODO Enhance javadoc
/**
- * TODO Enhance javadoc
- *
* {@link ShortcutManager} manages shortcuts created by applications.
*
* <h3>Dynamic shortcuts and pinned shortcuts</h3>
@@ -66,15 +64,18 @@
* {@link #getRemainingCallCount()} times until the rate-limiting counter is reset,
* which happens at a certain time every day.
*
- * <p>An applications can use {@link #getRateLimitResetTime()} to get the next reset time.
+ * <p>An application can use {@link #getRateLimitResetTime()} to get the next reset time.
+ *
+ * <p>For testing purposes, use "Developer Options" (found in the Settings menu) to reset the
+ * internal rate-limiting counter. Automated tests can use the following ADB shell command to
+ * achieve the same effect:</p>
+ * <pre>adb shell cmd shortcut reset-throttling</pre>
*
* <h3>Backup and Restore</h3>
*
* Shortcuts will be backed up and restored across devices. This means all information, including
* IDs, must be meaningful on a different device.
*
- * TODO: Define a Broadcast to let apps update shortcuts on a restored device.
- *
* <h3>APIs for launcher</h3>
*
* Launcher applications should use {@link LauncherApps} to get shortcuts that are published from
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index b75e653..7d903bd 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -31,12 +31,15 @@
import android.util.SparseIntArray;
import dalvik.system.CloseGuard;
+import com.android.internal.annotations.GuardedBy;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+
/**
* Sensor manager implementation that communicates with the built-in
* system sensors.
@@ -55,7 +58,9 @@
private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
private static final Object sLock = new Object();
- private static boolean sSensorModuleInitialized = false;
+ @GuardedBy("sLock")
+ private static boolean sNativeClassInited = false;
+ @GuardedBy("sLock")
private static InjectEventQueue sInjectEventQueue = null;
private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>();
@@ -84,8 +89,8 @@
/** {@hide} */
public SystemSensorManager(Context context, Looper mainLooper) {
synchronized(sLock) {
- if (!sSensorModuleInitialized) {
- sSensorModuleInitialized = true;
+ if (!sNativeClassInited) {
+ sNativeClassInited = true;
nativeClassInit();
}
}
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 8738424..e464a4a 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -57,6 +57,10 @@
public static final int RULE_REJECT_METERED = 1;
/** Reject traffic on all networks. */
public static final int RULE_REJECT_ALL = 2;
+ /** Allow traffic on metered networks. */
+ public static final int RULE_ALLOW_METERED = 3;
+ /** Temporarily allow traffic on metered networks because app is on foreground. */
+ public static final int RULE_TEMPORARY_ALLOW_METERED = 4;
public static final int FIREWALL_RULE_DEFAULT = 0;
public static final int FIREWALL_RULE_ALLOW = 1;
diff --git a/services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java b/core/java/android/net/metrics/CaptivePortalCheckResultEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java
rename to core/java/android/net/metrics/CaptivePortalCheckResultEvent.java
index 163f7e40..2239a25 100644
--- a/services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java
+++ b/core/java/android/net/metrics/CaptivePortalCheckResultEvent.java
@@ -19,6 +19,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+/**
+ * {@hide}
+ */
public class CaptivePortalCheckResultEvent extends IpConnectivityEvent implements Parcelable {
public static final String TAG = "CaptivePortalCheckResultEvent";
diff --git a/services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java b/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java
rename to core/java/android/net/metrics/CaptivePortalStateChangeEvent.java
index d0cc120..00808c1 100644
--- a/services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java
+++ b/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java
@@ -19,6 +19,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+/**
+ * {@hide}
+ */
public class CaptivePortalStateChangeEvent extends IpConnectivityEvent implements Parcelable {
public static final String TAG = "CaptivePortalStateChangeEvent";
diff --git a/services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java b/core/java/android/net/metrics/ConnectivityServiceChangeEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java
rename to core/java/android/net/metrics/ConnectivityServiceChangeEvent.java
index 92b376c..c6fcb2d 100644
--- a/services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java
+++ b/core/java/android/net/metrics/ConnectivityServiceChangeEvent.java
@@ -19,6 +19,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+/**
+ * {@hide}
+ */
public class ConnectivityServiceChangeEvent extends IpConnectivityEvent implements Parcelable {
public static final String TAG = "ConnectivityServiceChangeEvent";
diff --git a/services/net/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/DhcpClientEvent.java
rename to core/java/android/net/metrics/DhcpClientEvent.java
index 2c24034..7b44664 100644
--- a/services/net/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -19,6 +19,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+/**
+ * {@hide}
+ */
public class DhcpClientEvent extends IpConnectivityEvent implements Parcelable {
public static final String TAG = "DhcpClientEvent";
diff --git a/services/net/java/android/net/metrics/IpConnectivityEvent.java b/core/java/android/net/metrics/IpConnectivityEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/IpConnectivityEvent.java
rename to core/java/android/net/metrics/IpConnectivityEvent.java
index f277bd0..ec42890 100644
--- a/services/net/java/android/net/metrics/IpConnectivityEvent.java
+++ b/core/java/android/net/metrics/IpConnectivityEvent.java
@@ -20,6 +20,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+/**
+ * {@hide}
+ */
public class IpConnectivityEvent implements Parcelable {
// IPRM = IpReachabilityMonitor
// DHCP = DhcpClient
diff --git a/services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java b/core/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
rename to core/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
index a8c18d6..e71b0be 100644
--- a/services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
@@ -19,6 +19,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+/**
+ * {@hide}
+ */
public class IpReachabilityMonitorMessageEvent extends IpConnectivityEvent
implements Parcelable {
public static final String TAG = "IpReachabilityMonitorMessageEvent";
diff --git a/services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java b/core/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
rename to core/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
index 172cbf8..182b778 100644
--- a/services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
@@ -19,6 +19,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+/**
+ * {@hide}
+ */
public class IpReachabilityMonitorProbeEvent extends IpConnectivityEvent
implements Parcelable {
public static final String TAG = "IpReachabilityMonitorProbeEvent";
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 8bc903b..a0a16f1 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -224,8 +224,6 @@
* This is used by Gaming and VR applications to ensure the device provides
* will provide consistent performance over a large amount of time.
* </p>
- *
- * {@hide}
*/
public static final int SUSTAINED_PERFORMANCE_WAKE_LOCK = 0x00000100;
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index c028e15..7b0d2a4 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -315,27 +315,34 @@
* To gain access to descendants (child, grandchild, etc) documents, use
* {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or
* {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
- *
- * <b>If your application only needs to store internal data, consider using
+ * <p>
+ * If your application only needs to store internal data, consider using
* {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs},
- * {@link Context#getExternalCacheDirs()}, or
- * {@link Context#getExternalMediaDirs()}, which require no permissions to read or write.
+ * {@link Context#getExternalCacheDirs()}, or {@link Context#getExternalMediaDirs()}, which
+ * require no permissions to read or write.
+ * <p>
+ * Access to the entire volume is only available for non-primary volumes (for the primary
+ * volume, apps can use the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions) and should be used
+ * with caution, since users are more likely to deny access when asked for entire volume access
+ * rather than specific directories.
*
- * <strong>NOTE: </strong>requesting access to the entire volume is not recommended and it will
- * result in a stronger message displayed to the user, which may cause the user to reject
- * the request.
- *
- * @param directoryName must be one of
- * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
- * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
- * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
- * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
- * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}, or
- * {code null} to request access to the entire volume.
- *
+ * @param directoryName must be one of {@link Environment#DIRECTORY_MUSIC},
+ * {@link Environment#DIRECTORY_PODCASTS}, {@link Environment#DIRECTORY_RINGTONES},
+ * {@link Environment#DIRECTORY_ALARMS}, {@link Environment#DIRECTORY_NOTIFICATIONS},
+ * {@link Environment#DIRECTORY_PICTURES}, {@link Environment#DIRECTORY_MOVIES},
+ * {@link Environment#DIRECTORY_DOWNLOADS}, {@link Environment#DIRECTORY_DCIM}, or
+ * {@link Environment#DIRECTORY_DOCUMENTS}, or {code null} to request access to the
+ * entire volume.
+ * @return intent to request access, or {@code null} if the requested directory is invalid for
+ * that volume.
* @see DocumentsContract
*/
- public Intent createAccessIntent(String directoryName) {
+ public @Nullable Intent createAccessIntent(String directoryName) {
+ if ((isPrimary() && directoryName == null) ||
+ (directoryName != null && !Environment.isStandardDirectory(directoryName))) {
+ return null;
+ }
final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY);
intent.putExtra(EXTRA_STORAGE_VOLUME, this);
intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName);
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index 57c7718..2941283 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -32,6 +32,9 @@
*/
public static final PageRange ALL_PAGES = new PageRange(0, Integer.MAX_VALUE);
+ /** @hide */
+ public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[]{PageRange.ALL_PAGES};
+
private final int mStart;
private final int mEnd;
diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java
index cd5a903..7b9533d 100644
--- a/core/java/android/printservice/PrinterDiscoverySession.java
+++ b/core/java/android/printservice/PrinterDiscoverySession.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.content.pm.ParceledListSlice;
+import android.os.CancellationSignal;
import android.os.RemoteException;
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
@@ -412,11 +413,13 @@
* service.
*
* @param printerId The printer to icon belongs to.
+ * @param cancellationSignal Signal used to cancel the request
* @param callback Callback for returning the icon to the print spooler.
*
* @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
*/
public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
+ @NonNull CancellationSignal cancellationSignal,
@NonNull CustomPrinterIconCallback callback) {
}
@@ -533,7 +536,7 @@
if (!mIsDestroyed && mObserver != null) {
CustomPrinterIconCallback callback = new CustomPrinterIconCallback(printerId,
mObserver);
- onRequestCustomPrinterIcon(printerId, callback);
+ onRequestCustomPrinterIcon(printerId, new CancellationSignal(), callback);
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a78f468..ebecfdb 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7679,6 +7679,9 @@
BLUETOOTH_MAP_PRIORITY_PREFIX = "bluetooth_map_priority_";
/** {@hide} */
public static final String
+ BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX = "bluetooth_pbap_client_priority_";
+ /** {@hide} */
+ public static final String
BLUETOOTH_SAP_PRIORITY_PREFIX = "bluetooth_sap_priority_";
/**
@@ -7834,6 +7837,14 @@
}
/**
+ * Get the key that retrieves a bluetooth pbap client priority.
+ * @hide
+ */
+ public static final String getBluetoothPbapClientPriorityKey(String address) {
+ return BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
* Get the key that retrieves a bluetooth map priority.
* @hide
*/
diff --git a/core/java/android/util/Pair.java b/core/java/android/util/Pair.java
index 6027d08..f96da72 100644
--- a/core/java/android/util/Pair.java
+++ b/core/java/android/util/Pair.java
@@ -16,7 +16,7 @@
package android.util;
-import libcore.util.Objects;
+import java.util.Objects;
/**
* Container to ease passing around a tuple of two objects. This object provides a sensible
@@ -52,7 +52,7 @@
return false;
}
Pair<?, ?> p = (Pair<?, ?>) o;
- return Objects.equal(p.first, first) && Objects.equal(p.second, second);
+ return Objects.equals(p.first, first) && Objects.equals(p.second, second);
}
/**
@@ -65,6 +65,11 @@
return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
}
+ @Override
+ public String toString() {
+ return "Pair{" + String.valueOf(first) + " " + String.valueOf(second) + "}";
+ }
+
/**
* Convenience method for creating an appropriately typed pair.
* @param a the first object in the Pair
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7af4a1f..5bcf102 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -400,4 +400,14 @@
* @hide
*/
void registerShortcutKey(in long shortcutCode, IShortcutService keySubscriber);
+
+ /**
+ * Create the input consumer for wallpaper events.
+ */
+ void createWallpaperInputConsumer(out InputChannel inputChannel);
+
+ /**
+ * Remove the input consumer for wallpaper events.
+ */
+ void removeWallpaperInputConsumer();
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index a1e2e94..8e1609c 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -116,14 +116,11 @@
* @param top The new top position
* @param right The new right position
* @param bottom The new bottom position
- * @param requestedWidth The new requested width
- * @param requestedHeight The new requested height
* @param deferTransactionUntilFrame Frame number from our parent (attached) to
* defer this action until.
* @param outFrame Rect in which is placed the new position/size on screen.
*/
void repositionChild(IWindow childWindow, int left, int top, int right, int bottom,
- int requestedWidth, int requestedHeight,
long deferTransactionUntilFrame, out Rect outFrame);
/*
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 477ffd9..8a8fb43 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -490,7 +490,7 @@
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
;
- if (!creating && !force && !mUpdateWindowNeeded) {
+ if (!creating && !force && !mUpdateWindowNeeded && !sizeChanged) {
mLayout.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY;
} else {
@@ -584,18 +584,6 @@
mSurface.transferFrom(mNewSurface);
if (visible && mSurface.isValid()) {
- // We set SCALING_MODE_NO_SCALE_CROP to allow the WindowManager
- // to update our Surface crop without requiring a new buffer from
- // us. In the default mode of SCALING_MODE_FREEZE, surface geometry
- // state (which includes crop) is only applied when a buffer
- // with appropriate geometry is available. During drag resize
- // it is quite frequent that a matching buffer will not be available
- // (because we are constantly being resized and have fallen behind).
- // However in such situations the WindowManager still needs to be able
- // to update our crop to ensure we stay within the bounds of the containing
- // window.
- mSurface.setScalingMode(Surface.SCALING_MODE_NO_SCALE_CROP);
-
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
mSurfaceCreated = true;
mIsCreating = true;
@@ -666,7 +654,6 @@
mLocation[0], mLocation[1]));
mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop,
mLocation[0], mLocation[1],
- mWindowSpaceWidth, mWindowSpaceHeight,
-1, mWinFrame);
} catch (RemoteException ex) {
Log.e(TAG, "Exception from relayout", ex);
@@ -703,7 +690,6 @@
}
// Just using mRTLastReportedPosition as a dummy rect here
session.repositionChild(window, left, top, right, bottom,
- mWindowSpaceWidth, mWindowSpaceHeight,
frameNumber,
mRTLastReportedPosition);
// Now overwrite mRTLastReportedPosition with our values
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6811aed..dd79e62 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16299,8 +16299,10 @@
/**
* Create a snapshot of the view into a bitmap. We should probably make
* some form of this public, but should think about the API.
+ *
+ * @hide
*/
- Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
+ public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
int width = mRight - mLeft;
int height = mBottom - mTop;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 816d9c4..3f7bbdf 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3251,8 +3251,11 @@
}
}
+ /**
+ * @hide
+ */
@Override
- Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
+ public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
int count = mChildrenCount;
int[] visibilities = null;
@@ -3262,7 +3265,8 @@
View child = getChildAt(i);
visibilities[i] = child.getVisibility();
if (visibilities[i] == View.VISIBLE) {
- child.setVisibility(INVISIBLE);
+ child.mViewFlags = (child.mViewFlags & ~View.VISIBILITY_MASK)
+ | (View.INVISIBLE & View.VISIBILITY_MASK);
}
}
}
@@ -3271,7 +3275,9 @@
if (skipChildren) {
for (int i = 0; i < count; i++) {
- getChildAt(i).setVisibility(visibilities[i]);
+ View child = getChildAt(i);
+ child.mViewFlags = (child.mViewFlags & ~View.VISIBILITY_MASK)
+ | (visibilities[i] & View.VISIBILITY_MASK);
}
}
@@ -6749,7 +6755,8 @@
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
- // Do nothing
+ // Re-dispatch up the tree by default
+ dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null);
}
/**
@@ -6757,7 +6764,8 @@
*/
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
- // Do nothing
+ // Re-dispatch up the tree by default
+ dispatchNestedPreScroll(dx, dy, consumed, null);
}
/**
@@ -6765,7 +6773,8 @@
*/
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
- return false;
+ // Re-dispatch up the tree by default
+ return dispatchNestedFling(velocityX, velocityY, consumed);
}
/**
@@ -6773,7 +6782,8 @@
*/
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
- return false;
+ // Re-dispatch up the tree by default
+ return dispatchNestedPreFling(velocityX, velocityY);
}
/**
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 36ee3e6..2f3f0bf 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -298,7 +298,7 @@
private boolean mDestroyed;
- private boolean mOverlayWithDecorCaption = false;
+ private boolean mOverlayWithDecorCaptionEnabled = false;
// The current window attributes.
private final WindowManager.LayoutParams mWindowAttributes =
@@ -2139,13 +2139,13 @@
* down. This affects only freeform windows since they display the caption.
* @hide
*/
- public void setOverlayDecorCaption(boolean overlayCaption) {
- mOverlayWithDecorCaption = overlayCaption;
+ public void setOverlayWithDecorCaptionEnabled(boolean enabled) {
+ mOverlayWithDecorCaptionEnabled = enabled;
}
/** @hide */
- public boolean getOverlayDecorCaption() {
- return mOverlayWithDecorCaption;
+ public boolean isOverlayWithDecorCaptionEnabled() {
+ return mOverlayWithDecorCaptionEnabled;
}
/** @hide */
@@ -2181,7 +2181,7 @@
* Called when the activity changes from fullscreen mode to multi-window mode and visa-versa.
* @hide
*/
- public abstract void onMultiWindowChanged();
+ public abstract void onMultiWindowModeChanged();
/**
* Called when the activity just relaunched.
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index c1392fe..23b0df2 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -415,7 +415,7 @@
* Returns true if the window is current in multi-windowing mode. i.e. it shares the
* screen with other application windows.
*/
- public boolean inMultiWindowMode();
+ public boolean isInMultiWindowMode();
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index bdaf291..1482111 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2926,8 +2926,10 @@
mInputType = other.mInputType;
mLiveRegion = other.mLiveRegion;
mDrawingOrderInParent = other.mDrawingOrderInParent;
- if (other.mExtras != null && !other.mExtras.isEmpty()) {
- getExtras().putAll(other.mExtras);
+ if (other.mExtras != null) {
+ mExtras = new Bundle(other.mExtras);
+ } else {
+ mExtras = null;
}
mRangeInfo = (other.mRangeInfo != null)
? RangeInfo.obtain(other.mRangeInfo) : null;
@@ -3006,7 +3008,9 @@
mDrawingOrderInParent = parcel.readInt();
if (parcel.readInt() == 1) {
- getExtras().putAll(parcel.readBundle());
+ mExtras = parcel.readBundle();
+ } else {
+ mExtras = null;
}
if (parcel.readInt() == 1) {
@@ -3073,9 +3077,7 @@
mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;
mInputType = InputType.TYPE_NULL;
mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE;
- if (mExtras != null) {
- mExtras.clear();
- }
+ mExtras = null;
if (mRangeInfo != null) {
mRangeInfo.recycle();
mRangeInfo = null;
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index a3c49c5..89dec2d 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -16,6 +16,7 @@
package android.view.inputmethod;
+import android.annotation.CallSuper;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -154,12 +155,11 @@
}
/**
- * Called when this InputConnection is no longer used by the InputMethodManager.
- *
- * @hide
+ * Default implementation calls {@link #finishComposingText()}.
*/
- public void reportFinish() {
- // Intentionally empty
+ @CallSuper
+ public void closeConnection() {
+ finishComposingText();
}
/**
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 8002a8e..9f66429 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -45,6 +45,8 @@
* was introduced in {@link android.os.Build.VERSION_CODES#N}.</li>
* <li>{@link #getHandler()}}, which was introduced in
* {@link android.os.Build.VERSION_CODES#N}.</li>
+ * <li>{@link #closeConnection()}}, which was introduced in
+ * {@link android.os.Build.VERSION_CODES#N}.</li>
* </ul>
*
* <h3>Implementing an IME or an editor</h3>
@@ -820,4 +822,18 @@
* @return {@code null} to use the default {@link Handler}.
*/
public Handler getHandler();
+
+ /**
+ * Called by the system up to only once to notify that the system is about to invalidate
+ * connection between the input method and the application.
+ *
+ * <p><strong>Editor authors</strong>: You can clear all the nested batch edit right now and
+ * you no longer need to handle subsequent callbacks on this connection, including
+ * {@link #beginBatchEdit()}}. Note that although the system tries to call this method whenever
+ * possible, there may be a chance that this method is not called in some exceptional
+ * situations.</p>
+ *
+ * <p>Note: This does nothing when called from input methods.</p>
+ */
+ public void closeConnection();
}
diff --git a/core/java/android/view/inputmethod/InputConnectionInspector.java b/core/java/android/view/inputmethod/InputConnectionInspector.java
index 46b2c3e..118a61f 100644
--- a/core/java/android/view/inputmethod/InputConnectionInspector.java
+++ b/core/java/android/view/inputmethod/InputConnectionInspector.java
@@ -73,6 +73,11 @@
* {@link android.os.Build.VERSION_CODES#N} and later.
*/
int GET_HANDLER = 1 << 5;
+ /**
+ * {@link InputConnection#closeConnection()}} is available in
+ * {@link android.os.Build.VERSION_CODES#N} and later.
+ */
+ int CLOSE_CONNECTION = 1 << 6;
}
private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
@@ -119,6 +124,9 @@
if (!hasGetHandler(clazz)) {
flags |= MissingMethodFlags.GET_HANDLER;
}
+ if (!hasCloseConnection(clazz)) {
+ flags |= MissingMethodFlags.CLOSE_CONNECTION;
+ }
sMissingMethodsMap.put(clazz, flags);
return flags;
}
@@ -178,6 +186,15 @@
}
}
+ private static boolean hasCloseConnection(@NonNull final Class clazz) {
+ try {
+ final Method method = clazz.getMethod("closeConnection");
+ return !Modifier.isAbstract(method.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
final StringBuilder sb = new StringBuilder();
boolean isEmpty = true;
@@ -219,6 +236,12 @@
}
sb.append("getHandler()");
}
+ if ((flags & MissingMethodFlags.CLOSE_CONNECTION) != 0) {
+ if (!isEmpty) {
+ sb.append(",");
+ }
+ sb.append("closeConnection()");
+ }
return sb.toString();
}
}
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 381df49..e743f62 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -261,4 +261,12 @@
public Handler getHandler() {
return mTarget.getHandler();
}
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ public void closeConnection() {
+ mTarget.closeConnection();
+ }
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f5908a5..6879901 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -544,7 +544,7 @@
// reportFinish() will take effect.
return;
}
- reportFinish();
+ closeConnection();
}
@Override
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 7cbe8de..6e1dff9 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -5937,6 +5937,11 @@
public Handler getHandler() {
return getTarget().getHandler();
}
+
+ @Override
+ public void closeConnection() {
+ getTarget().closeConnection();
+ }
}
/**
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index f16fdd6..00f368e 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -325,16 +325,24 @@
if (getChildCount() > 0) {
final View child = getChildAt(0);
- int width = getMeasuredWidth();
- if (child.getMeasuredWidth() < width) {
- final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final int widthPadding;
+ final int heightPadding;
+ final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+ if (targetSdkVersion >= Build.VERSION_CODES.M) {
+ widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
+ heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
+ } else {
+ widthPadding = mPaddingLeft + mPaddingRight;
+ heightPadding = mPaddingTop + mPaddingBottom;
+ }
- int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mPaddingTop
- + mPaddingBottom, lp.height);
- width -= mPaddingLeft;
- width -= mPaddingRight;
- int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
-
+ int desiredWidth = getMeasuredWidth() - widthPadding;
+ if (child.getMeasuredWidth() < desiredWidth) {
+ final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ desiredWidth, MeasureSpec.EXACTLY);
+ final int childHeightMeasureSpec = getChildMeasureSpec(
+ heightMeasureSpec, heightPadding, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
@@ -1235,17 +1243,17 @@
}
@Override
- protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
+ protected void measureChild(View child, int parentWidthMeasureSpec,
+ int parentHeightMeasureSpec) {
ViewGroup.LayoutParams lp = child.getLayoutParams();
- int childWidthMeasureSpec;
- int childHeightMeasureSpec;
+ final int horizontalPadding = mPaddingLeft + mPaddingRight;
+ final int childWidthMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
+ Math.max(0, MeasureSpec.getSize(parentWidthMeasureSpec) - horizontalPadding),
+ MeasureSpec.UNSPECIFIED);
- childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop
- + mPaddingBottom, lp.height);
-
- childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-
+ final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+ mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@@ -1257,8 +1265,11 @@
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
- final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
- lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
+ final int usedTotal = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin +
+ widthUsed;
+ final int childWidthMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
+ Math.max(0, MeasureSpec.getSize(parentWidthMeasureSpec) - usedTotal),
+ MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 18687c9..92631da 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -173,9 +173,6 @@
private int mHeight = LayoutParams.WRAP_CONTENT;
private int mLastHeight;
- private int mPopupWidth;
- private int mPopupHeight;
-
private float mElevation;
private Drawable mBackground;
@@ -1298,8 +1295,6 @@
mPopupViewInitialLayoutDirectionInherited =
(mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
- mPopupWidth = p.width;
- mPopupHeight = p.height;
}
/**
@@ -2006,7 +2001,7 @@
* @param height the new height, must be >= 0 or -1 to ignore
*/
public void update(View anchor, int width, int height) {
- update(anchor, false, 0, 0, true, width, height);
+ update(anchor, false, 0, 0, width, height);
}
/**
@@ -2026,11 +2021,11 @@
* @param height the new height, must be >= 0 or -1 to ignore
*/
public void update(View anchor, int xoff, int yoff, int width, int height) {
- update(anchor, true, xoff, yoff, true, width, height);
+ update(anchor, true, xoff, yoff, width, height);
}
private void update(View anchor, boolean updateLocation, int xoff, int yoff,
- boolean updateDimension, int width, int height) {
+ int width, int height) {
if (!isShowing() || mContentView == null) {
return;
@@ -2055,13 +2050,13 @@
final int oldX = p.x;
final int oldY = p.y;
- if (updateDimension) {
- if (width == -1) {
- width = mPopupWidth;
- }
- if (height == -1) {
- height = mPopupHeight;
- }
+ // If an explicit width/height has not specified, use the most recent
+ // explicitly specified value (either from setWidth/Height or update).
+ if (width == -1) {
+ width = mWidth;
+ }
+ if (height == -1) {
+ height = mHeight;
}
final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 3f7a07b..0555cd4 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -350,24 +350,24 @@
if (getChildCount() > 0) {
final View child = getChildAt(0);
- final int height = getMeasuredHeight();
- if (child.getMeasuredHeight() < height) {
- final int widthPadding;
- final int heightPadding;
- final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
- if (targetSdkVersion >= VERSION_CODES.M) {
- widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
- heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
- } else {
- widthPadding = mPaddingLeft + mPaddingRight;
- heightPadding = mPaddingTop + mPaddingBottom;
- }
+ final int widthPadding;
+ final int heightPadding;
+ final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+ final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (targetSdkVersion >= VERSION_CODES.M) {
+ widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
+ heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
+ } else {
+ widthPadding = mPaddingLeft + mPaddingRight;
+ heightPadding = mPaddingTop + mPaddingBottom;
+ }
+ final int desiredHeight = getMeasuredHeight() - heightPadding;
+ if (child.getMeasuredHeight() < desiredHeight) {
final int childWidthMeasureSpec = getChildMeasureSpec(
widthMeasureSpec, widthPadding, lp.width);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
- height - heightPadding, MeasureSpec.EXACTLY);
+ desiredHeight, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
@@ -1268,9 +1268,10 @@
childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
+ mPaddingRight, lp.width);
-
+ final int verticalPadding = mPaddingTop + mPaddingBottom;
childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
- MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);
+ Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
+ MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@@ -1283,8 +1284,11 @@
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
+ final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
+ heightUsed;
final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
- MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);
+ Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
+ MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 43cf5a1..ee716df 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -16,6 +16,9 @@
package android.widget;
+import com.android.internal.R;
+import com.android.internal.widget.ExploreByTouchHelper;
+
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -44,14 +47,13 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import com.android.internal.R;
-import com.android.internal.widget.ExploreByTouchHelper;
-
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Locale;
+import libcore.icu.LocaleData;
+
/**
* A calendar-like view displaying a specified month and the appropriate selectable day numbers
* within the specified month.
@@ -66,7 +68,6 @@
private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
private static final String MONTH_YEAR_FORMAT = "MMMMy";
- private static final String DAY_OF_WEEK_FORMAT = "EEEEE";
private static final int SELECTED_HIGHLIGHT_ALPHA = 0xB0;
@@ -80,6 +81,7 @@
private final Paint mDayHighlightPaint = new Paint();
private final Paint mDayHighlightSelectorPaint = new Paint();
+ /** Array of single-character weekday labels ordered by column index. */
private final String[] mDayOfWeekLabels = new String[7];
private final Calendar mCalendar;
@@ -120,7 +122,7 @@
*/
private int mToday = DEFAULT_SELECTED_DAY;
- /** The first day of the week (ex. Calendar.SUNDAY). */
+ /** The first day of the week (ex. Calendar.SUNDAY) indexed from one. */
private int mWeekStart = DEFAULT_WEEK_START;
/** The number of days (ex. 28) in the current month. */
@@ -199,13 +201,11 @@
Log.d(LOG_TAG, "mWeekStart => " + mWeekStart);
}
- final Calendar calendar = Calendar.getInstance(mLocale);
- calendar.setFirstDayOfWeek(mWeekStart);
-
- final SimpleDateFormat formatter = new SimpleDateFormat(DAY_OF_WEEK_FORMAT, mLocale);
- for (int i = 0; i < 7; i++) {
- calendar.set(Calendar.DAY_OF_WEEK, i);
- mDayOfWeekLabels[i] = formatter.format(calendar.getTime());
+ // Use tiny (e.g. single-character) weekday names from ICU. The indices
+ // for this list correspond to Calendar days, e.g. SUNDAY is index 1.
+ final String[] tinyWeekdayNames = LocaleData.get(mLocale).tinyWeekdayNames;
+ for (int i = 0; i < DAYS_IN_WEEK; i++) {
+ mDayOfWeekLabels[i] = tinyWeekdayNames[(mWeekStart + i - 1) % DAYS_IN_WEEK + 1];
}
if (DEBUG_WRONG_DATE) {
@@ -662,8 +662,7 @@
colCenterRtl = colCenter;
}
- final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK;
- final String label = mDayOfWeekLabels[dayOfWeek];
+ final String label = mDayOfWeekLabels[col];
canvas.drawText(label, colCenterRtl, rowCenter - halfLineHeight, p);
}
}
@@ -813,6 +812,11 @@
*/
void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart,
int enabledDayEnd) {
+ if (DEBUG_WRONG_DATE) {
+ Log.d(LOG_TAG, "setMonthParams(" + selectedDay + ", " + month + ", " + year + ", "
+ + weekStart + ", " + enabledDayStart + ", " + enabledDayEnd + ")");
+ }
+
mActivatedDay = selectedDay;
if (isValidMonth(month)) {
@@ -849,6 +853,14 @@
mTouchHelper.invalidateRoot();
updateMonthYearLabel();
+
+ if (DEBUG_WRONG_DATE) {
+ Log.d(LOG_TAG, "mMonth = " + mMonth);
+ Log.d(LOG_TAG, "mDayOfWeekStart = " + mDayOfWeekStart);
+ Log.d(LOG_TAG, "mWeekStart = " + mWeekStart);
+ Log.d(LOG_TAG, "mDaysInMonth = " + mDaysInMonth);
+ Log.d(LOG_TAG, "mToday = " + mToday);
+ }
}
private static int getDaysInMonth(int month, int year) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a9af654..4a68d3c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3411,17 +3411,10 @@
}
/**
- * Sets whether the movement method will automatically be set to {@link LinkMovementMethod}
- * after {@link #setText} or {@link #append} is called. The movement method is set if one of the
- * following is true:
- * <ul>
- * <li>{@link #setAutoLinkMask} has been set to nonzero and links are detected in
- * {@link #setText} or {@link #append}.
- * <li>The input for {@link #setText} or {@link #append} contains a {@link ClickableSpan}.
- * </ul>
- *
- * <p>This function does not have an immediate effect, movement method will be set only after a
- * call to {@link #setText} or {@link #append}. The default is true.</p>
+ * Sets whether the movement method will automatically be set to
+ * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
+ * set to nonzero and links are detected in {@link #setText}.
+ * The default is true.
*
* @attr ref android.R.styleable#TextView_linksClickable
*/
@@ -3431,14 +3424,10 @@
}
/**
- * Returns whether the movement method will automatically be set to {@link LinkMovementMethod}
- * after {@link #setText} or {@link #append} is called.
- *
- * See {@link #setLinksClickable} for details.
- *
- * <p>The default is true.</p>
- *
- * @see #setLinksClickable
+ * Returns whether the movement method will automatically be set to
+ * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
+ * set to nonzero and links are detected in {@link #setText}.
+ * The default is true.
*
* @attr ref android.R.styleable#TextView_linksClickable
*/
@@ -4032,19 +4021,13 @@
((Editable) mText).append(text, start, end);
- boolean hasClickableSpans = false;
if (mAutoLinkMask != 0) {
- hasClickableSpans = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
- } else if (mLinksClickable && text instanceof Spanned) {
- ClickableSpan[] clickableSpans =
- ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
- hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
- }
-
- // Do not change the movement method for text that supports text selection as it
- // would prevent an arbitrary cursor displacement.
- if (hasClickableSpans && mLinksClickable && !textCanBeSelected()) {
- setMovementMethod(LinkMovementMethod.getInstance());
+ boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
+ // Do not change the movement method for text that support text selection as it
+ // would prevent an arbitrary cursor displacement.
+ if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
+ setMovementMethod(LinkMovementMethod.getInstance());
+ }
}
}
@@ -4397,7 +4380,6 @@
text = TextUtils.stringOrSpannedString(text);
}
- boolean hasClickableSpans = false;
if (mAutoLinkMask != 0) {
Spannable s2;
@@ -4407,32 +4389,22 @@
s2 = mSpannableFactory.newSpannable(text);
}
- hasClickableSpans = Linkify.addLinks(s2, mAutoLinkMask);
- if (hasClickableSpans) {
+ if (Linkify.addLinks(s2, mAutoLinkMask)) {
text = s2;
- }
- } else if (mLinksClickable && text instanceof Spanned) {
- ClickableSpan[] clickableSpans =
- ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
- hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
- if (hasClickableSpans && !(text instanceof Spannable)) {
- text = mSpannableFactory.newSpannable(text);
- }
- }
+ type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
- if (hasClickableSpans) {
- type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
- /*
- * We must go ahead and set the text before changing the
- * movement method, because setMovementMethod() may call
- * setText() again to try to upgrade the buffer type.
- */
- mText = text;
+ /*
+ * We must go ahead and set the text before changing the
+ * movement method, because setMovementMethod() may call
+ * setText() again to try to upgrade the buffer type.
+ */
+ mText = text;
- // Do not change the movement method for text that supports text selection as it
- // would prevent an arbitrary cursor displacement.
- if (mLinksClickable && !textCanBeSelected()) {
- setMovementMethod(LinkMovementMethod.getInstance());
+ // Do not change the movement method for text that support text selection as it
+ // would prevent an arbitrary cursor displacement.
+ if (mLinksClickable && !textCanBeSelected()) {
+ setMovementMethod(LinkMovementMethod.getInstance());
+ }
}
}
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 06daf61..5b0a90a 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -106,6 +106,8 @@
* @attr ref android.R.styleable#Toolbar_contentInsetLeft
* @attr ref android.R.styleable#Toolbar_contentInsetRight
* @attr ref android.R.styleable#Toolbar_contentInsetStart
+ * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation
+ * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions
* @attr ref android.R.styleable#Toolbar_gravity
* @attr ref android.R.styleable#Toolbar_logo
* @attr ref android.R.styleable#Toolbar_logoDescription
@@ -159,6 +161,8 @@
private int mTitleMarginBottom;
private final RtlSpacingHelper mContentInsets = new RtlSpacingHelper();
+ private int mContentInsetStartWithNavigation;
+ private int mContentInsetEndWithActions;
private int mGravity = Gravity.START | Gravity.CENTER_VERTICAL;
@@ -272,6 +276,11 @@
mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
}
+ mContentInsetStartWithNavigation = a.getDimensionPixelOffset(
+ R.styleable.Toolbar_contentInsetStartWithNavigation, RtlSpacingHelper.UNDEFINED);
+ mContentInsetEndWithActions = a.getDimensionPixelOffset(
+ R.styleable.Toolbar_contentInsetEndWithActions, RtlSpacingHelper.UNDEFINED);
+
mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon);
mCollapseDescription = a.getText(R.styleable.Toolbar_collapseContentDescription);
@@ -1055,7 +1064,7 @@
}
/**
- * Set the content insets for this toolbar relative to layout direction.
+ * Sets the content insets for this toolbar relative to layout direction.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -1069,13 +1078,15 @@
* @see #getContentInsetEnd()
* @see #getContentInsetLeft()
* @see #getContentInsetRight()
+ * @attr ref android.R.styleable#Toolbar_contentInsetEnd
+ * @attr ref android.R.styleable#Toolbar_contentInsetStart
*/
public void setContentInsetsRelative(int contentInsetStart, int contentInsetEnd) {
mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
}
/**
- * Get the starting content inset for this toolbar.
+ * Gets the starting content inset for this toolbar.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -1088,13 +1099,14 @@
* @see #getContentInsetEnd()
* @see #getContentInsetLeft()
* @see #getContentInsetRight()
+ * @attr ref android.R.styleable#Toolbar_contentInsetStart
*/
public int getContentInsetStart() {
return mContentInsets.getStart();
}
/**
- * Get the ending content inset for this toolbar.
+ * Gets the ending content inset for this toolbar.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -1107,13 +1119,14 @@
* @see #getContentInsetStart()
* @see #getContentInsetLeft()
* @see #getContentInsetRight()
+ * @attr ref android.R.styleable#Toolbar_contentInsetEnd
*/
public int getContentInsetEnd() {
return mContentInsets.getEnd();
}
/**
- * Set the content insets for this toolbar.
+ * Sets the content insets for this toolbar.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -1127,13 +1140,15 @@
* @see #getContentInsetEnd()
* @see #getContentInsetLeft()
* @see #getContentInsetRight()
+ * @attr ref android.R.styleable#Toolbar_contentInsetLeft
+ * @attr ref android.R.styleable#Toolbar_contentInsetRight
*/
public void setContentInsetsAbsolute(int contentInsetLeft, int contentInsetRight) {
mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight);
}
/**
- * Get the left content inset for this toolbar.
+ * Gets the left content inset for this toolbar.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -1146,13 +1161,14 @@
* @see #getContentInsetStart()
* @see #getContentInsetEnd()
* @see #getContentInsetRight()
+ * @attr ref android.R.styleable#Toolbar_contentInsetLeft
*/
public int getContentInsetLeft() {
return mContentInsets.getLeft();
}
/**
- * Get the right content inset for this toolbar.
+ * Gets the right content inset for this toolbar.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -1165,11 +1181,160 @@
* @see #getContentInsetStart()
* @see #getContentInsetEnd()
* @see #getContentInsetLeft()
+ * @attr ref android.R.styleable#Toolbar_contentInsetRight
*/
public int getContentInsetRight() {
return mContentInsets.getRight();
}
+ /**
+ * Gets the start content inset to use when a navigation button is present.
+ *
+ * <p>Different content insets are often called for when additional buttons are present
+ * in the toolbar, as well as at different toolbar sizes. The larger value of
+ * {@link #getContentInsetStart()} and this value will be used during layout.</p>
+ *
+ * @return the start content inset used when a navigation icon has been set in pixels
+ *
+ * @see #setContentInsetStartWithNavigation(int)
+ * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation
+ */
+ public int getContentInsetStartWithNavigation() {
+ return mContentInsetStartWithNavigation != RtlSpacingHelper.UNDEFINED
+ ? mContentInsetStartWithNavigation
+ : getContentInsetStart();
+ }
+
+ /**
+ * Sets the start content inset to use when a navigation button is present.
+ *
+ * <p>Different content insets are often called for when additional buttons are present
+ * in the toolbar, as well as at different toolbar sizes. The larger value of
+ * {@link #getContentInsetStart()} and this value will be used during layout.</p>
+ *
+ * @param insetStartWithNavigation the inset to use when a navigation icon has been set
+ * in pixels
+ *
+ * @see #getContentInsetStartWithNavigation()
+ * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation
+ */
+ public void setContentInsetStartWithNavigation(int insetStartWithNavigation) {
+ if (insetStartWithNavigation < 0) {
+ insetStartWithNavigation = RtlSpacingHelper.UNDEFINED;
+ }
+ if (insetStartWithNavigation != mContentInsetStartWithNavigation) {
+ mContentInsetStartWithNavigation = insetStartWithNavigation;
+ if (getNavigationIcon() != null) {
+ requestLayout();
+ }
+ }
+ }
+
+ /**
+ * Gets the end content inset to use when action buttons are present.
+ *
+ * <p>Different content insets are often called for when additional buttons are present
+ * in the toolbar, as well as at different toolbar sizes. The larger value of
+ * {@link #getContentInsetEnd()} and this value will be used during layout.</p>
+ *
+ * @return the end content inset used when a menu has been set in pixels
+ *
+ * @see #setContentInsetEndWithActions(int)
+ * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions
+ */
+ public int getContentInsetEndWithActions() {
+ return mContentInsetEndWithActions != RtlSpacingHelper.UNDEFINED
+ ? mContentInsetEndWithActions
+ : getContentInsetEnd();
+ }
+
+ /**
+ * Sets the start content inset to use when action buttons are present.
+ *
+ * <p>Different content insets are often called for when additional buttons are present
+ * in the toolbar, as well as at different toolbar sizes. The larger value of
+ * {@link #getContentInsetEnd()} and this value will be used during layout.</p>
+ *
+ * @param insetEndWithActions the inset to use when a menu has been set in pixels
+ *
+ * @see #setContentInsetEndWithActions(int)
+ * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions
+ */
+ public void setContentInsetEndWithActions(int insetEndWithActions) {
+ if (insetEndWithActions < 0) {
+ insetEndWithActions = RtlSpacingHelper.UNDEFINED;
+ }
+ if (insetEndWithActions != mContentInsetEndWithActions) {
+ mContentInsetEndWithActions = insetEndWithActions;
+ if (getNavigationIcon() != null) {
+ requestLayout();
+ }
+ }
+ }
+
+ /**
+ * Gets the content inset that will be used on the starting side of the bar in the current
+ * toolbar configuration.
+ *
+ * @return the current content inset start in pixels
+ *
+ * @see #getContentInsetStartWithNavigation()
+ */
+ public int getCurrentContentInsetStart() {
+ return getNavigationIcon() != null
+ ? Math.max(getContentInsetStart(), Math.max(mContentInsetStartWithNavigation, 0))
+ : getContentInsetStart();
+ }
+
+ /**
+ * Gets the content inset that will be used on the ending side of the bar in the current
+ * toolbar configuration.
+ *
+ * @return the current content inset end in pixels
+ *
+ * @see #getContentInsetEndWithActions()
+ */
+ public int getCurrentContentInsetEnd() {
+ boolean hasActions = false;
+ if (mMenuView != null) {
+ final MenuBuilder mb = mMenuView.peekMenu();
+ hasActions = mb != null && mb.hasVisibleItems();
+ }
+ return hasActions
+ ? Math.max(getContentInsetEnd(), Math.max(mContentInsetEndWithActions, 0))
+ : getContentInsetEnd();
+ }
+
+ /**
+ * Gets the content inset that will be used on the left side of the bar in the current
+ * toolbar configuration.
+ *
+ * @return the current content inset left in pixels
+ *
+ * @see #getContentInsetStartWithNavigation()
+ * @see #getContentInsetEndWithActions()
+ */
+ public int getCurrentContentInsetLeft() {
+ return isLayoutRtl()
+ ? getCurrentContentInsetEnd()
+ : getCurrentContentInsetStart();
+ }
+
+ /**
+ * Gets the content inset that will be used on the right side of the bar in the current
+ * toolbar configuration.
+ *
+ * @return the current content inset right in pixels
+ *
+ * @see #getContentInsetStartWithNavigation()
+ * @see #getContentInsetEndWithActions()
+ */
+ public int getCurrentContentInsetRight() {
+ return isLayoutRtl()
+ ? getCurrentContentInsetStart()
+ : getCurrentContentInsetEnd();
+ }
+
private void ensureNavButtonView() {
if (mNavButtonView == null) {
mNavButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle);
@@ -1406,7 +1571,7 @@
childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState());
}
- final int contentInsetStart = getContentInsetStart();
+ final int contentInsetStart = getCurrentContentInsetStart();
width += Math.max(contentInsetStart, navWidth);
collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth);
@@ -1420,7 +1585,7 @@
childState = combineMeasuredStates(childState, mMenuView.getMeasuredState());
}
- final int contentInsetEnd = getContentInsetEnd();
+ final int contentInsetEnd = getCurrentContentInsetEnd();
width += Math.max(contentInsetEnd, menuWidth);
collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth);
@@ -1543,10 +1708,12 @@
}
}
- collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left);
- collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right));
- left = Math.max(left, getContentInsetLeft());
- right = Math.min(right, width - paddingRight - getContentInsetRight());
+ final int contentInsetLeft = getCurrentContentInsetLeft();
+ final int contentInsetRight = getCurrentContentInsetRight();
+ collapsingMargins[0] = Math.max(0, contentInsetLeft - left);
+ collapsingMargins[1] = Math.max(0, contentInsetRight - (width - paddingRight - right));
+ left = Math.max(left, contentInsetLeft);
+ right = Math.min(right, width - paddingRight - contentInsetRight);
if (shouldLayout(mExpandedActionView)) {
if (isRtl) {
diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java
index d8d6e56..36db5d7 100644
--- a/core/java/com/android/internal/app/LocaleHelper.java
+++ b/core/java/com/android/internal/app/LocaleHelper.java
@@ -181,6 +181,7 @@
public static final class LocaleInfoComparator implements Comparator<LocaleStore.LocaleInfo> {
private final Collator mCollator;
private final boolean mCountryMode;
+ private static final String PREFIX_ARABIC = "\u0627\u0644"; // ALEF-LAM, ال
/**
* Constructor.
@@ -192,6 +193,20 @@
mCountryMode = countryMode;
}
+ /*
+ * The Arabic collation should ignore Alef-Lam at the beginning (b/26277596)
+ *
+ * We look at the label's locale, not the current system locale.
+ * This is because the name of the Arabic language itself is in Arabic,
+ * and starts with Alef-Lam, no matter what the system locale is.
+ */
+ private String removePrefixForCompare(Locale locale, String str) {
+ if ("ar".equals(locale.getLanguage()) && str.startsWith(PREFIX_ARABIC)) {
+ return str.substring(PREFIX_ARABIC.length());
+ }
+ return str;
+ }
+
/**
* Compares its two arguments for order.
*
@@ -206,7 +221,9 @@
// and "all others" (== 0)
if (mCountryMode || (lhs.isSuggested() == rhs.isSuggested())) {
// They are in the same "bucket" (suggested / others), so we compare the text
- return mCollator.compare(lhs.getLabel(mCountryMode), rhs.getLabel(mCountryMode));
+ return mCollator.compare(
+ removePrefixForCompare(lhs.getLocale(), lhs.getLabel(mCountryMode)),
+ removePrefixForCompare(rhs.getLocale(), rhs.getLabel(mCountryMode)));
} else {
// One locale is suggested and one is not, so we put them in different "buckets"
return lhs.isSuggested() ? -1 : 1;
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 4b695b9..ea0fbda 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -209,6 +209,7 @@
private Drawable mResizingBackgroundDrawable;
private Drawable mCaptionBackgroundDrawable;
private Drawable mUserCaptionBackgroundDrawable;
+ private Drawable mOriginalBackgroundDrawable;
private float mAvailableWidth;
@@ -888,6 +889,11 @@
mBackgroundPadding.setEmpty();
}
drawableChanged();
+
+ // Make sure we don't reset to the old drawable when finishing resizing.
+ if (mResizeMode != RESIZE_MODE_INVALID) {
+ mOriginalBackgroundDrawable = null;
+ }
}
}
@@ -1950,6 +1956,9 @@
updateElevation();
updateColorViews(null /* insets */, false);
+
+ mOriginalBackgroundDrawable = getBackground();
+ setBackgroundDrawable(null);
}
mResizeMode = resizeMode;
getViewRootImpl().requestInvalidateRootRenderNode();
@@ -1961,6 +1970,10 @@
updateColorViews(null /* insets */, false);
mResizeMode = RESIZE_MODE_INVALID;
getViewRootImpl().requestInvalidateRootRenderNode();
+ if (mOriginalBackgroundDrawable != null) {
+ setBackgroundDrawable(mOriginalBackgroundDrawable);
+ mOriginalBackgroundDrawable = null;
+ }
}
@Override
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 794a6d6..d80b63a 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -680,7 +680,7 @@
}
@Override
- public void onMultiWindowChanged() {
+ public void onMultiWindowModeChanged() {
if (mDecor != null) {
mDecor.onConfigurationChanged(getContext().getResources().getConfiguration());
}
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 6c1ebb4..3a4afad 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -27,13 +27,12 @@
import android.os.RemoteException;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
-
-import java.lang.ref.WeakReference;
+import android.view.inputmethod.InputConnectionInspector;
+import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
public abstract class IInputConnectionWrapper extends IInputContext.Stub {
static final String TAG = "IInputConnectionWrapper";
@@ -61,7 +60,7 @@
private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
private static final int DO_CLEAR_META_KEY_STATES = 130;
private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
- private static final int DO_REPORT_FINISH = 150;
+ private static final int DO_CLOSE_CONNECTION = 150;
@GuardedBy("mLock")
@Nullable
@@ -222,8 +221,8 @@
seq, callback));
}
- public void reportFinish() {
- dispatchMessage(obtainMessage(DO_REPORT_FINISH));
+ public void closeConnection() {
+ dispatchMessage(obtainMessage(DO_CLOSE_CONNECTION));
}
void dispatchMessage(Message msg) {
@@ -501,10 +500,10 @@
}
return;
}
- case DO_REPORT_FINISH: {
+ case DO_CLOSE_CONNECTION: {
// Note that we do not need to worry about race condition here, because 1) mFinished
// is updated only inside this block, and 2) the code here is running on a Handler
- // hence we assume multiple DO_REPORT_FINISH messages will not be handled at the
+ // hence we assume multiple DO_CLOSE_CONNECTION messages will not be handled at the
// same time.
if (isFinished()) {
return;
@@ -518,11 +517,10 @@
if (ic == null) {
return;
}
- ic.finishComposingText();
- // TODO: Make reportFinish() public method of InputConnection to remove this
- // check.
- if (ic instanceof BaseInputConnection) {
- ((BaseInputConnection) ic).reportFinish();
+ @MissingMethodFlags
+ final int missingMethods = InputConnectionInspector.getMissingMethodFlags(ic);
+ if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) {
+ ic.closeConnection();
}
} finally {
synchronized (mLock) {
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 85b8606..f9884d8 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -487,6 +487,10 @@
return null;
}
+ public void closeConnection() {
+ // Nothing should happen when called from input method.
+ }
+
private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
return (mMissingMethods & methodFlag) == methodFlag;
}
diff --git a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
index 526e2ae..9e2cbdb 100644
--- a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
+++ b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
@@ -45,7 +45,8 @@
private static float[] createLUT(TimeInterpolator interpolator, long duration) {
long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
- int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
+ // We need 2 frame values as the minimal.
+ int numAnimFrames = Math.max(2, (int) Math.ceil(((double) duration) / animIntervalMs));
float values[] = new float[numAnimFrames];
float lastFrame = numAnimFrames - 1;
for (int i = 0; i < numAnimFrames; i++) {
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 31b2f96..df57639 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -65,7 +65,6 @@
private final Context mContext;
private final Resources mResources;
- private final boolean mShowCascadingMenus;
/**
* Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
@@ -188,9 +187,6 @@
public MenuBuilder(Context context) {
mContext = context;
mResources = context.getResources();
- mShowCascadingMenus = context.getResources().getBoolean(
- com.android.internal.R.bool.config_enableCascadingSubmenus);
-
mItems = new ArrayList<MenuItemImpl>();
mVisibleItems = new ArrayList<MenuItemImpl>();
@@ -915,10 +911,6 @@
close(true /* closeAllMenus */);
}
} else if (itemImpl.hasSubMenu() || providerHasSubMenu) {
- if (!mShowCascadingMenus) {
- close(false /* closeAllMenus */);
- }
-
if (!itemImpl.hasSubMenu()) {
itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl));
}
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index 2cb224e..8ced36f 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -240,7 +240,10 @@
mTreeObserver = null;
}
mShownAnchorView.removeOnAttachStateChangeListener(mAttachStateChangeListener);
- mOnDismissListener.onDismiss();
+
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
+ }
}
@Override
@@ -265,6 +268,13 @@
subPopup.setPresenterCallback(mPresenterCallback);
subPopup.setForceShowIcon(mAdapter.getForceShowIcon());
+ // Pass responsibility for handling onDismiss to the submenu.
+ subPopup.setOnDismissListener(mOnDismissListener);
+ mOnDismissListener = null;
+
+ // Close this menu popup to make room for the submenu popup.
+ dismiss();
+
// Show the new sub-menu popup at the same location as this popup.
if (subPopup.tryShow(mXOffset, mYOffset)) {
if (mPresenterCallback != null) {
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
index 409a17f..e59d7ba 100644
--- a/core/java/com/android/internal/widget/DecorCaptionView.java
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -136,7 +136,7 @@
public void setPhoneWindow(PhoneWindow owner, boolean show) {
mOwner = owner;
mShow = show;
- mOverlayWithAppContent = owner.getOverlayDecorCaption();
+ mOverlayWithAppContent = owner.isOverlayWithDecorCaptionEnabled();
if (mOverlayWithAppContent) {
// The caption is covering the content, so we make its background transparent to make
// the content visible.
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index a96b5a0..7f7528d 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -83,13 +83,9 @@
return false;
}
- /**
- * @hide
- */
@Override
- public void reportFinish() {
- super.reportFinish();
-
+ public void closeConnection() {
+ super.closeConnection();
synchronized(this) {
while (mBatchEditNesting > 0) {
endBatchEdit();
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index e39bb1c..90f407f 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -36,15 +36,17 @@
#include "core_jni_helpers.h"
-static struct {
+namespace {
+
+using namespace android;
+
+struct {
jclass clazz;
jmethodID dispatchSensorEvent;
jmethodID dispatchFlushCompleteEvent;
jmethodID dispatchAdditionalInfoEvent;
} gBaseEventQueueClassInfo;
-namespace android {
-
struct SensorOffsets
{
jclass clazz;
@@ -72,9 +74,9 @@
} gListOffsets;
/*
- * The method below are not thread-safe and not intended to be
+ * nativeClassInit is not inteneded to be thread-safe. It should be called before other native...
+ * functions (except nativeCreate).
*/
-
static void
nativeClassInit (JNIEnv *_env, jclass _this)
{
@@ -494,9 +496,7 @@
(void*)nativeInjectSensorData },
};
-}; // namespace android
-
-using namespace android;
+} //unnamed namespace
int register_android_hardware_SensorManager(JNIEnv *env)
{
diff --git a/core/res/res/values-sw600dp/dimens_material.xml b/core/res/res/values-sw600dp/dimens_material.xml
index 3bbb352..1ec5c0f 100644
--- a/core/res/res/values-sw600dp/dimens_material.xml
+++ b/core/res/res/values-sw600dp/dimens_material.xml
@@ -23,6 +23,8 @@
<dimen name="action_bar_default_height_material">64dp</dimen>
<!-- Default content inset of an action bar. -->
<dimen name="action_bar_content_inset_material">24dp</dimen>
+ <!-- Default content inset of an action bar with navigation present. -->
+ <dimen name="action_bar_content_inset_with_nav">80dp</dimen>
<!-- Default start padding of an action bar. -->
<dimen name="action_bar_default_padding_start_material">8dp</dimen>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 00eb81a..4429001 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4384,8 +4384,7 @@
<attr name="autoLink" />
<!-- If set to false, keeps the movement method from being set
to the link movement method even if autoLink causes links
- to be found or the input text contains a
- {@link android.text.style.ClickableSpan ClickableSpan}. -->
+ to be found. -->
<attr name="linksClickable" format="boolean" />
<!-- If set, specifies that this TextView has a numeric input method.
The default is false.
@@ -7644,6 +7643,12 @@
<!-- Minimum inset for content views within a bar. Navigation buttons and
menu views are excepted. Only valid for some themes and configurations. -->
<attr name="contentInsetRight" format="dimension" />
+ <!-- Minimum inset for content views within a bar when a navigation button
+ is present, such as the Up button. Only valid for some themes and configurations. -->
+ <attr name="contentInsetStartWithNavigation" format="dimension" />
+ <!-- Minimum inset for content views within a bar when actions from a menu
+ are present. Only valid for some themes and configurations. -->
+ <attr name="contentInsetEndWithActions" format="dimension" />
<!-- Elevation for the action bar itself -->
<attr name="elevation" />
<!-- Reference to a theme that should be used to inflate popups
@@ -8011,6 +8016,8 @@
<attr name="contentInsetEnd" />
<attr name="contentInsetLeft" />
<attr name="contentInsetRight" />
+ <attr name="contentInsetStartWithNavigation" />
+ <attr name="contentInsetEndWithActions" />
<attr name="maxButtonHeight" format="dimension" />
<attr name="navigationButtonStyle" format="reference" />
<attr name="buttonGravity">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6ecaa1f..892b3d5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2497,4 +2497,8 @@
<!-- True if the device supports at least one form of multi-window.
E.g. freeform, split-screen, picture-in-picture. -->
<bool name="config_supportsMultiWindow">true</bool>
+
+ <!-- True if the device requires AppWidgetService even if it does not have
+ the PackageManager.FEATURE_APP_WIDGETS feature -->
+ <bool name="config_enableAppWidgetService">false</bool>
</resources>
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index 2fe4f66..ad2b335d 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -41,6 +41,8 @@
<dimen name="action_bar_default_padding_end_material">0dp</dimen>
<!-- Default content inset of an action bar. -->
<dimen name="action_bar_content_inset_material">16dp</dimen>
+ <!-- Default content inset of an action bar when a navigation button is present. -->
+ <dimen name="action_bar_content_inset_with_nav">72dp</dimen>
<!-- Vertical padding around action bar icons. -->
<dimen name="action_bar_icon_vertical_padding_material">16dp</dimen>
<!-- Top margin for action bar subtitles -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ac29f92..0839187 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2711,6 +2711,8 @@
<public type="attr" name="popupExitTransition" />
<public type="attr" name="minimalHeight" />
<public type="attr" name="forceHasOverlappingRendering" />
+ <public type="attr" name="contentInsetStartWithNavigation" />
+ <public type="attr" name="contentInsetEndWithActions" />
<public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
<public type="style" name="Widget.Material.SeekBar.Discrete" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6041f637c..b878b00 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2953,10 +2953,11 @@
<!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=30] -->
<string name="hardware">Show virtual keyboard</string>
- <!-- Title of the notification to prompt the user to select a keyboard layout. -->
- <string name="select_keyboard_layout_notification_title">Select keyboard layout</string>
- <!-- Message of the notification to prompt the user to select a keyboard layout. -->
- <string name="select_keyboard_layout_notification_message">Touch to select a keyboard layout.</string>
+ <!-- Title of the notification to prompt the user to configure physical keyboard settings. -->
+ <string name="select_keyboard_layout_notification_title">Configure physical keyboard</string>
+ <!-- Message of the notification to prompt the user to configure physical keyboard settings
+ where the user can associate language with physical keyboard layout. -->
+ <string name="select_keyboard_layout_notification_message">Tap to select language and layout</string>
<string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
<string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 86b9f1d..790dcfa 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1234,6 +1234,7 @@
<item name="collapseIcon">?attr/homeAsUpIndicator</item>
<item name="collapseContentDescription">@string/toolbar_collapse_description</item>
<item name="contentInsetStart">16dp</item>
+ <item name="contentInsetStartWithNavigation">@dimen/action_bar_content_inset_with_nav</item>
<item name="touchscreenBlocksFocus">true</item>
</style>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index e636bc0..2420c1a 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -311,7 +311,7 @@
<style name="TextAppearance.Material.Widget.PopupMenu.Header">
<item name="fontFamily">@string/font_family_title_material</item>
<item name="textSize">@dimen/text_size_menu_header_material</item>
- <item name="textColor">?attr/textColorSecondary</item>
+ <item name="textColor">?attr/colorAccent</item>
</style>
<style name="TextAppearance.Material.Widget.DropDownHint" parent="TextAppearance.Material.Menu" />
@@ -944,6 +944,7 @@
<item name="homeLayout">@layout/action_bar_home_material</item>
<item name="gravity">center_vertical</item>
<item name="contentInsetStart">@dimen/action_bar_content_inset_material</item>
+ <item name="contentInsetStartWithNavigation">@dimen/action_bar_content_inset_with_nav</item>
<item name="contentInsetEnd">@dimen/action_bar_content_inset_material</item>
<item name="elevation">@dimen/action_bar_elevation_material</item>
<item name="popupTheme">?attr/actionBarPopupTheme</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6526571..694e934 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -307,6 +307,7 @@
<java-symbol type="bool" name="config_supportsMultiWindow" />
<java-symbol type="bool" name="config_guestUserEphemeral" />
<java-symbol type="bool" name="config_localDisplaysMirrorContent" />
+ <java-symbol type="bool" name="config_enableAppWidgetService" />
<java-symbol type="string" name="config_defaultPictureInPictureBounds" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index b780778..b7926cf 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1093,7 +1093,12 @@
</intent-filter>
</activity>
-
+ <activity android:name="android.view.ViewCaptureTestActivity" android:label="ViewCaptureTestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
<!-- Activity-level metadata -->
<meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" />
@@ -1251,6 +1256,15 @@
<service android:name="android.os.BinderThreadPriorityService"
android:process=":BinderThreadPriorityService" />
+ <!-- Used by ApplyOverrideConfigurationTest -->
+ <activity android:name="android.app.activity.ApplyOverrideConfigurationActivity"
+ android:configChanges="orientation|screenSize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<!-- Application components used for search manager tests -->
<activity android:name="android.app.activity.SearchableActivity"
diff --git a/core/tests/coretests/res/drawable-nodpi/view_capture_test_no_children_golden.png b/core/tests/coretests/res/drawable-nodpi/view_capture_test_no_children_golden.png
new file mode 100644
index 0000000..52092c4
--- /dev/null
+++ b/core/tests/coretests/res/drawable-nodpi/view_capture_test_no_children_golden.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-nodpi/view_capture_test_with_children_golden.png b/core/tests/coretests/res/drawable-nodpi/view_capture_test_with_children_golden.png
new file mode 100644
index 0000000..3bcbd9a
--- /dev/null
+++ b/core/tests/coretests/res/drawable-nodpi/view_capture_test_with_children_golden.png
Binary files differ
diff --git a/core/tests/coretests/res/layout/view_capture_snapshot.xml b/core/tests/coretests/res/layout/view_capture_snapshot.xml
new file mode 100644
index 0000000..0b589a4
--- /dev/null
+++ b/core/tests/coretests/res/layout/view_capture_snapshot.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2016, 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout android:id="@+id/capture"
+ android:background="#00f"
+ android:orientation="vertical"
+ android:layout_width="100px"
+ android:layout_height="100px">
+
+ <View android:id="@+id/child1"
+ android:background="#f00"
+ android:layout_width="match_parent"
+ android:layout_height="25px"/>
+
+ <View android:id="@+id/child2"
+ android:background="#fff"
+ android:layout_width="match_parent"
+ android:layout_height="25px"
+ android:visibility="invisible"/>
+
+ <View android:id="@+id/child3"
+ android:background="#ff0"
+ android:layout_width="match_parent"
+ android:layout_height="25px"
+ android:visibility="gone"/>
+
+ <View android:id="@+id/child4"
+ android:background="#0ff"
+ android:layout_width="match_parent"
+ android:layout_height="25px" />
+ </LinearLayout>
+</FrameLayout>
diff --git a/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationActivity.java b/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationActivity.java
new file mode 100644
index 0000000..3df522d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+package android.app.activity;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+
+public class ApplyOverrideConfigurationActivity extends Activity {
+
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(newBase);
+
+ Configuration overrideConfig = new Configuration();
+ overrideConfig.smallestScreenWidthDp = ApplyOverrideConfigurationTest.OVERRIDE_WIDTH;
+ applyOverrideConfiguration(overrideConfig);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationTest.java b/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationTest.java
new file mode 100644
index 0000000..15ed77e
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/ApplyOverrideConfigurationTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+package android.app.activity;
+
+import android.app.UiAutomation;
+import android.content.res.Configuration;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.ActivityInstrumentationTestCase2;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ApplyOverrideConfigurationTest extends
+ ActivityInstrumentationTestCase2<ApplyOverrideConfigurationActivity> {
+
+ public static final int OVERRIDE_WIDTH = 9999;
+
+ public ApplyOverrideConfigurationTest() {
+ super(ApplyOverrideConfigurationActivity.class);
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+ getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
+ }
+
+ @Test
+ public void testConfigurationIsOverriden() throws Exception {
+ Configuration config = getActivity().getResources().getConfiguration();
+ assertEquals(OVERRIDE_WIDTH, config.smallestScreenWidthDp);
+
+ getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_90);
+
+ config = getActivity().getResources().getConfiguration();
+ assertEquals(OVERRIDE_WIDTH, config.smallestScreenWidthDp);
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
+ super.tearDown();
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 3cadbf6..d4bb0f3 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.app.ResourcesManager;
import android.os.Binder;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.filters.SmallTest;
import android.util.DisplayMetrics;
import android.util.LocaleList;
import android.util.TypedValue;
@@ -58,7 +58,7 @@
}
@Override
- protected DisplayMetrics getDisplayMetricsLocked(int displayId) {
+ protected DisplayMetrics getDisplayMetrics(int displayId) {
return mDisplayMetrics;
}
};
@@ -173,25 +173,12 @@
// The implementations should be the same.
assertSame(resources1.getImpl(), resources2.getImpl());
-
- final Configuration overrideConfig = new Configuration();
- overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
- Resources resources3 = mResourcesManager.getResources(
- activity2, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY,
- overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
-
- // Since we requested new resources for activity2, the resource should be the same
- // as the one returned before for activity2.
- assertSame(resources2, resources3);
-
- // But the implementation has changed.
- assertNotSame(resources1.getImpl(), resources2.getImpl());
}
@SmallTest
public void testThemesGetUpdatedWithNewImpl() {
Binder activity1 = new Binder();
- Resources resources1 = mResourcesManager.getResources(
+ Resources resources1 = mResourcesManager.createBaseActivityResources(
activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
assertNotNull(resources1);
@@ -207,16 +194,59 @@
final Configuration overrideConfig = new Configuration();
overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
- Resources resources2 = mResourcesManager.getResources(
- activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY,
- overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
- assertNotNull(resources2);
- assertSame(resources1, resources2);
- assertSame(resources2, theme.getResources());
+ mResourcesManager.updateResourcesForActivity(activity1, overrideConfig);
+ assertSame(resources1, theme.getResources());
// Make sure we can still access the data.
assertTrue(theme.resolveAttribute(android.R.attr.windowNoTitle, value, true));
assertEquals(TypedValue.TYPE_INT_BOOLEAN, value.type);
assertTrue(value.data != 0);
}
+
+ @SmallTest
+ public void testMultipleResourcesForOneActivityGetUpdatedWhenActivityBaseUpdates() {
+ Binder activity1 = new Binder();
+
+ // Create a Resources for the Activity.
+ Configuration config1 = new Configuration();
+ config1.densityDpi = 280;
+ Resources resources1 = mResourcesManager.createBaseActivityResources(
+ activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config1,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources1);
+
+ // Create a Resources based on the Activity.
+ Configuration config2 = new Configuration();
+ config2.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES;
+ Resources resources2 = mResourcesManager.getResources(
+ activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config2,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources2);
+
+ assertNotSame(resources1, resources2);
+ assertNotSame(resources1.getImpl(), resources2.getImpl());
+
+ final Configuration expectedConfig1 = new Configuration();
+ expectedConfig1.setLocales(LocaleList.getAdjustedDefault());
+ expectedConfig1.densityDpi = 280;
+ assertEquals(expectedConfig1, resources1.getConfiguration());
+
+ // resources2 should be based on the Activity's override config, so the density should
+ // be the same as resources1.
+ final Configuration expectedConfig2 = new Configuration();
+ expectedConfig2.setLocales(LocaleList.getAdjustedDefault());
+ expectedConfig2.densityDpi = 280;
+ expectedConfig2.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES;
+ assertEquals(expectedConfig2, resources2.getConfiguration());
+
+ // Now update the Activity base override, and both resources should update.
+ config1.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mResourcesManager.updateResourcesForActivity(activity1, config1);
+
+ expectedConfig1.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ assertEquals(expectedConfig1, resources1.getConfiguration());
+
+ expectedConfig2.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ assertEquals(expectedConfig2, resources2.getConfiguration());
+ }
}
diff --git a/core/tests/coretests/src/android/print/BasePrintTest.java b/core/tests/coretests/src/android/print/BasePrintTest.java
index c9bc8aa..d56a405 100644
--- a/core/tests/coretests/src/android/print/BasePrintTest.java
+++ b/core/tests/coretests/src/android/print/BasePrintTest.java
@@ -28,6 +28,7 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.print.PrintAttributes;
@@ -281,7 +282,8 @@
}
if (onRequestCustomPrinterIcon != null) {
doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
- any(PrinterId.class), any(CustomPrinterIconCallback.class));
+ any(PrinterId.class), any(CancellationSignal.class),
+ any(CustomPrinterIconCallback.class));
}
if (onStopPrinterStateTracking != null) {
doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
diff --git a/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java b/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java
index 26b7cae..be002e2 100644
--- a/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java
+++ b/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java
@@ -16,6 +16,7 @@
package android.print.mockservice;
+import android.os.CancellationSignal;
import android.print.PrinterId;
import android.printservice.CustomPrinterIconCallback;
@@ -42,7 +43,7 @@
public abstract void onStartPrinterStateTracking(PrinterId printerId);
public abstract void onRequestCustomPrinterIcon(PrinterId printerId,
- CustomPrinterIconCallback callback);
+ CancellationSignal cancellationSignal, CustomPrinterIconCallback callback);
public abstract void onStopPrinterStateTracking(PrinterId printerId);
diff --git a/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java b/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
index 04683f2..e132d79 100644
--- a/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
+++ b/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
@@ -16,6 +16,7 @@
package android.print.mockservice;
+import android.os.CancellationSignal;
import android.print.PrinterId;
import android.printservice.CustomPrinterIconCallback;
import android.printservice.PrintService;
@@ -70,9 +71,9 @@
@Override
public void onRequestCustomPrinterIcon(PrinterId printerId,
- CustomPrinterIconCallback callback) {
+ CancellationSignal cancellationSignal, CustomPrinterIconCallback callback) {
if (mCallbacks != null) {
- mCallbacks.onRequestCustomPrinterIcon(printerId, callback);
+ mCallbacks.onRequestCustomPrinterIcon(printerId, cancellationSignal, callback);
}
}
diff --git a/core/tests/coretests/src/android/view/ViewCaptureTest.java b/core/tests/coretests/src/android/view/ViewCaptureTest.java
new file mode 100644
index 0000000..15cfe23
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewCaptureTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package android.view;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseIntArray;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+public class ViewCaptureTest {
+
+ private static final SparseIntArray EXPECTED_CHILDREN_VISIBILITY = new SparseIntArray();
+ static {
+ EXPECTED_CHILDREN_VISIBILITY.append(R.id.child1, View.VISIBLE);
+ EXPECTED_CHILDREN_VISIBILITY.append(R.id.child2, View.INVISIBLE);
+ EXPECTED_CHILDREN_VISIBILITY.append(R.id.child3, View.GONE);
+ EXPECTED_CHILDREN_VISIBILITY.append(R.id.child4, View.VISIBLE);
+ }
+
+ @Rule
+ public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>(
+ ViewCaptureTestActivity.class);
+
+ private Activity mActivity;
+ private ViewGroup mViewToCapture;
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = mActivityRule.getActivity();
+ mViewToCapture = (ViewGroup) mActivity.findViewById(R.id.capture);
+ }
+
+ @Test
+ @SmallTest
+ public void testCreateSnapshot() {
+ assertChildrenVisibility();
+ testCreateSnapshot(true, R.drawable.view_capture_test_no_children_golden);
+ assertChildrenVisibility();
+ testCreateSnapshot(false, R.drawable.view_capture_test_with_children_golden);
+ assertChildrenVisibility();
+ }
+
+ private void testCreateSnapshot(boolean skipChildren, int goldenResId) {
+ Bitmap result = mViewToCapture.createSnapshot(Bitmap.Config.ARGB_8888, 0, skipChildren);
+ Bitmap golden = BitmapFactory.decodeResource(mActivity.getResources(), goldenResId);
+ assertTrue(golden.sameAs(result));
+ }
+
+ private void assertChildrenVisibility() {
+ for (int i = 0; i < EXPECTED_CHILDREN_VISIBILITY.size(); i++) {
+ int id = EXPECTED_CHILDREN_VISIBILITY.keyAt(i);
+ View child = mViewToCapture.findViewById(id);
+ Assert.assertEquals(EXPECTED_CHILDREN_VISIBILITY.get(id), child.getVisibility());
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewCaptureTestActivity.java b/core/tests/coretests/src/android/view/ViewCaptureTestActivity.java
new file mode 100644
index 0000000..20e3eb5
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewCaptureTestActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package android.view;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.frameworks.coretests.R;
+
+public class ViewCaptureTestActivity extends Activity {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.view_capture_snapshot);
+ }
+}
diff --git a/docs/docs-redirect-index.html b/docs/docs-redirect-index.html
index dc0bc72..ea5f186 100644
--- a/docs/docs-redirect-index.html
+++ b/docs/docs-redirect-index.html
@@ -1,8 +1,8 @@
-<html>
-<head>
-<meta http-equiv="refresh" content="0;url=framework/index.html">
-</head>
-<body>
-<a href="framework/index.html">click here if you are not redirected</a>
-</body>
-</html>
+<html>
+<head>
+<meta http-equiv="refresh" content="0;url=framework/index.html">
+</head>
+<body>
+<a href="framework/index.html">click here if you are not redirected</a>
+</body>
+</html>
diff --git a/docs/docs-redirect.html b/docs/docs-redirect.html
index 4e0743e..d83564c 100644
--- a/docs/docs-redirect.html
+++ b/docs/docs-redirect.html
@@ -1,8 +1,8 @@
-<html>
-<head>
-<meta http-equiv="refresh" content="0;url=docs/offline.html">
-</head>
-<body>
-<a href="docs/offline.html">click here if you are not redirected</a>
-</body>
-</html>
+<html>
+<head>
+<meta http-equiv="refresh" content="0;url=docs/offline.html">
+</head>
+<body>
+<a href="docs/offline.html">click here if you are not redirected</a>
+</body>
+</html>
diff --git a/docs/docs-samples-redirect.html b/docs/docs-samples-redirect.html
index 4e549e6..abefe6c 100644
--- a/docs/docs-samples-redirect.html
+++ b/docs/docs-samples-redirect.html
@@ -1,8 +1,8 @@
-<html>
-<head>
-<meta http-equiv="refresh" content="0;url=../../samples/">
-</head>
-<body>
-<a href="../../samples/">click here if you are not redirected</a>
-</body>
-</html>
+<html>
+<head>
+<meta http-equiv="refresh" content="0;url=../../samples/">
+</head>
+<body>
+<a href="../../samples/">click here if you are not redirected</a>
+</body>
+</html>
diff --git a/docs/html/guide/topics/renderscript/reference/overview.jd b/docs/html/guide/topics/renderscript/reference/overview.jd
index 5e824ee..017a713 100644
--- a/docs/html/guide/topics/renderscript/reference/overview.jd
+++ b/docs/html/guide/topics/renderscript/reference/overview.jd
@@ -611,7 +611,7 @@
</tr>
</tbody></table>
<h2>Conversion Functions</h2>
-<p> The functions below convert from a numerical vector type to another, of from one color
+<p> The functions below convert from a numerical vector type to another, or from one color
representation to another.
</p>
<table class='jd-sumtable'><tbody>
diff --git a/docs/html/guide/topics/renderscript/reference/rs_convert.jd b/docs/html/guide/topics/renderscript/reference/rs_convert.jd
index d4bf77fa..89fd040 100644
--- a/docs/html/guide/topics/renderscript/reference/rs_convert.jd
+++ b/docs/html/guide/topics/renderscript/reference/rs_convert.jd
@@ -4,7 +4,7 @@
<div class='renderscript'>
<h2>Overview</h2>
-<p> The functions below convert from a numerical vector type to another, of from one color
+<p> The functions below convert from a numerical vector type to another, or from one color
representation to another.
</p>
<h2>Summary</h2>
diff --git a/docs/html/guide/topics/resources/localization.jd b/docs/html/guide/topics/resources/localization.jd
index 0a96a15..e60ca9f 100644
--- a/docs/html/guide/topics/resources/localization.jd
+++ b/docs/html/guide/topics/resources/localization.jd
@@ -1,484 +1,484 @@
-page.title=Localizing with Resources
-parent.title=Application Resources
-page.tags="localizing","localization","resources", "formats", "l10n"
-parent.link=index.html
-@jd:body
-
-<div id="qv-wrapper">
- <div id="qv">
-
-<h2>Quickview</h2>
-
-<ul>
- <li>Use resource sets to create a localized app.</li>
- <li>Android loads the correct resource set for the user's language and locale.</li>
- <li>If localized resources are not available, Android loads your default resources.</li>
-</ul>
-
-<h2>In this document</h2>
-<ol>
- <li><a href="#resource-switching">Overview: Resource-Switching in Android</a></li>
-<li><a href="#using-framework">Using Resources for Localization</a></li>
-<li><a href="#strategies">Localization Tips</a></li>
-<li><a href="#testing">Testing Localized Applications</a></li>
-</ol>
-
-<h2>See also</h2>
- <ol>
- <li><a href="{@docRoot}distribute/tools/localization-checklist.html">Localization Checklist</a></li>
- <li><a href="{@docRoot}guide/topics/resources/providing-resources.html">Providing Resources</a></li>
- <li><a href="{@docRoot}guide/topics/ui/declaring-layout.html">Layouts</a></li>
- <li><a href="{@docRoot}reference/android/app/Activity.html#ActivityLifecycle">Activity Lifecycle</a></li>
-</ol>
-</div>
-</div>
-
-<p>Android will run on many devices in many regions. To reach the most users,
-your application should handle text, audio files, numbers, currency, and
-graphics in ways appropriate to the locales where your application will be used.
-</p>
-
-<p>This document describes best practices for localizing Android
-applications. The principles apply whether you are developing your application
-using ADT with Eclipse, Ant-based tools, or any other IDE. </p>
-
-<p>You should already have a working knowledge of Java and be familiar with
-Android resource loading, the declaration of user interface elements in XML,
-development considerations such as Activity lifecycle, and general principles of
-internationalization and localization. </p>
-
-<p>It is good practice to use the Android resource framework to separate the
-localized aspects of your application as much as possible from the core Java
-functionality:</p>
-
-<ul>
- <li>You can put most or all of the <em>contents</em> of your application's
-user interface into resource files, as described in this document and in <a
-href="{@docRoot}guide/topics/resources/providing-resources.html">Providing Resources</a>.</li>
- <li>The <em>behavior</em> of the user interface, on the other hand, is driven
-by your Java code.
- For example, if users input data that needs to be formatted or sorted
-differently depending on locale, then you would use Java to handle the data
-programmatically. This document does not cover how to localize your Java code.
-</li>
-</ul>
-
-<p>For a short guide to localizing strings in your app, see the training lesson, <a
-href="{@docRoot}training/basics/supporting-devices/languages.html">Supporting Different Languages</a>. </p>
-
-
-<h2 id="resource-switching">Overview: Resource-Switching in Android</h2>
-
-<p>Resources are text strings, layouts, sounds, graphics, and any other static
-data that your Android application needs. An application can include multiple
-sets of resources, each customized for a different device configuration. When a
-user runs the application, Android automatically selects and loads the
-resources that best match the device.</p>
-
-<p>(This document focuses on localization and locale. For a complete description
-of resource-switching and all the types of configurations that you can
-specify — screen orientation, touchscreen type, and so on — see <a
-href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">Providing
-Alternative Resources</a>.)</p>
-
-<table border="0" cellspacing="0" cellpadding="0">
- <tr border="0">
- <td width="180" style="border: 0pt none ;"><p class="special-note">
- <strong>When you write your application:</strong>
- <br><br>
- You create a set of default resources, plus alternatives to be used in
- different locales.</p></td>
- <td style="border: 0pt none; padding:0">
- <p style="border:0; padding:0"><img src="../../../images/resources/right-arrow.png" alt="right-arrow"
- width="51" height="17"></p></td>
- <td width="180" style="border: 0pt none ;"><p class="special-note">
- <strong>When a user runs your application:</strong>
- <br><br>The Android system selects which resources to load, based on the
- device's locale.</p></td>
- </tr>
-</table>
-
-<p>When you write your application, you create default and alternative resources
-for your application to use. To create resources, you place files within
-specially named subdirectories of the project's <code>res/</code> directory.
-</p>
-
-
-
-<h3 id="defaults-r-important">Why Default Resources Are Important</h3>
-
-<p>Whenever the application runs in a locale for which you have not provided
-locale-specific text, Android will load the default strings from
-<code>res/values/strings.xml</code>. If this default file is absent, or if it
-is missing a string that your application needs, then your application will not run
-and will show an error.
-The example below illustrates what can happen when the default text file is incomplete. </p>
-
-<p><em>Example:</em>
-<p>An application's Java code refers to just two strings, <code>text_a</code> and
- <code>text_b</code>. This application includes a localized resource file
- (<code>res/values-en/strings.xml</code>) that defines <code>text_a</code> and
- <code>text_b</code> in English. This application also includes a default
- resource file (<code>res/values/strings.xml</code>) that includes a
-definition for <code>text_a</code>, but not for <code>text_b</code>:
-<ul>
- <li>This application might compile without a problem. An IDE such as Eclipse
- will not highlight any errors if a resource is missing.</li>
- <li>When this application is launched on a device with locale set to English,
- the application might run without a problem, because
- <code>res/values-en/strings.xml</code> contains both of the needed text
- strings.</li>
- <li>However, <strong>the user will see an error message and a Force Close
- button</strong> when this application is launched on a device set to a
- language other than English. The application will not load.</li>
-</ul>
-
-
-<p>To prevent this situation, make sure that a <code>res/values/strings.xml</code>
- file exists and that it defines every needed string. The situation applies to
- all types of resources, not just strings: You
- need to create a set of default resource files containing all
- the resources that your application calls upon — layouts, drawables,
- animations, etc. For information about testing, see <a href="#test-for-default">
- Testing for Default Resources</a>.</p>
-
-<h2 id="using-framework">Using Resources for Localization</h2>
-
-<h3 id="creating-defaults">How to Create Default Resources</h3>
-
-<p>Put the application's default text in
-a file with the following location and name:</p>
-<p><code> res/values/strings.xml</code> (required directory)</p>
-
-<p>The text strings in <code>res/values/strings.xml</code> should use the
-default language, which is the language that you expect most of your application's users to
-speak. </p>
-
-<p>The default resource set must also include any default drawables and layouts,
- and can include other types of resources such as animations.
-<br>
- <code> res/drawable/</code>(required directory holding at least
- one graphic file, for the application's icon on Google Play)<br>
- <code> res/layout/</code> (required directory holding an XML
- file that defines the default layout)<br>
- <code> res/anim/</code> (required if you have any
- <code>res/anim-<em><qualifiers></em></code> folders)<br>
- <code> res/xml/</code> (required if you have any
- <code>res/xml-<em><qualifiers></em></code> folders)<br>
- <code> res/raw/</code> (required if you have any
- <code>res/raw-<em><qualifiers></em></code> folders)
-</p>
-
-<p class="note"><strong>Tip:</strong> In your code, examine each reference to
- an Android resource. Make sure that a default resource is defined for each
- one. Also make sure that the default string file is complete: A <em>
- localized</em> string file can contain a subset of the strings, but the
- <em>default</em> string file must contain them all.
-</p>
-
-<h3 id="creating-alternatives">How to Create Alternative Resources</h3>
-
-<p>A large part of localizing an application is providing alternative text for
-different languages. In some cases you will also provide alternative graphics,
-sounds, layouts, and other locale-specific resources. </p>
-
-<p>An application can specify many <code>res/<em><qualifiers></em>/</code>
-directories, each with different qualifiers. To create an alternative resource for
-a different locale, you use a qualifier that specifies a language or a
-language-region combination. (The name of a resource directory must conform
-to the naming scheme described in
-<a href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">Providing
-Alternative Resources</a>,
-or else it will not compile.)</p>
-
-<p><em>Example:</em></p>
-
-<p>Suppose that your application's default language is English. Suppose also
-that you want to localize all the text in your application to French, and most
-of the text in your application (everything except the application's title) to
-Japanese. In this case, you could create three alternative <code>strings.xml</code>
-files, each stored in a locale-specific resource directory:</p>
-
-<ol>
- <li><code>res/values/strings.xml</code><br>
- Contains English text for all the strings that the application uses,
-including text for a string named <code>title</code>.</li>
- <li><code>res/values-fr/strings.xml</code><br>
- Contain French text for all the strings, including <code>title</code>.</li>
- <li><code>res/values-ja/strings.xml</code><br>
- Contain Japanese text for all the strings <em>except</em>
-<code>title</code>.<br>
- <code></code></li>
-</ol>
-
-<p>If your Java code refers to <code>R.string.title</code>, here is what will
-happen at runtime:</p>
-
-<ul>
- <li>If the device is set to any language other than French, Android will load
-<code>title</code> from the <code>res/values/strings.xml</code> file.</li>
- <li>If the device is set to French, Android will load <code>title</code> from
-the <code>res/values-fr/strings.xml</code> file.</li>
-</ul>
-
-<p>Notice that if the device is set to Japanese, Android will look for
-<code>title</code> in the <code>res/values-ja/strings.xml</code> file. But
-because no such string is included in that file, Android will fall back to the
-default, and will load <code>title</code> in English from the
-<code>res/values/strings.xml</code> file. </p>
-
-<h3 id="resource-precedence">Which Resources Take Precedence?</h3>
-
-<p> If multiple resource files match a device's configuration, Android follows a
-set of rules in deciding which file to use. Among the qualifiers that can be
-specified in a resource directory name, <strong>locale almost always takes
-precedence</strong>. </p>
-<p><em>Example:</em></p>
-
-<p>Assume that an application includes a default set of graphics and two other
-sets of graphics, each optimized for a different device setup:</p>
-
-<ul>
- <li><code>res/drawable/</code><br>
- Contains
- default graphics.</li>
- <li><code>res/drawable-small-land-stylus/</code><br>
- Contains graphics optimized for use with a device that expects input from a
- stylus and has a QVGA low-density screen in landscape orientation.</li>
- <li><code>res/drawable-ja/</code> <br>
- Contains graphics optimized for use with Japanese.</li>
-</ul>
-
-<p>If the application runs on a device that is configured to use Japanese,
-Android will load graphics from <code>res/drawable-ja/</code>, even if the
-device happens to be one that expects input from a stylus and has a QVGA
-low-density screen in landscape orientation.</p>
-
-<p class="note"><strong>Exception:</strong> The only qualifiers that take
-precedence over locale in the selection process are MCC and MNC (mobile country
-code and mobile network code). </p>
-
-<p><em>Example:</em></p>
-
-<p>Assume that you have the following situation:</p>
-
-<ul>
- <li>The application code calls for <code>R.string.text_a</code></li>
- <li>Two relevant resource files are available:
- <ul>
- <li><code>res/values-mcc404/strings.xml</code>, which includes
-<code>text_a</code> in the application's default language, in this case
-English.</li>
- <li><code>res/values-hi/strings.xml</code>, which includes
-<code>text_a</code> in Hindi.</li>
- </ul>
- </li>
- <li>The application is running on a device that has the following
-configuration:
- <ul>
- <li>The SIM card is connected to a mobile network in India (MCC 404).</li>
- <li>The language is set to Hindi (<code>hi</code>).</li>
- </ul>
- </li>
-</ul>
-
-<p>Android will load <code>text_a</code> from
-<code>res/values-mcc404/strings.xml</code> (in English), even if the device is
-configured for Hindi. That is because in the resource-selection process, Android
-will prefer an MCC match over a language match. </p>
-
-<p>The selection process is not always as straightforward as these examples
-suggest. Please read <a
-href="{@docRoot}guide/topics/resources/providing-resources.html#BestMatch">How Android Finds
-the Best-matching Resource</a> for a more nuanced description of the
-process. All the qualifiers are described and listed in order of
-precedence in <a
-href="{@docRoot}guide/topics/resources/providing-resources.html#table2">Table 2 of Providing
-Alternative Resources</a>.</p>
-
-<h3 id="referring-to-resources">Referring to Resources in Java</h3>
-
-<p>In your application's Java code, you refer to resources using the syntax
-<code>R.<em>resource_type</em>.<em>resource_name</em></code> or
-<code>android.R.<em>resource_type</em>.<em>resource_name</em></code><em>.</em>
-For more about this, see <a
-href="{@docRoot}guide/topics/resources/accessing-resources.html">Accessing Resources</a>.</p>
-
-<h2 id="checklist">Localization Checklist</h2>
-
-<p>For a complete overview of the process of localizing and distributing an Android application,
-see the <a href="{@docRoot}distribute/tools/localization-checklist.html">Localization
-Checklist</a> document.</p>
-
-<h2 id="strategies">Localization Tips</h2>
-
-<h4 id="failing2">Design your application to work in any locale</h4>
-
-<p>You cannot assume anything about the device on which a user will
-run your application. The device might have hardware that you were not
-anticipating, or it might be set to a locale that you did not plan for or that
-you cannot test. Design your application so that it will function normally or fail gracefully no
-matter what device it runs on.</p>
-
-<p class="note"><strong>Important:</strong> Make sure that your application
-includes a full set of default resources.</p> <p>Make sure to include
-<code>res/drawable/</code> and a <code>res/values/</code> folders (without any
-additional modifiers in the folder names) that contain all the images and text
-that your application will need. </p>
-
-<p>If an application is missing even one default resource, it will not run on a
- device that is set to an unsupported locale. For example, the
- <code>res/values/strings.xml</code> default file might lack one string that
- the application needs: When the application runs in an unsupported locale and
- attempts to load <code>res/values/strings.xml</code>, the user will see an
- error message and a Force Close button. An IDE such as Eclipse will not
- highlight this kind of error, and you will not see the problem when you
- test the application on a device or emulator that is set to a supported locale.</p>
-
-<p>For more information, see <a href="#test-for-default">Testing for Default Resources</a>.</p>
-
-<h4>Design a flexible layout</h4>
-
-<p> If you need to rearrange your layout to fit a certain language (for example
-German with its long words), you can create an alternative layout for that
-language (for example <code>res/layout-de/main.xml</code>). However, doing this
-can make your application harder to maintain. It is better to create a single
-layout that is more flexible.</p>
-
-<p>Another typical situation is a language that requires something different in
-its layout. For example, you might have a contact form that should include two
-name fields when the application runs in Japanese, but three name fields when
-the application runs in some other language. You could handle this in either of
-two ways:</p>
-
-<ul>
- <li>Create one layout with a field that you can programmatically enable or
-disable, based on the language, or</li>
- <li>Have the main layout include another layout that includes the changeable
-field. The second layout can have different configurations for different
-languages.</li>
-</ul>
-
-<h4>Avoid creating more resource files and text strings than you need</h4>
-
-<p>You probably do not need to create a locale-specific
-alternative for every resource in your application. For example, the layout
-defined in the <code>res/layout/main.xml</code> file might work in any locale,
-in which case there would be no need to create any alternative layout files.
-</p>
-
-<p>Also, you might not need to create alternative text for every
-string. For example, assume the following:</p>
-
-<ul>
- <li>Your application's default language is American
-English. Every string that the application uses is defined, using American
-English spellings, in <code>res/values/strings.xml</code>. </li>
-
- <li>For a few important phrases, you want to provide
-British English spelling. You want these alternative strings to be used when your
-application runs on a device in the United Kingdom. </li>
-</ul>
-
-<p>To do this, you could create a small file called
-<code>res/values-en-rGB/strings.xml</code> that includes only the strings that
-should be different when the application runs in the U.K. For all the rest of
-the strings, the application will fall back to the defaults and use what is
-defined in <code>res/values/strings.xml</code>.</p>
-
-<h4>Use the Android Context object for manual locale lookup</h4>
-
-<p>You can look up the locale using the {@link android.content.Context} object
-that Android makes available:</p>
-
-<pre>String locale = context.getResources().getConfiguration().locale.getDisplayName();</pre>
-
-<h2 id="testing">Testing Localized Applications</h2>
-
-<h3 id="device">Testing on a Device</h3>
-<p>Keep in mind that the device you are testing may be significantly different from
- the devices available to consumers in other geographies. The locales available
- on your device may differ from those available on other devices. Also, the
- resolution and density of the device screen may differ, which could affect
- the display of strings and drawables in your UI.</p>
-
-<p>To change the locale or language on a device, use the Settings application.</p>
-
-<h3 id="emulator">Testing on an Emulator</h3>
-
-<p>For details about using the emulator, see See <a
-href="{@docRoot}tools/help/emulator.html">Android Emulator</a>.</p>
-<h4>Creating and using a custom locale</h4>
-
-<p>A "custom" locale is a language/region combination that the Android
-system image does not explicitly support. (For a list of supported locales in
-Android platforms see the Version Notes in the <a
-href="{@docRoot}sdk/index.html">SDK</a> tab). You can test
-how your application will run in a custom locale by creating a custom locale in
-the emulator. There are two ways to do this:</p>
-
-<ul>
- <li>Use the Custom Locale application, which is accessible from the
-Application tab. (After you create a custom locale, switch to it by
-pressing and holding the locale name.)</li>
- <li>Change to a custom locale from the adb shell, as described below.</li>
-</ul>
-
-<p>When you set the emulator to a locale that is not available in the Android
-system image, the system itself will display in its default language. Your
-application, however, should localize properly.</p>
-
-<h4>Changing the emulator locale from the adb shell</h4>
-
-<p>To change the locale in the emulator by using the adb shell. </p>
-
-<ol>
- <li>Pick the locale you want to test and determine its BCP-47 language tag, for
-example, Canadian French would be <code>fr-CA</code>.<br>
- </li>
- <li>Launch an emulator.</li>
- <li>From a command-line shell on the host computer, run the following
-command:<br>
- <code>adb shell</code><br>
- or if you have a device attached, specify that you want the emulator by adding
-the <code>-e</code> option:<br>
- <code>adb -e shell</code></li>
- <li>At the adb shell prompt (<code>#</code>), run this command: <br>
- <code>setprop persist.sys.locale [<em>BCP-47 language tag</em>];stop;sleep 5;start <br>
- </code>Replace bracketed sections with the appropriate codes from Step
-1.</li>
-</ol>
-
-<p>For instance, to test in Canadian French:</p>
-
-<p><code>setprop persist.sys.locale fr-CA;stop;sleep 5;start </code></p>
-
-<p>This will cause the emulator to restart. (It will look like a full reboot,
-but it is not.) Once the Home screen appears again, re-launch your application (for
-example, click the Run icon in Eclipse), and the application will launch with
-the new locale. </p>
-
-<h3 id="test-for-default">Testing for Default Resources</h3>
-<p>Here's how to test whether an application includes every string resource that it needs: </p>
-<ol><li>Set the emulator or device to a language that your application does not
- support. For example, if the application has French strings in
- <code>res/values-fr/</code> but does not have any Spanish strings in
- <code>res/values-es/</code>, then set the emulator's locale to Spanish.
- (You can use the Custom Locale application to set the emulator to an
- unsupported locale.)</li>
- <li>Run the application.</li>
-<li>If the application shows an error message and a Force Close button, it might
- be looking for a string that is not available. Make sure that your
- <code>res/values/strings.xml</code> file includes a definition for
- every string that the application uses.</li>
-</ol>
-</p>
-
-<p>If the test is successful, repeat it for other types of
- configurations. For example, if the application has a layout file called
- <code>res/layout-land/main.xml</code> but does not contain a file called
- <code>res/layout-port/main.xml</code>, then set the emulator or device to
- portrait orientation and see if the application will run.
-
-
-
+page.title=Localizing with Resources
+parent.title=Application Resources
+page.tags="localizing","localization","resources", "formats", "l10n"
+parent.link=index.html
+@jd:body
+
+<div id="qv-wrapper">
+ <div id="qv">
+
+<h2>Quickview</h2>
+
+<ul>
+ <li>Use resource sets to create a localized app.</li>
+ <li>Android loads the correct resource set for the user's language and locale.</li>
+ <li>If localized resources are not available, Android loads your default resources.</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+ <li><a href="#resource-switching">Overview: Resource-Switching in Android</a></li>
+<li><a href="#using-framework">Using Resources for Localization</a></li>
+<li><a href="#strategies">Localization Tips</a></li>
+<li><a href="#testing">Testing Localized Applications</a></li>
+</ol>
+
+<h2>See also</h2>
+ <ol>
+ <li><a href="{@docRoot}distribute/tools/localization-checklist.html">Localization Checklist</a></li>
+ <li><a href="{@docRoot}guide/topics/resources/providing-resources.html">Providing Resources</a></li>
+ <li><a href="{@docRoot}guide/topics/ui/declaring-layout.html">Layouts</a></li>
+ <li><a href="{@docRoot}reference/android/app/Activity.html#ActivityLifecycle">Activity Lifecycle</a></li>
+</ol>
+</div>
+</div>
+
+<p>Android will run on many devices in many regions. To reach the most users,
+your application should handle text, audio files, numbers, currency, and
+graphics in ways appropriate to the locales where your application will be used.
+</p>
+
+<p>This document describes best practices for localizing Android
+applications. The principles apply whether you are developing your application
+using ADT with Eclipse, Ant-based tools, or any other IDE. </p>
+
+<p>You should already have a working knowledge of Java and be familiar with
+Android resource loading, the declaration of user interface elements in XML,
+development considerations such as Activity lifecycle, and general principles of
+internationalization and localization. </p>
+
+<p>It is good practice to use the Android resource framework to separate the
+localized aspects of your application as much as possible from the core Java
+functionality:</p>
+
+<ul>
+ <li>You can put most or all of the <em>contents</em> of your application's
+user interface into resource files, as described in this document and in <a
+href="{@docRoot}guide/topics/resources/providing-resources.html">Providing Resources</a>.</li>
+ <li>The <em>behavior</em> of the user interface, on the other hand, is driven
+by your Java code.
+ For example, if users input data that needs to be formatted or sorted
+differently depending on locale, then you would use Java to handle the data
+programmatically. This document does not cover how to localize your Java code.
+</li>
+</ul>
+
+<p>For a short guide to localizing strings in your app, see the training lesson, <a
+href="{@docRoot}training/basics/supporting-devices/languages.html">Supporting Different Languages</a>. </p>
+
+
+<h2 id="resource-switching">Overview: Resource-Switching in Android</h2>
+
+<p>Resources are text strings, layouts, sounds, graphics, and any other static
+data that your Android application needs. An application can include multiple
+sets of resources, each customized for a different device configuration. When a
+user runs the application, Android automatically selects and loads the
+resources that best match the device.</p>
+
+<p>(This document focuses on localization and locale. For a complete description
+of resource-switching and all the types of configurations that you can
+specify — screen orientation, touchscreen type, and so on — see <a
+href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">Providing
+Alternative Resources</a>.)</p>
+
+<table border="0" cellspacing="0" cellpadding="0">
+ <tr border="0">
+ <td width="180" style="border: 0pt none ;"><p class="special-note">
+ <strong>When you write your application:</strong>
+ <br><br>
+ You create a set of default resources, plus alternatives to be used in
+ different locales.</p></td>
+ <td style="border: 0pt none; padding:0">
+ <p style="border:0; padding:0"><img src="../../../images/resources/right-arrow.png" alt="right-arrow"
+ width="51" height="17"></p></td>
+ <td width="180" style="border: 0pt none ;"><p class="special-note">
+ <strong>When a user runs your application:</strong>
+ <br><br>The Android system selects which resources to load, based on the
+ device's locale.</p></td>
+ </tr>
+</table>
+
+<p>When you write your application, you create default and alternative resources
+for your application to use. To create resources, you place files within
+specially named subdirectories of the project's <code>res/</code> directory.
+</p>
+
+
+
+<h3 id="defaults-r-important">Why Default Resources Are Important</h3>
+
+<p>Whenever the application runs in a locale for which you have not provided
+locale-specific text, Android will load the default strings from
+<code>res/values/strings.xml</code>. If this default file is absent, or if it
+is missing a string that your application needs, then your application will not run
+and will show an error.
+The example below illustrates what can happen when the default text file is incomplete. </p>
+
+<p><em>Example:</em>
+<p>An application's Java code refers to just two strings, <code>text_a</code> and
+ <code>text_b</code>. This application includes a localized resource file
+ (<code>res/values-en/strings.xml</code>) that defines <code>text_a</code> and
+ <code>text_b</code> in English. This application also includes a default
+ resource file (<code>res/values/strings.xml</code>) that includes a
+definition for <code>text_a</code>, but not for <code>text_b</code>:
+<ul>
+ <li>This application might compile without a problem. An IDE such as Eclipse
+ will not highlight any errors if a resource is missing.</li>
+ <li>When this application is launched on a device with locale set to English,
+ the application might run without a problem, because
+ <code>res/values-en/strings.xml</code> contains both of the needed text
+ strings.</li>
+ <li>However, <strong>the user will see an error message and a Force Close
+ button</strong> when this application is launched on a device set to a
+ language other than English. The application will not load.</li>
+</ul>
+
+
+<p>To prevent this situation, make sure that a <code>res/values/strings.xml</code>
+ file exists and that it defines every needed string. The situation applies to
+ all types of resources, not just strings: You
+ need to create a set of default resource files containing all
+ the resources that your application calls upon — layouts, drawables,
+ animations, etc. For information about testing, see <a href="#test-for-default">
+ Testing for Default Resources</a>.</p>
+
+<h2 id="using-framework">Using Resources for Localization</h2>
+
+<h3 id="creating-defaults">How to Create Default Resources</h3>
+
+<p>Put the application's default text in
+a file with the following location and name:</p>
+<p><code> res/values/strings.xml</code> (required directory)</p>
+
+<p>The text strings in <code>res/values/strings.xml</code> should use the
+default language, which is the language that you expect most of your application's users to
+speak. </p>
+
+<p>The default resource set must also include any default drawables and layouts,
+ and can include other types of resources such as animations.
+<br>
+ <code> res/drawable/</code>(required directory holding at least
+ one graphic file, for the application's icon on Google Play)<br>
+ <code> res/layout/</code> (required directory holding an XML
+ file that defines the default layout)<br>
+ <code> res/anim/</code> (required if you have any
+ <code>res/anim-<em><qualifiers></em></code> folders)<br>
+ <code> res/xml/</code> (required if you have any
+ <code>res/xml-<em><qualifiers></em></code> folders)<br>
+ <code> res/raw/</code> (required if you have any
+ <code>res/raw-<em><qualifiers></em></code> folders)
+</p>
+
+<p class="note"><strong>Tip:</strong> In your code, examine each reference to
+ an Android resource. Make sure that a default resource is defined for each
+ one. Also make sure that the default string file is complete: A <em>
+ localized</em> string file can contain a subset of the strings, but the
+ <em>default</em> string file must contain them all.
+</p>
+
+<h3 id="creating-alternatives">How to Create Alternative Resources</h3>
+
+<p>A large part of localizing an application is providing alternative text for
+different languages. In some cases you will also provide alternative graphics,
+sounds, layouts, and other locale-specific resources. </p>
+
+<p>An application can specify many <code>res/<em><qualifiers></em>/</code>
+directories, each with different qualifiers. To create an alternative resource for
+a different locale, you use a qualifier that specifies a language or a
+language-region combination. (The name of a resource directory must conform
+to the naming scheme described in
+<a href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">Providing
+Alternative Resources</a>,
+or else it will not compile.)</p>
+
+<p><em>Example:</em></p>
+
+<p>Suppose that your application's default language is English. Suppose also
+that you want to localize all the text in your application to French, and most
+of the text in your application (everything except the application's title) to
+Japanese. In this case, you could create three alternative <code>strings.xml</code>
+files, each stored in a locale-specific resource directory:</p>
+
+<ol>
+ <li><code>res/values/strings.xml</code><br>
+ Contains English text for all the strings that the application uses,
+including text for a string named <code>title</code>.</li>
+ <li><code>res/values-fr/strings.xml</code><br>
+ Contain French text for all the strings, including <code>title</code>.</li>
+ <li><code>res/values-ja/strings.xml</code><br>
+ Contain Japanese text for all the strings <em>except</em>
+<code>title</code>.<br>
+ <code></code></li>
+</ol>
+
+<p>If your Java code refers to <code>R.string.title</code>, here is what will
+happen at runtime:</p>
+
+<ul>
+ <li>If the device is set to any language other than French, Android will load
+<code>title</code> from the <code>res/values/strings.xml</code> file.</li>
+ <li>If the device is set to French, Android will load <code>title</code> from
+the <code>res/values-fr/strings.xml</code> file.</li>
+</ul>
+
+<p>Notice that if the device is set to Japanese, Android will look for
+<code>title</code> in the <code>res/values-ja/strings.xml</code> file. But
+because no such string is included in that file, Android will fall back to the
+default, and will load <code>title</code> in English from the
+<code>res/values/strings.xml</code> file. </p>
+
+<h3 id="resource-precedence">Which Resources Take Precedence?</h3>
+
+<p> If multiple resource files match a device's configuration, Android follows a
+set of rules in deciding which file to use. Among the qualifiers that can be
+specified in a resource directory name, <strong>locale almost always takes
+precedence</strong>. </p>
+<p><em>Example:</em></p>
+
+<p>Assume that an application includes a default set of graphics and two other
+sets of graphics, each optimized for a different device setup:</p>
+
+<ul>
+ <li><code>res/drawable/</code><br>
+ Contains
+ default graphics.</li>
+ <li><code>res/drawable-small-land-stylus/</code><br>
+ Contains graphics optimized for use with a device that expects input from a
+ stylus and has a QVGA low-density screen in landscape orientation.</li>
+ <li><code>res/drawable-ja/</code> <br>
+ Contains graphics optimized for use with Japanese.</li>
+</ul>
+
+<p>If the application runs on a device that is configured to use Japanese,
+Android will load graphics from <code>res/drawable-ja/</code>, even if the
+device happens to be one that expects input from a stylus and has a QVGA
+low-density screen in landscape orientation.</p>
+
+<p class="note"><strong>Exception:</strong> The only qualifiers that take
+precedence over locale in the selection process are MCC and MNC (mobile country
+code and mobile network code). </p>
+
+<p><em>Example:</em></p>
+
+<p>Assume that you have the following situation:</p>
+
+<ul>
+ <li>The application code calls for <code>R.string.text_a</code></li>
+ <li>Two relevant resource files are available:
+ <ul>
+ <li><code>res/values-mcc404/strings.xml</code>, which includes
+<code>text_a</code> in the application's default language, in this case
+English.</li>
+ <li><code>res/values-hi/strings.xml</code>, which includes
+<code>text_a</code> in Hindi.</li>
+ </ul>
+ </li>
+ <li>The application is running on a device that has the following
+configuration:
+ <ul>
+ <li>The SIM card is connected to a mobile network in India (MCC 404).</li>
+ <li>The language is set to Hindi (<code>hi</code>).</li>
+ </ul>
+ </li>
+</ul>
+
+<p>Android will load <code>text_a</code> from
+<code>res/values-mcc404/strings.xml</code> (in English), even if the device is
+configured for Hindi. That is because in the resource-selection process, Android
+will prefer an MCC match over a language match. </p>
+
+<p>The selection process is not always as straightforward as these examples
+suggest. Please read <a
+href="{@docRoot}guide/topics/resources/providing-resources.html#BestMatch">How Android Finds
+the Best-matching Resource</a> for a more nuanced description of the
+process. All the qualifiers are described and listed in order of
+precedence in <a
+href="{@docRoot}guide/topics/resources/providing-resources.html#table2">Table 2 of Providing
+Alternative Resources</a>.</p>
+
+<h3 id="referring-to-resources">Referring to Resources in Java</h3>
+
+<p>In your application's Java code, you refer to resources using the syntax
+<code>R.<em>resource_type</em>.<em>resource_name</em></code> or
+<code>android.R.<em>resource_type</em>.<em>resource_name</em></code><em>.</em>
+For more about this, see <a
+href="{@docRoot}guide/topics/resources/accessing-resources.html">Accessing Resources</a>.</p>
+
+<h2 id="checklist">Localization Checklist</h2>
+
+<p>For a complete overview of the process of localizing and distributing an Android application,
+see the <a href="{@docRoot}distribute/tools/localization-checklist.html">Localization
+Checklist</a> document.</p>
+
+<h2 id="strategies">Localization Tips</h2>
+
+<h4 id="failing2">Design your application to work in any locale</h4>
+
+<p>You cannot assume anything about the device on which a user will
+run your application. The device might have hardware that you were not
+anticipating, or it might be set to a locale that you did not plan for or that
+you cannot test. Design your application so that it will function normally or fail gracefully no
+matter what device it runs on.</p>
+
+<p class="note"><strong>Important:</strong> Make sure that your application
+includes a full set of default resources.</p> <p>Make sure to include
+<code>res/drawable/</code> and a <code>res/values/</code> folders (without any
+additional modifiers in the folder names) that contain all the images and text
+that your application will need. </p>
+
+<p>If an application is missing even one default resource, it will not run on a
+ device that is set to an unsupported locale. For example, the
+ <code>res/values/strings.xml</code> default file might lack one string that
+ the application needs: When the application runs in an unsupported locale and
+ attempts to load <code>res/values/strings.xml</code>, the user will see an
+ error message and a Force Close button. An IDE such as Eclipse will not
+ highlight this kind of error, and you will not see the problem when you
+ test the application on a device or emulator that is set to a supported locale.</p>
+
+<p>For more information, see <a href="#test-for-default">Testing for Default Resources</a>.</p>
+
+<h4>Design a flexible layout</h4>
+
+<p> If you need to rearrange your layout to fit a certain language (for example
+German with its long words), you can create an alternative layout for that
+language (for example <code>res/layout-de/main.xml</code>). However, doing this
+can make your application harder to maintain. It is better to create a single
+layout that is more flexible.</p>
+
+<p>Another typical situation is a language that requires something different in
+its layout. For example, you might have a contact form that should include two
+name fields when the application runs in Japanese, but three name fields when
+the application runs in some other language. You could handle this in either of
+two ways:</p>
+
+<ul>
+ <li>Create one layout with a field that you can programmatically enable or
+disable, based on the language, or</li>
+ <li>Have the main layout include another layout that includes the changeable
+field. The second layout can have different configurations for different
+languages.</li>
+</ul>
+
+<h4>Avoid creating more resource files and text strings than you need</h4>
+
+<p>You probably do not need to create a locale-specific
+alternative for every resource in your application. For example, the layout
+defined in the <code>res/layout/main.xml</code> file might work in any locale,
+in which case there would be no need to create any alternative layout files.
+</p>
+
+<p>Also, you might not need to create alternative text for every
+string. For example, assume the following:</p>
+
+<ul>
+ <li>Your application's default language is American
+English. Every string that the application uses is defined, using American
+English spellings, in <code>res/values/strings.xml</code>. </li>
+
+ <li>For a few important phrases, you want to provide
+British English spelling. You want these alternative strings to be used when your
+application runs on a device in the United Kingdom. </li>
+</ul>
+
+<p>To do this, you could create a small file called
+<code>res/values-en-rGB/strings.xml</code> that includes only the strings that
+should be different when the application runs in the U.K. For all the rest of
+the strings, the application will fall back to the defaults and use what is
+defined in <code>res/values/strings.xml</code>.</p>
+
+<h4>Use the Android Context object for manual locale lookup</h4>
+
+<p>You can look up the locale using the {@link android.content.Context} object
+that Android makes available:</p>
+
+<pre>String locale = context.getResources().getConfiguration().locale.getDisplayName();</pre>
+
+<h2 id="testing">Testing Localized Applications</h2>
+
+<h3 id="device">Testing on a Device</h3>
+<p>Keep in mind that the device you are testing may be significantly different from
+ the devices available to consumers in other geographies. The locales available
+ on your device may differ from those available on other devices. Also, the
+ resolution and density of the device screen may differ, which could affect
+ the display of strings and drawables in your UI.</p>
+
+<p>To change the locale or language on a device, use the Settings application.</p>
+
+<h3 id="emulator">Testing on an Emulator</h3>
+
+<p>For details about using the emulator, see See <a
+href="{@docRoot}tools/help/emulator.html">Android Emulator</a>.</p>
+<h4>Creating and using a custom locale</h4>
+
+<p>A "custom" locale is a language/region combination that the Android
+system image does not explicitly support. (For a list of supported locales in
+Android platforms see the Version Notes in the <a
+href="{@docRoot}sdk/index.html">SDK</a> tab). You can test
+how your application will run in a custom locale by creating a custom locale in
+the emulator. There are two ways to do this:</p>
+
+<ul>
+ <li>Use the Custom Locale application, which is accessible from the
+Application tab. (After you create a custom locale, switch to it by
+pressing and holding the locale name.)</li>
+ <li>Change to a custom locale from the adb shell, as described below.</li>
+</ul>
+
+<p>When you set the emulator to a locale that is not available in the Android
+system image, the system itself will display in its default language. Your
+application, however, should localize properly.</p>
+
+<h4>Changing the emulator locale from the adb shell</h4>
+
+<p>To change the locale in the emulator by using the adb shell. </p>
+
+<ol>
+ <li>Pick the locale you want to test and determine its BCP-47 language tag, for
+example, Canadian French would be <code>fr-CA</code>.<br>
+ </li>
+ <li>Launch an emulator.</li>
+ <li>From a command-line shell on the host computer, run the following
+command:<br>
+ <code>adb shell</code><br>
+ or if you have a device attached, specify that you want the emulator by adding
+the <code>-e</code> option:<br>
+ <code>adb -e shell</code></li>
+ <li>At the adb shell prompt (<code>#</code>), run this command: <br>
+ <code>setprop persist.sys.locale [<em>BCP-47 language tag</em>];stop;sleep 5;start <br>
+ </code>Replace bracketed sections with the appropriate codes from Step
+1.</li>
+</ol>
+
+<p>For instance, to test in Canadian French:</p>
+
+<p><code>setprop persist.sys.locale fr-CA;stop;sleep 5;start </code></p>
+
+<p>This will cause the emulator to restart. (It will look like a full reboot,
+but it is not.) Once the Home screen appears again, re-launch your application (for
+example, click the Run icon in Eclipse), and the application will launch with
+the new locale. </p>
+
+<h3 id="test-for-default">Testing for Default Resources</h3>
+<p>Here's how to test whether an application includes every string resource that it needs: </p>
+<ol><li>Set the emulator or device to a language that your application does not
+ support. For example, if the application has French strings in
+ <code>res/values-fr/</code> but does not have any Spanish strings in
+ <code>res/values-es/</code>, then set the emulator's locale to Spanish.
+ (You can use the Custom Locale application to set the emulator to an
+ unsupported locale.)</li>
+ <li>Run the application.</li>
+<li>If the application shows an error message and a Force Close button, it might
+ be looking for a string that is not available. Make sure that your
+ <code>res/values/strings.xml</code> file includes a definition for
+ every string that the application uses.</li>
+</ol>
+</p>
+
+<p>If the test is successful, repeat it for other types of
+ configurations. For example, if the application has a layout file called
+ <code>res/layout-land/main.xml</code> but does not contain a file called
+ <code>res/layout-port/main.xml</code>, then set the emulator or device to
+ portrait orientation and see if the application will run.
+
+
+
diff --git a/docs/overview-ext.html b/docs/overview-ext.html
index 5720245..f80d629 100644
--- a/docs/overview-ext.html
+++ b/docs/overview-ext.html
@@ -1,8 +1,8 @@
-<body>
-Some random libraries that we've imported:
-<ul>
- <li>GData</li>
- <li>Apache commons HTTPClient</li>
- <li>A sax parser</li>
-</ul>
-</body>
+<body>
+Some random libraries that we've imported:
+<ul>
+ <li>GData</li>
+ <li>Apache commons HTTPClient</li>
+ <li>A sax parser</li>
+</ul>
+</body>
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 8f7c6a62..7871aa8 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -179,10 +179,10 @@
int tag = 0;
String tagStr = parser.getAttributeValue(null, "tag");
if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) {
- tag = tagStr.charAt(0) << 24 +
- tagStr.charAt(1) << 16 +
- tagStr.charAt(2) << 8 +
- tagStr.charAt(3);
+ tag = (tagStr.charAt(0) << 24) +
+ (tagStr.charAt(1) << 16) +
+ (tagStr.charAt(2) << 8) +
+ (tagStr.charAt(3) );
} else {
throw new XmlPullParserException("Invalid tag attribute value.", parser, null);
}
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index cfcb4e0..deea972 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -27,12 +27,13 @@
// APIs used by KeyChain
String requestPrivateKey(String alias);
byte[] getCertificate(String alias);
+ byte[] getCaCertificates(String alias);
// APIs used by CertInstaller
void installCaCertificate(in byte[] caCertificate);
// APIs used by DevicePolicyManager
- boolean installKeyPair(in byte[] privateKey, in byte[] userCert, String alias);
+ boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias);
boolean removeKeyPair(String alias);
// APIs used by Settings
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 0886487..cce58c2 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -42,6 +42,8 @@
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.BlockingQueue;
@@ -389,7 +391,12 @@
/**
* Returns the {@code X509Certificate} chain for the requested
- * alias, or null if no there is no result.
+ * alias, or null if there is no result.
+ * <p>
+ * <strong>Note:</strong> If a certificate chain was explicitly specified when the alias was
+ * installed, this method will return that chain. If only the client certificate was specified
+ * at the installation time, this method will try to build a certificate chain using all
+ * available trust anchors (preinstalled and user-added).
*
* <p> This method may block while waiting for a connection to another process, and must never
* be called from the main thread.
@@ -413,11 +420,31 @@
if (certificateBytes == null) {
return null;
}
-
- TrustedCertificateStore store = new TrustedCertificateStore();
- List<X509Certificate> chain = store
- .getCertificateChain(toCertificate(certificateBytes));
- return chain.toArray(new X509Certificate[chain.size()]);
+ X509Certificate leafCert = toCertificate(certificateBytes);
+ final byte[] certChainBytes = keyChainService.getCaCertificates(alias);
+ // If the keypair is installed with a certificate chain by either
+ // DevicePolicyManager.installKeyPair or CertInstaller, return that chain.
+ if (certChainBytes != null && certChainBytes.length != 0) {
+ Collection<X509Certificate> chain = toCertificates(certChainBytes);
+ ArrayList<X509Certificate> fullChain = new ArrayList<>(chain.size() + 1);
+ fullChain.add(leafCert);
+ fullChain.addAll(chain);
+ return fullChain.toArray(new X509Certificate[fullChain.size()]);
+ } else {
+ // If there isn't a certificate chain, either due to a pre-existing keypair
+ // installed before N, or no chain is explicitly installed under the new logic,
+ // fall back to old behavior of constructing the chain from trusted credentials.
+ //
+ // This logic exists to maintain old behaviour for already installed keypair, at
+ // the cost of potentially returning extra certificate chain for new clients who
+ // explicitly installed only the client certificate without a chain. The latter
+ // case is actually no different from pre-N behaviour of getCertificateChain(),
+ // in that sense this change introduces no regression. Besides the returned chain
+ // is still valid so the consumer of the chain should have no problem verifying it.
+ TrustedCertificateStore store = new TrustedCertificateStore();
+ List<X509Certificate> chain = store.getCertificateChain(leafCert);
+ return chain.toArray(new X509Certificate[chain.size()]);
+ }
} catch (CertificateException e) {
throw new KeyChainException(e);
} catch (RemoteException e) {
@@ -486,6 +513,21 @@
}
}
+ /** @hide */
+ @NonNull
+ public static Collection<X509Certificate> toCertificates(@NonNull byte[] bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("bytes == null");
+ }
+ try {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ return (Collection<X509Certificate>) certFactory.generateCertificates(
+ new ByteArrayInputStream(bytes));
+ } catch (CertificateException e) {
+ throw new AssertionError(e);
+ }
+ }
+
/**
* @hide for reuse by CertInstaller and Settings.
* @see KeyChain#bind
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 52fe973..1ccc59a 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -3935,7 +3935,9 @@
if (Res_GETPACKAGE(resID)+1 == 0) {
ALOGW("No package identifier when getting name for resource number 0x%08x", resID);
} else {
+#ifndef STATIC_ANDROIDFW_FOR_TOOLS
ALOGW("No known package when getting name for resource number 0x%08x", resID);
+#endif
}
return false;
}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 01f9cde..1f17851 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -322,6 +322,7 @@
$(hwui_test_common_src_files) \
tests/microbench/main.cpp \
tests/microbench/DisplayListCanvasBench.cpp \
+ tests/microbench/FontBench.cpp \
tests/microbench/LinearAllocatorBench.cpp \
tests/microbench/PathParserBench.cpp \
tests/microbench/ShadowBench.cpp \
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
index 85903654..b70d586 100644
--- a/libs/hwui/BakedOpState.cpp
+++ b/libs/hwui/BakedOpState.cpp
@@ -108,5 +108,63 @@
clippedBounds.doIntersect(clipRect->rect);
}
+BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+ BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
+ allocator, snapshot, recordedOp, false);
+ if (bakedState->computedState.clippedBounds.isEmpty()) {
+ // bounds are empty, so op is rejected
+ allocator.rewindIfLastAlloc(bakedState);
+ return nullptr;
+ }
+ return bakedState;
+}
+
+BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+ return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
+}
+
+BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+ bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
+ ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
+ : true;
+
+ BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
+ allocator, snapshot, recordedOp, expandForStroke);
+ if (bakedState->computedState.clippedBounds.isEmpty()) {
+ // bounds are empty, so op is rejected
+ // NOTE: this won't succeed if a clip was allocated
+ allocator.rewindIfLastAlloc(bakedState);
+ return nullptr;
+ }
+ return bakedState;
+}
+
+BakedOpState* BakedOpState::tryShadowOpConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+
+ // clip isn't empty, so construct the op
+ return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
+}
+
+BakedOpState* BakedOpState::directConstruct(LinearAllocator& allocator,
+ const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
+ return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
+}
+
+void BakedOpState::setupOpacity(const SkPaint* paint) {
+ computedState.opaqueOverClippedBounds = computedState.transform.isSimple()
+ && computedState.clipState->mode == ClipMode::Rectangle
+ && MathUtils::areEqual(alpha, 1.0f)
+ && !roundRectClipState
+ && PaintUtils::isOpaquePaint(paint);
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 4e3cb8a..e1441fc 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -93,6 +93,7 @@
Rect clippedBounds;
int clipSideFlags = 0;
const SkPath* localProjectionPathMask = nullptr;
+ bool opaqueOverClippedBounds = false;
};
/**
@@ -103,23 +104,10 @@
class BakedOpState {
public:
static BakedOpState* tryConstruct(LinearAllocator& allocator,
- Snapshot& snapshot, const RecordedOp& recordedOp) {
- if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
- BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
- allocator, snapshot, recordedOp, false);
- if (bakedState->computedState.clippedBounds.isEmpty()) {
- // bounds are empty, so op is rejected
- allocator.rewindIfLastAlloc(bakedState);
- return nullptr;
- }
- return bakedState;
- }
+ Snapshot& snapshot, const RecordedOp& recordedOp);
static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator,
- Snapshot& snapshot, const RecordedOp& recordedOp) {
- if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
- return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
- }
+ Snapshot& snapshot, const RecordedOp& recordedOp);
enum class StrokeBehavior {
// stroking is forced, regardless of style on paint (such as for lines)
@@ -129,35 +117,16 @@
};
static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
- Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
- if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
- bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
- ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
- : true;
-
- BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
- allocator, snapshot, recordedOp, expandForStroke);
- if (bakedState->computedState.clippedBounds.isEmpty()) {
- // bounds are empty, so op is rejected
- // NOTE: this won't succeed if a clip was allocated
- allocator.rewindIfLastAlloc(bakedState);
- return nullptr;
- }
- return bakedState;
- }
+ Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior);
static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
- Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
- if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-
- // clip isn't empty, so construct the op
- return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
- }
+ Snapshot& snapshot, const ShadowOp* shadowOpPtr);
static BakedOpState* directConstruct(LinearAllocator& allocator,
- const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
- return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
- }
+ const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp);
+
+ // Set opaqueOverClippedBounds. If this method isn't called, the op is assumed translucent.
+ void setupOpacity(const SkPaint* paint);
// computed state:
ResolvedRenderState computedState;
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index b1314fe..9c46c00 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -37,7 +37,8 @@
const LightGeometry& lightGeometry, const Rect &contentDrawBounds, Caches& caches)
: mCanvasState(*this)
, mCaches(caches)
- , mLightRadius(lightGeometry.radius) {
+ , mLightRadius(lightGeometry.radius)
+ , mDrawFbo0(!nodes.empty()) {
ATRACE_NAME("prepare drawing commands");
mLayerBuilders.reserve(layers.entries().size());
@@ -481,12 +482,17 @@
* Defers an unmergeable, strokeable op, accounting correctly
* for paint's style on the bounds being computed.
*/
-const BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
BakedOpState::StrokeBehavior strokeBehavior) {
// Note: here we account for stroke when baking the op
BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
if (!bakedState) return nullptr; // quick rejected
+
+ if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) {
+ bakedState->setupOpacity(op.paint);
+ }
+
currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
return bakedState;
}
@@ -516,6 +522,7 @@
void FrameBuilder::deferBitmapOp(const BitmapOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
+ bakedState->setupOpacity(op.paint);
// Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation
// Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
index 0b7a606..e418227 100644
--- a/libs/hwui/FrameBuilder.h
+++ b/libs/hwui/FrameBuilder.h
@@ -137,12 +137,14 @@
}
GL_CHECKPOINT(MODERATE);
- const LayerBuilder& fbo0 = *(mLayerBuilders[0]);
- renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
- GL_CHECKPOINT(MODERATE);
- fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
- GL_CHECKPOINT(MODERATE);
- renderer.endFrame(fbo0.repaintRect);
+ if (CC_LIKELY(mDrawFbo0)) {
+ const LayerBuilder& fbo0 = *(mLayerBuilders[0]);
+ renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
+ GL_CHECKPOINT(MODERATE);
+ fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
+ GL_CHECKPOINT(MODERATE);
+ renderer.endFrame(fbo0.repaintRect);
+ }
}
void dump() const {
@@ -201,7 +203,7 @@
return mAllocator.create<SkPath>();
}
- const BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+ BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined);
/**
@@ -239,6 +241,8 @@
// contains single-frame objects, such as BakedOpStates, LayerBuilders, Batches
LinearAllocator mAllocator;
+
+ const bool mDrawFbo0;
};
}; // namespace uirenderer
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 76e587e..47d0108 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -15,6 +15,8 @@
*/
#include "JankTracker.h"
+#include "Properties.h"
+
#include <algorithm>
#include <cutils/ashmem.h>
#include <cutils/log.h>
@@ -77,6 +79,11 @@
// If a frame is > this, start counting in increments of 4ms
static const uint32_t kBucket4msIntervals = 48;
+// For testing purposes to try and eliminate test infra overhead we will
+// consider any unknown delay of frame start as part of the test infrastructure
+// and filter it out of the frame profile data
+static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
+
// This will be called every frame, performance sensitive
// Uses bit twiddling to avoid branching while achieving the packing desired
static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) {
@@ -242,7 +249,7 @@
mData->totalFrameCount++;
// Fast-path for jank-free frames
int64_t totalDuration =
- frame[FrameInfoIndex::FrameCompleted] - frame[FrameInfoIndex::IntendedVsync];
+ frame[FrameInfoIndex::FrameCompleted] - frame[sFrameStart];
uint32_t framebucket = frameCountIndexForFrameTime(
totalDuration, mData->frameCounts.size() - 1);
// Keep the fast path as fast as possible.
@@ -280,6 +287,9 @@
}
void JankTracker::dumpData(const ProfileData* data, int fd) {
+ if (sFrameStart != FrameInfoIndex::IntendedVsync) {
+ dprintf(fd, "\nNote: Data has been filtered!");
+ }
dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime);
dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount);
dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount,
@@ -305,6 +315,9 @@
mData->totalFrameCount = 0;
mData->jankFrameCount = 0;
mData->statStartTime = systemTime(CLOCK_MONOTONIC);
+ sFrameStart = Properties::filterOutTestOverhead
+ ? FrameInfoIndex::HandleInputStart
+ : FrameInfoIndex::IntendedVsync;
}
uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp
index e6a95ff..eea11bf 100644
--- a/libs/hwui/LayerBuilder.cpp
+++ b/libs/hwui/LayerBuilder.cpp
@@ -236,6 +236,21 @@
mClearRects.push_back(rect);
}
+void LayerBuilder::onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState) {
+ if (bakedState->op->opId != RecordedOpId::CopyToLayerOp) {
+ // First non-CopyToLayer, so stop stashing up layer clears for unclipped save layers,
+ // and issue them together in one draw.
+ flushLayerClears(allocator);
+
+ if (CC_UNLIKELY(activeUnclippedSaveLayers.empty()
+ && bakedState->computedState.opaqueOverClippedBounds
+ && bakedState->computedState.clippedBounds.contains(repaintRect))) {
+ // discard all deferred drawing ops, since new one will occlude them
+ clear();
+ }
+ }
+}
+
void LayerBuilder::flushLayerClears(LinearAllocator& allocator) {
if (CC_UNLIKELY(!mClearRects.empty())) {
const int vertCount = mClearRects.size() * 4;
@@ -270,11 +285,7 @@
void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator,
BakedOpState* op, batchid_t batchId) {
- if (batchId != OpBatchType::CopyToLayer) {
- // if first op after one or more unclipped saveLayers, flush the layer clears
- flushLayerClears(allocator);
- }
-
+ onDeferOp(allocator, op);
OpBatch* targetBatch = mBatchLookup[batchId];
size_t insertBatchIndex = mBatches.size();
@@ -295,10 +306,7 @@
void LayerBuilder::deferMergeableOp(LinearAllocator& allocator,
BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
- if (batchId != OpBatchType::CopyToLayer) {
- // if first op after one or more unclipped saveLayers, flush the layer clears
- flushLayerClears(allocator);
- }
+ onDeferOp(allocator, op);
MergingOpBatch* targetBatch = nullptr;
// Try to merge with any existing batch with same mergeId
@@ -348,6 +356,14 @@
}
}
+void LayerBuilder::clear() {
+ mBatches.clear();
+ for (int i = 0; i < OpBatchType::Count; i++) {
+ mBatchLookup[i] = nullptr;
+ mMergingBatchLookup[i].clear();
+ }
+}
+
void LayerBuilder::dump() const {
ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)",
this, width, height, offscreenBuffer, beginLayerOp,
diff --git a/libs/hwui/LayerBuilder.h b/libs/hwui/LayerBuilder.h
index 4a7ca2d..4de432c 100644
--- a/libs/hwui/LayerBuilder.h
+++ b/libs/hwui/LayerBuilder.h
@@ -100,9 +100,7 @@
return mBatches.empty();
}
- void clear() {
- mBatches.clear();
- }
+ void clear();
void dump() const;
@@ -117,6 +115,7 @@
// list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps
std::vector<BakedOpState*> activeUnclippedSaveLayers;
private:
+ void onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState);
void flushLayerClears(LinearAllocator& allocator);
std::vector<BatchBase*> mBatches;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index bbd8c72..6f68c2b 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -66,6 +66,8 @@
bool Properties::waitForGpuCompletion = false;
+bool Properties::filterOutTestOverhead = false;
+
static int property_get_int(const char* key, int defaultValue) {
char buf[PROPERTY_VALUE_MAX] = {'\0',};
@@ -156,6 +158,8 @@
textureCacheFlushRate = std::max(0.0f, std::min(1.0f,
property_get_float(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, DEFAULT_TEXTURE_CACHE_FLUSH_RATE)));
+ filterOutTestOverhead = property_get_bool(PROPERTY_FILTER_TEST_OVERHEAD, false);
+
return (prevDebugLayersUpdates != debugLayersUpdates)
|| (prevDebugOverdraw != debugOverdraw)
|| (prevDebugStencilClip != debugStencilClip);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 249b5b0..171873d 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -151,6 +151,8 @@
*/
#define PROPERTY_ENABLE_PARTIAL_UPDATES "debug.hwui.enable_partial_updates"
+#define PROPERTY_FILTER_TEST_OVERHEAD "debug.hwui.filter_test_overhead"
+
///////////////////////////////////////////////////////////////////////////////
// Runtime configuration properties
///////////////////////////////////////////////////////////////////////////////
@@ -294,6 +296,10 @@
// Should be used only by test apps
static bool waitForGpuCompletion;
+ // Should only be set by automated tests to try and filter out
+ // any overhead they add
+ static bool filterOutTestOverhead;
+
private:
static ProfileType sProfileType;
static bool sDisableProfileBars;
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index 9a825fd..8e04c87 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -356,8 +356,6 @@
}
void Font::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs) {
- ATRACE_NAME("Precache Glyphs");
-
if (numGlyphs == 0 || glyphs == nullptr) {
return;
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index eee5278..6933b2f 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -86,10 +86,12 @@
freePrefetechedLayers();
destroyHardwareResources();
mAnimationContext->destroy();
+#if !HWUI_NEW_OPS
if (mCanvas) {
delete mCanvas;
mCanvas = nullptr;
}
+#endif
}
void CanvasContext::setSurface(Surface* surface) {
@@ -587,9 +589,11 @@
void CanvasContext::buildLayer(RenderNode* node) {
ATRACE_CALL();
- if (!mEglManager.hasEglContext() || !mCanvas) {
- return;
- }
+ if (!mEglManager.hasEglContext()) return;
+#if !HWUI_NEW_OPS
+ if (!mCanvas) return;
+#endif
+
// buildLayer() will leave the tree in an unknown state, so we must stop drawing
stopDrawing();
@@ -609,7 +613,15 @@
node->setPropertyFieldsDirty(RenderNode::GENERIC);
#if HWUI_NEW_OPS
- // TODO: support buildLayer
+ static const std::vector< sp<RenderNode> > emptyNodeList;
+ auto& caches = Caches::getInstance();
+ FrameBuilder frameBuilder(mLayerUpdateQueue, SkRect::MakeWH(1, 1), 1, 1,
+ emptyNodeList, mLightGeometry, mContentDrawBounds, caches);
+ mLayerUpdateQueue.clear();
+ BakedOpRenderer renderer(caches, mRenderThread.renderState(),
+ mOpaque, mLightInfo);
+ LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case");
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
#else
mCanvas->markLayersAsBuildLayers();
mCanvas->flushLayerUpdates();
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 6706c30..6d0889e 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -196,10 +196,11 @@
RingBuffer<SwapHistory, 3> mSwapHistory;
bool mOpaque;
- OpenGLRenderer* mCanvas = nullptr;
#if HWUI_NEW_OPS
BakedOpRenderer::LightInfo mLightInfo;
FrameBuilder::LightGeometry mLightGeometry = { {0, 0, 0}, 0 };
+#else
+ OpenGLRenderer* mCanvas = nullptr;
#endif
bool mHaveNewSurface = false;
diff --git a/libs/hwui/tests/microbench/FontBench.cpp b/libs/hwui/tests/microbench/FontBench.cpp
new file mode 100644
index 0000000..df3d041
--- /dev/null
+++ b/libs/hwui/tests/microbench/FontBench.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <benchmark/benchmark.h>
+
+#include "GammaFontRenderer.h"
+#include "tests/common/TestUtils.h"
+
+#include <SkPaint.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+void BM_FontRenderer_precache_cachehits(benchmark::State& state) {
+ TestUtils::runOnRenderThread([&state](renderthread::RenderThread& thread) {
+ SkPaint paint;
+ paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ GammaFontRenderer gammaFontRenderer;
+ FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
+ fontRenderer.setFont(&paint, SkMatrix::I());
+
+ std::vector<glyph_t> glyphs;
+ std::vector<float> positions;
+ float totalAdvance;
+ uirenderer::Rect bounds;
+ TestUtils::layoutTextUnscaled(paint, "This is a test",
+ &glyphs, &positions, &totalAdvance, &bounds);
+
+ fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I());
+
+ while (state.KeepRunning()) {
+ fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I());
+ }
+ });
+}
+BENCHMARK(BM_FontRenderer_precache_cachehits);
diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
index 9daf633..0aef620 100644
--- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp
+++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
@@ -113,15 +113,17 @@
};
void BM_FrameBuilder_defer_scene(benchmark::State& state) {
- const char* sceneName = *(SCENES.begin() + state.range_x());
- state.SetLabel(sceneName);
- auto nodes = getSyncedSceneNodes(sceneName);
- while (state.KeepRunning()) {
- FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
- SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h,
- nodes, sLightGeometry, Caches::getInstance());
- benchmark::DoNotOptimize(&frameBuilder);
- }
+ TestUtils::runOnRenderThread([&state](RenderThread& thread) {
+ const char* sceneName = *(SCENES.begin() + state.range_x());
+ state.SetLabel(sceneName);
+ auto nodes = getSyncedSceneNodes(sceneName);
+ while (state.KeepRunning()) {
+ FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
+ SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h,
+ nodes, sLightGeometry, Caches::getInstance());
+ benchmark::DoNotOptimize(&frameBuilder);
+ }
+ });
}
BENCHMARK(BM_FrameBuilder_defer_scene)->DenseRange(0, SCENES.size() - 1);
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index e97aaa6..7dfafb9 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -30,6 +30,7 @@
namespace uirenderer {
const LayerUpdateQueue sEmptyLayerUpdateQueue;
+const std::vector< sp<RenderNode> > sEmptyNodeList;
const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50};
@@ -216,6 +217,123 @@
<< "Expect number of ops = 2 * loop count";
}
+RENDERTHREAD_TEST(FrameBuilder, empty_noFbo0) {
+ class EmptyNoFbo0TestRenderer : public TestRendererBase {
+ public:
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ ADD_FAILURE() << "Primary frame draw not expected in this test";
+ }
+ void endFrame(const Rect& repaintRect) override {
+ ADD_FAILURE() << "Primary frame draw not expected in this test";
+ }
+ };
+
+ // Pass empty node list, so no work is enqueued for Fbo0
+ FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ sEmptyNodeList, sLightGeometry, Caches::getInstance());
+ EmptyNoFbo0TestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+}
+
+RENDERTHREAD_TEST(FrameBuilder, empty_withFbo0) {
+ class EmptyWithFbo0TestRenderer : public TestRendererBase {
+ public:
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ EXPECT_EQ(0, mIndex++);
+ }
+ void endFrame(const Rect& repaintRect) override {
+ EXPECT_EQ(1, mIndex++);
+ }
+ };
+ auto node = TestUtils::createNode(10, 10, 110, 110,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ // no drawn content
+ });
+ auto syncedNodeList = TestUtils::createSyncedNodeList(node);
+
+ // Draw, but pass empty node list, so no work is done for primary frame
+ FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ syncedNodeList, sLightGeometry, Caches::getInstance());
+ EmptyWithFbo0TestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex()) << "No drawing content produced,"
+ " but fbo0 update lifecycle should still be observed";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_rects) {
+ class AvoidOverdrawRectsTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(mIndex++, 0) << "Should be one rect";
+ EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds)
+ << "Last rect should occlude others.";
+ }
+ };
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.drawRect(10, 10, 190, 190, SkPaint());
+ });
+
+ // Damage (and therefore clip) is same as last draw, subset of renderable area.
+ // This means last op occludes other contents, and they'll be rejected to avoid overdraw.
+ SkRect damageRect = SkRect::MakeLTRB(10, 10, 190, 190);
+ FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, damageRect, 200, 200,
+ TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+ EXPECT_EQ(3u, node->getDisplayList()->getOps().size())
+ << "Recording must not have rejected ops, in order for this test to be valid";
+
+ AvoidOverdrawRectsTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_bitmaps) {
+ static SkBitmap opaqueBitmap = TestUtils::createSkBitmap(50, 50,
+ SkColorType::kRGB_565_SkColorType);
+ static SkBitmap transpBitmap = TestUtils::createSkBitmap(50, 50,
+ SkColorType::kAlpha_8_SkColorType);
+ class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase {
+ public:
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_LT(mIndex++, 2) << "Should be two bitmaps";
+ switch(mIndex++) {
+ case 0:
+ EXPECT_EQ(opaqueBitmap.pixelRef(), op.bitmap->pixelRef());
+ break;
+ case 1:
+ EXPECT_EQ(transpBitmap.pixelRef(), op.bitmap->pixelRef());
+ break;
+ default:
+ ADD_FAILURE() << "Only two ops expected.";
+ }
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 50, 50,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 50, 50, SkPaint());
+ canvas.drawRect(0, 0, 50, 50, SkPaint());
+ canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+
+ // only the below draws should remain, since they're
+ canvas.drawBitmap(opaqueBitmap, 0, 0, nullptr);
+ canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+ });
+
+ FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(50, 50), 50, 50,
+ TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+ EXPECT_EQ(5u, node->getDisplayList()->getOps().size())
+ << "Recording must not have rejected ops, in order for this test to be valid";
+
+ AvoidOverdrawBitmapsTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly one op";
+}
+
RENDERTHREAD_TEST(FrameBuilder, clippedMerging) {
class ClippedMergingTestRenderer : public TestRendererBase {
public:
@@ -1078,6 +1196,64 @@
*(parent->getLayerHandle()) = nullptr;
}
+
+RENDERTHREAD_TEST(FrameBuilder, buildLayer) {
+ class BuildLayerTestRenderer : public TestRendererBase {
+ public:
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+ EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+ EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
+ }
+ void onColorOp(const ColorOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+
+ EXPECT_TRUE(state.computedState.transform.isIdentity())
+ << "Transform should be reset within layer";
+
+ EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect())
+ << "Damage rect should be used to clip layer content";
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ ADD_FAILURE() << "Primary frame draw not expected in this test";
+ }
+ void endFrame(const Rect& repaintRect) override {
+ ADD_FAILURE() << "Primary frame draw not expected in this test";
+ }
+ };
+
+ auto node = TestUtils::createNode(10, 10, 110, 110,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
+ canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
+ });
+ OffscreenBuffer** layerHandle = node->getLayerHandle();
+
+ // create RenderNode's layer here in same way prepareTree would
+ OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+ *layerHandle = &layer;
+
+ auto syncedNodeList = TestUtils::createSyncedNodeList(node);
+
+ // only enqueue partial damage
+ LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+ layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
+
+ // Draw, but pass empty node list, so no work is done for primary frame
+ FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(1, 1), 1, 1,
+ sEmptyNodeList, sLightGeometry, Caches::getInstance());
+ BuildLayerTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex());
+
+ // clean up layer pointer, so we can safely destruct RenderNode
+ *layerHandle = nullptr;
+}
+
static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) {
SkPaint paint;
paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index db53713..4faab9a 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -67,6 +67,21 @@
&& getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode;
}
+ static bool isOpaquePaint(const SkPaint* paint) {
+ if (!paint) return true; // default (paintless) behavior is SrcOver, black
+
+ if (paint->getAlpha() != 0xFF
+ || PaintUtils::isBlendedShader(paint->getShader())
+ || PaintUtils::isBlendedColorFilter(paint->getColorFilter())) {
+ return false;
+ }
+
+ // Only let simple srcOver / src blending modes declare opaque, since behavior is clear.
+ SkXfermode::Mode mode = getXfermode(paint->getXfermode());
+ return mode == SkXfermode::Mode::kSrcOver_Mode
+ || mode == SkXfermode::Mode::kSrc_Mode;
+ }
+
static bool isBlendedShader(const SkShader* shader) {
if (shader == nullptr) {
return false;
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 9c509d6..d76feec 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -121,7 +121,7 @@
* @param satIndex the index of the satellite in the list.
*/
public float getElevationDegrees(int satIndex) {
- return 0f;
+ return mElevations[satIndex];
}
/**
diff --git a/location/java/android/location/INetInitiatedListener.aidl b/location/java/android/location/INetInitiatedListener.aidl
index f2f5a32..fc64dd6 100644
--- a/location/java/android/location/INetInitiatedListener.aidl
+++ b/location/java/android/location/INetInitiatedListener.aidl
@@ -1,26 +1,26 @@
-/*
-**
-** Copyright 2008, 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.
-*/
-
-package android.location;
-
-/**
- * {@hide}
- */
-interface INetInitiatedListener
-{
- boolean sendNiResponse(int notifId, int userResponse);
-}
+/*
+**
+** Copyright 2008, 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.
+*/
+
+package android.location;
+
+/**
+ * {@hide}
+ */
+interface INetInitiatedListener
+{
+ boolean sendNiResponse(int notifId, int userResponse);
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3c42161..0a8c692 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2149,7 +2149,7 @@
}
if (listener != null) {
Log.d(TAG, "AudioManager dispatching onAudioFocusChange("
- + msg.what + ") for " + msg.obj);
+ + msg.arg1 + ") for " + msg.obj);
listener.onAudioFocusChange(msg.arg1);
}
break;
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 4bf0852..cd2d51d 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -693,7 +693,7 @@
*/
public ExifInterface(FileDescriptor fileDescriptor) throws IOException {
if (fileDescriptor == null) {
- throw new IllegalArgumentException("parcelFileDescriptor cannot be null");
+ throw new IllegalArgumentException("fileDescriptor cannot be null");
}
mAssetInputStream = null;
mFilename = null;
@@ -705,7 +705,7 @@
try {
fileDescriptor = Os.dup(fileDescriptor);
} catch (ErrnoException e) {
- e.rethrowAsIOException();
+ throw e.rethrowAsIOException();
}
} else {
mSeekableFileDescriptor = null;
@@ -811,6 +811,9 @@
*/
public void setAttribute(String tag, String value) {
for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
+ if (i == IFD_THUMBNAIL_HINT && !mHasThumbnail) {
+ continue;
+ }
if (sExifTagMapsForWriting[i].containsKey(tag)) {
mAttributes[i].put(tag, value);
}
@@ -818,6 +821,35 @@
}
/**
+ * Update the values of the tags in the tag groups if any value for the tag already was stored.
+ *
+ * @param tag the name of the tag.
+ * @param value the value of the tag.
+ * @return Returns {@code true} if updating is placed.
+ */
+ private boolean updateAttribute(String tag, String value) {
+ boolean updated = false;
+ for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
+ if (mAttributes[i].containsKey(tag)) {
+ mAttributes[i].put(tag, value);
+ updated = true;
+ }
+ }
+ return updated;
+ }
+
+ /**
+ * Remove any values of the specified tag.
+ *
+ * @param tag the name of the tag.
+ */
+ private void removeAttribute(String tag) {
+ for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
+ mAttributes[i].remove(tag);
+ }
+ }
+
+ /**
* This function decides which parser to read the image data according to the given input stream
* type and the content of the input stream. In each case, it reads the first three bytes to
* determine whether the image data format is JPEG or not.
@@ -853,7 +885,7 @@
} catch (IOException e) {
// Ignore exceptions in order to keep the compatibility with the old versions of
// ExifInterface.
- Log.w(TAG, "Invalid JPEG: ExifInterface got an unsupported image format file"
+ Log.w(TAG, "Invalid image: ExifInterface got an unsupported image format file"
+ "(ExifInterface supports JPEG and some RAW image formats only) "
+ "or a corrupted JPEG file to ExifInterface.", e);
} finally {
@@ -882,27 +914,22 @@
// Mark for disabling the save feature.
mIsRaw = true;
- for (Map.Entry entry : (Set<Map.Entry>) map.entrySet()) {
- String attrName = (String) entry.getKey();
-
- switch (attrName) {
- case TAG_HAS_THUMBNAIL:
- mHasThumbnail = ((String) entry.getValue()).equalsIgnoreCase("true");
- break;
- case TAG_THUMBNAIL_OFFSET:
- mThumbnailOffset = Integer.parseInt((String) entry.getValue());
- break;
- case TAG_THUMBNAIL_LENGTH:
- mThumbnailLength = Integer.parseInt((String) entry.getValue());
- break;
- case TAG_THUMBNAIL_DATA:
- mThumbnailBytes = (byte[]) entry.getValue();
- break;
- default:
- setAttribute(attrName, (String) entry.getValue());
- break;
- }
+ String value = (String) map.remove(TAG_HAS_THUMBNAIL);
+ mHasThumbnail = value != null && value.equalsIgnoreCase("true");
+ value = (String) map.remove(TAG_THUMBNAIL_OFFSET);
+ if (value != null) {
+ mThumbnailOffset = Integer.parseInt(value);
}
+ value = (String) map.remove(TAG_THUMBNAIL_LENGTH);
+ if (value != null) {
+ mThumbnailLength = Integer.parseInt(value);
+ }
+ mThumbnailBytes = (byte[]) map.remove(TAG_THUMBNAIL_DATA);
+
+ for (Map.Entry entry : (Set<Map.Entry>) map.entrySet()) {
+ setAttribute((String) entry.getKey(), (String) entry.getValue());
+ }
+
return true;
}
@@ -928,7 +955,7 @@
/**
* Save the tag data into the original image file. This is expensive because it involves
* copying all the data from one file to another and deleting the old file and renaming the
- * other. It's best to use{@link #setAttribute(String,String)} to set all attributes to write
+ * other. It's best to use {@link #setAttribute(String,String)} to set all attributes to write
* and make a single call rather than multiple calls for each attribute.
*/
public void saveAttributes() throws IOException {
@@ -963,7 +990,7 @@
Streams.copy(in, out);
}
} catch (ErrnoException e) {
- e.rethrowAsIOException();
+ throw e.rethrowAsIOException();
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
@@ -982,7 +1009,7 @@
}
saveJpegAttributes(in, out);
} catch (ErrnoException e) {
- e.rethrowAsIOException();
+ throw e.rethrowAsIOException();
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
@@ -1276,7 +1303,8 @@
throw new IOException("Invalid exif");
}
length = 0;
- setAttribute("UserComment", new String(bytes, Charset.forName("US-ASCII")));
+ mAttributes[IFD_EXIF_HINT].put(TAG_USER_COMMENT,
+ new String(bytes, Charset.forName("US-ASCII")));
break;
}
@@ -1296,9 +1324,10 @@
if (dataInputStream.skipBytes(1) != 1) {
throw new IOException("Invalid SOFx");
}
- setAttribute("ImageLength",
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH,
String.valueOf(dataInputStream.readUnsignedShort()));
- setAttribute("ImageWidth", String.valueOf(dataInputStream.readUnsignedShort()));
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH,
+ String.valueOf(dataInputStream.readUnsignedShort()));
length -= 5;
break;
}
@@ -1521,31 +1550,31 @@
convertToInt(TAG_GPS_ALTITUDE_REF);
convertToRational(TAG_GPS_LONGITUDE);
convertToRational(TAG_GPS_LATITUDE);
- convertToTimetamp(TAG_GPS_TIMESTAMP);
+ convertToTimestamp(TAG_GPS_TIMESTAMP);
// The value of DATETIME tag has the same value of DATETIME_ORIGINAL tag.
- String valueOfDateTimeOriginal = getAttribute("DateTimeOriginal");
+ String valueOfDateTimeOriginal = getAttribute(TAG_DATETIME_ORIGINAL);
if (valueOfDateTimeOriginal != null) {
- setAttribute(TAG_DATETIME, valueOfDateTimeOriginal);
+ mAttributes[IFD_TIFF_HINT].put(TAG_DATETIME, valueOfDateTimeOriginal);
}
// Add the default value.
if (getAttribute(TAG_IMAGE_WIDTH) == null) {
- setAttribute(TAG_IMAGE_WIDTH, "0");
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, "0");
}
if (getAttribute(TAG_IMAGE_LENGTH) == null) {
- setAttribute(TAG_IMAGE_LENGTH, "0");
+ mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, "0");
}
if (getAttribute(TAG_ORIENTATION) == null) {
- setAttribute(TAG_ORIENTATION, "0");
+ mAttributes[IFD_TIFF_HINT].put(TAG_ORIENTATION, "0");
}
if (getAttribute(TAG_LIGHT_SOURCE) == null) {
- setAttribute(TAG_LIGHT_SOURCE, "0");
+ mAttributes[IFD_EXIF_HINT].put(TAG_LIGHT_SOURCE, "0");
}
}
// Converts the tag value to timestamp; Otherwise deletes the given tag.
- private void convertToTimetamp(String tagName) {
+ private void convertToTimestamp(String tagName) {
String entryValue = getAttribute(tagName);
if (entryValue == null) return;
int dataFormat = getDataFormatOfExifEntryValue(entryValue);
@@ -1566,9 +1595,9 @@
int value = numerator / denominator;
stringBuilder.append(String.format("%02d", value));
}
- setAttribute(tagName, stringBuilder.toString());
+ updateAttribute(tagName, stringBuilder.toString());
} else if (dataFormat != IFD_FORMAT_STRING) {
- setAttribute(tagName, null);
+ removeAttribute(tagName);
}
}
@@ -1595,14 +1624,14 @@
}
stringBuilder.append((double) numerator / denominator);
}
- setAttribute(tagName, stringBuilder.toString());
+ updateAttribute(tagName, stringBuilder.toString());
break;
}
case IFD_FORMAT_DOUBLE:
// Keep it as is.
break;
default:
- setAttribute(tagName, null);
+ removeAttribute(tagName);
break;
}
}
@@ -1624,14 +1653,14 @@
double doubleValue = Double.parseDouble(component);
stringBuilder.append((int) (doubleValue * 10000.0)).append("/").append(10000);
}
- setAttribute(tagName, stringBuilder.toString());
+ updateAttribute(tagName, stringBuilder.toString());
break;
}
case IFD_FORMAT_SRATIONAL:
// Keep it as is.
break;
default:
- setAttribute(tagName, null);
+ removeAttribute(tagName);
break;
}
}
@@ -1642,7 +1671,7 @@
if (entryValue == null) return;
int dataFormat = getDataFormatOfExifEntryValue(entryValue);
if (dataFormat != IFD_FORMAT_SLONG) {
- setAttribute(tagName, null);
+ removeAttribute(tagName);
}
}
@@ -1758,7 +1787,7 @@
String entryValue = readExifEntryValue(
dataInputStream, dataFormat, numberOfComponents);
if (entryValue != null) {
- setAttribute(tagName, entryValue);
+ mAttributes[hint].put(tagName, entryValue);
}
} else {
StringBuilder entryValueBuilder = new StringBuilder();
@@ -1769,7 +1798,7 @@
entryValueBuilder.append(readExifEntryValue(
dataInputStream, dataFormat, numberOfComponents));
}
- setAttribute(tagName, entryValueBuilder.toString());
+ mAttributes[hint].put(tagName, entryValueBuilder.toString());
}
if (dataInputStream.peek() != nextEntryOffset) {
@@ -1886,11 +1915,11 @@
// Remove IFD pointer tags (we'll re-add it later.)
for (ExifTag tag : IFD_POINTER_TAGS) {
- setAttribute(tag.name, null);
+ removeAttribute(tag.name);
}
// Remove old thumbnail data
- setAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name, null);
- setAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name, null);
+ removeAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name);
+ removeAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name);
// Remove null value tags.
for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index b63df6f..d2dc440 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -554,6 +554,10 @@
if (bufidx >= 0) {
size_t bufsize;
uint8_t *buf = AMediaCodec_getInputBuffer(codec, bufidx, &bufsize);
+ if (buf == nullptr) {
+ ALOGE("AMediaCodec_getInputBuffer returned nullptr, short decode");
+ break;
+ }
int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
ALOGV("read %d", sampleSize);
if (sampleSize < 0) {
@@ -563,10 +567,16 @@
}
int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
- AMediaCodec_queueInputBuffer(codec, bufidx,
+ media_status_t mstatus = AMediaCodec_queueInputBuffer(codec, bufidx,
0 /* offset */, sampleSize, presentationTimeUs,
sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
- AMediaExtractor_advance(ex);
+ if (mstatus != AMEDIA_OK) {
+ // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+ ALOGE("AMediaCodec_queueInputBuffer returned status %d, short decode",
+ (int)mstatus);
+ break;
+ }
+ (void)AMediaExtractor_advance(ex);
}
}
@@ -581,6 +591,10 @@
ALOGV("got decoded buffer size %d", info.size);
uint8_t *buf = AMediaCodec_getOutputBuffer(codec, status, NULL /* out_size */);
+ if (buf == nullptr) {
+ ALOGE("AMediaCodec_getOutputBuffer returned nullptr, short decode");
+ break;
+ }
size_t dataSize = info.size;
if (dataSize > available) {
dataSize = available;
@@ -589,7 +603,14 @@
writePos += dataSize;
written += dataSize;
available -= dataSize;
- AMediaCodec_releaseOutputBuffer(codec, status, false /* render */);
+ media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
+ codec, status, false /* render */);
+ if (mstatus != AMEDIA_OK) {
+ // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+ ALOGE("AMediaCodec_releaseOutputBuffer returned status %d, short decode",
+ (int)mstatus);
+ break;
+ }
if (available == 0) {
// there might be more data, but there's no space for it
sawOutputEOS = true;
@@ -610,21 +631,21 @@
}
}
- AMediaCodec_stop(codec);
- AMediaCodec_delete(codec);
- AMediaExtractor_delete(ex);
+ (void)AMediaCodec_stop(codec);
+ (void)AMediaCodec_delete(codec);
+ (void)AMediaExtractor_delete(ex);
if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, numChannels)) {
- AMediaFormat_delete(format);
+ (void)AMediaFormat_delete(format);
return UNKNOWN_ERROR;
}
- AMediaFormat_delete(format);
+ (void)AMediaFormat_delete(format);
*memsize = written;
return OK;
}
- AMediaFormat_delete(format);
+ (void)AMediaFormat_delete(format);
}
- AMediaExtractor_delete(ex);
+ (void)AMediaExtractor_delete(ex);
return UNKNOWN_ERROR;
}
diff --git a/media/tests/MediaFrameworkTest/res/values/exifinterface.xml b/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
index d556ad3..3c5dd37 100644
--- a/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
+++ b/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
@@ -103,7 +103,7 @@
<item>600</item>
<item>800</item>
<item>1</item>
- <item />
+ <item>0</item>
</array>
<array name="volantis_jpg">
<item>false</item>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
index 6207f7d..dde9bda 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
@@ -45,7 +45,7 @@
private static final String TAG = ExifInterface.class.getSimpleName();
private static final boolean VERBOSE = false; // lots of logging
- private static final double DIFFERENCE_TOLERANCE = .005;
+ private static final double DIFFERENCE_TOLERANCE = .001;
// List of files.
private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
@@ -111,11 +111,11 @@
public final String gpsLongitudeRef;
public final String gpsProcessingMethod;
public final String gpsTimestamp;
- public final String imageLength;
- public final String imageWidth;
+ public final int imageLength;
+ public final int imageWidth;
public final String iso;
- public final String whiteBalance;
- public final String orientation;
+ public final int orientation;
+ public final int whiteBalance;
private static String getString(TypedArray typedArray, int index) {
String stringValue = typedArray.getString(index);
@@ -137,7 +137,7 @@
longitude = typedArray.getFloat(5, 0f);
altitude = typedArray.getFloat(6, 0f);
- // Read values.
+ // Reads values.
make = getString(typedArray, 7);
model = getString(typedArray, 8);
aperture = typedArray.getFloat(9, 0f);
@@ -154,11 +154,11 @@
gpsLongitudeRef = getString(typedArray, 20);
gpsProcessingMethod = getString(typedArray, 21);
gpsTimestamp = getString(typedArray, 22);
- imageLength = getString(typedArray, 23);
- imageWidth = getString(typedArray, 24);
+ imageLength = typedArray.getInt(23, 0);
+ imageWidth = typedArray.getInt(24, 0);
iso = getString(typedArray, 25);
- orientation = getString(typedArray, 26);
- whiteBalance = getString(typedArray, 27);
+ orientation = typedArray.getInt(26, 0);
+ whiteBalance = typedArray.getInt(27, 0);
typedArray.recycle();
}
@@ -208,11 +208,13 @@
+ bitmap.getHeight());
}
} else {
- Log.e(TAG, fileName + " Corrupted image (no thumbnail)");
+ Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
+ + "A thumbnail is expected.");
}
} else {
if (exifInterface.getThumbnail() != null) {
- Log.e(TAG, fileName + " Corrupted image (a thumbnail exists)");
+ Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
+ + "No thumbnail is expected.");
} else {
Log.v(TAG, fileName + " No thumbnail");
}
@@ -226,28 +228,27 @@
Log.v(TAG, fileName + " Latitude = " + latLong[0]);
Log.v(TAG, fileName + " Longitude = " + latLong[1]);
} else {
- Log.v(TAG, fileName + "No latlong data");
+ Log.v(TAG, fileName + " No latlong data");
}
// Prints values.
for (String tagKey : EXIF_TAGS) {
String tagValue = exifInterface.getAttribute(tagKey);
- Log.v(TAG, fileName + "Key{" + tagKey + "} = '" + tagValue + "'");
+ Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
}
}
- private void compareFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
- String stringValue = exifInterface.getAttribute(tag);
- float floatValue = 0f;
-
- if (stringValue != null) {
- floatValue = Float.parseFloat(stringValue);
- }
-
- assertEquals(expectedValue, floatValue, DIFFERENCE_TOLERANCE);
+ private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
+ int intValue = exifInterface.getAttributeInt(tag, 0);
+ assertEquals(expectedValue, intValue);
}
- private void compareStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
+ private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
+ double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
+ assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
+ }
+
+ private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
String stringValue = exifInterface.getAttribute(tag);
if (stringValue != null) {
stringValue = stringValue.trim();
@@ -257,7 +258,10 @@
}
private void compareWithExpectedValue(ExifInterface exifInterface,
- ExpectedValue expectedValue) {
+ ExpectedValue expectedValue, String verboseTag) {
+ if (VERBOSE) {
+ printExifTagsAndValues(verboseTag, exifInterface);
+ }
// Checks a thumbnail image.
assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
if (expectedValue.hasThumbnail) {
@@ -282,78 +286,144 @@
assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
// Checks values.
- compareStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
- compareStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
- compareFloatTag(exifInterface, ExifInterface.TAG_APERTURE, expectedValue.aperture);
- compareStringTag(exifInterface, ExifInterface.TAG_DATETIME, expectedValue.datetime);
- compareFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
- compareFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
- compareStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
- compareStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
- compareStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
+ assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
+ assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
+ assertFloatTag(exifInterface, ExifInterface.TAG_APERTURE, expectedValue.aperture);
+ assertStringTag(exifInterface, ExifInterface.TAG_DATETIME, expectedValue.datetime);
+ assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
+ assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
+ assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
+ assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
+ assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
expectedValue.gpsAltitudeRef);
- compareStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP,
- expectedValue.gpsDatestamp);
- compareStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
- compareStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
+ assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
+ assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
+ assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
expectedValue.gpsLatitudeRef);
- compareStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE,
- expectedValue.gpsLongitude);
- compareStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
+ assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
+ assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
expectedValue.gpsLongitudeRef);
- compareStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
+ assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
expectedValue.gpsProcessingMethod);
- compareStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP,
- expectedValue.gpsTimestamp);
- compareStringTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
- compareStringTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
- compareStringTag(exifInterface, ExifInterface.TAG_ISO, expectedValue.iso);
- compareStringTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
- compareStringTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE,
- expectedValue.whiteBalance);
+ assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
+ assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
+ assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
+ assertStringTag(exifInterface, ExifInterface.TAG_ISO, expectedValue.iso);
+ assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
+ assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
}
private void testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)
throws IOException {
- // Created via path.
+ String verboseTag = imageFile.getName();
+
+ // Creates via path.
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
- if (VERBOSE) {
- printExifTagsAndValues(imageFile.getName(), exifInterface);
- }
- compareWithExpectedValue(exifInterface, expectedValue);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
- // Created from an asset file.
- InputStream in = mContext.getAssets().open(imageFile.getName());
- exifInterface = new ExifInterface(in);
- if (VERBOSE) {
- printExifTagsAndValues(imageFile.getName(), exifInterface);
- }
- compareWithExpectedValue(exifInterface, expectedValue);
-
- // Created via InputStream.
- in = null;
+ // Creates from an asset file.
+ InputStream in = null;
try {
- in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+ in = mContext.getAssets().open(imageFile.getName());
exifInterface = new ExifInterface(in);
- if (VERBOSE) {
- printExifTagsAndValues(imageFile.getName(), exifInterface);
- }
- compareWithExpectedValue(exifInterface, expectedValue);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
} finally {
IoUtils.closeQuietly(in);
}
- // Created via FileDescriptor.
+ // Creates via InputStream.
+ in = null;
try {
- FileDescriptor fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
- exifInterface = new ExifInterface(fd);
- if (VERBOSE) {
- printExifTagsAndValues(imageFile.getName(), exifInterface);
- }
- compareWithExpectedValue(exifInterface, expectedValue);
- } catch (ErrnoException e) {
- e.rethrowAsIOException();
+ in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+ exifInterface = new ExifInterface(in);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+ } finally {
+ IoUtils.closeQuietly(in);
}
+
+ // Creates via FileDescriptor.
+ FileDescriptor fd = null;
+ try {
+ fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
+ exifInterface = new ExifInterface(fd);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ }
+
+ private void testSaveAttributes_withFileName(File imageFile, ExpectedValue expectedValue)
+ throws IOException {
+ String verboseTag = imageFile.getName();
+
+ ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+ exifInterface.saveAttributes();
+ exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+
+ // Test for modifying one attribute.
+ String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+ exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
+ exifInterface.saveAttributes();
+ exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+ assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+ // Restore the backup value.
+ exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
+ exifInterface.saveAttributes();
+ exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+ }
+
+ private void testSaveAttributes_withFileDescriptor(File imageFile, ExpectedValue expectedValue)
+ throws IOException {
+ String verboseTag = imageFile.getName();
+
+ FileDescriptor fd = null;
+ try {
+ fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
+ ExifInterface exifInterface = new ExifInterface(fd);
+ exifInterface.saveAttributes();
+ Os.lseek(fd, 0, OsConstants.SEEK_SET);
+ exifInterface = new ExifInterface(fd);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+
+ // Test for modifying one attribute.
+ String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+ exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
+ exifInterface.saveAttributes();
+ Os.lseek(fd, 0, OsConstants.SEEK_SET);
+ exifInterface = new ExifInterface(fd);
+ assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+ // Restore the backup value.
+ exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
+ exifInterface.saveAttributes();
+ Os.lseek(fd, 0, OsConstants.SEEK_SET);
+ exifInterface = new ExifInterface(fd);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ }
+
+ private void testSaveAttributes_withInputStream(File imageFile, ExpectedValue expectedValue)
+ throws IOException {
+ InputStream in = null;
+ try {
+ in = getContext().getAssets().open(imageFile.getName());
+ ExifInterface exifInterface = new ExifInterface(in);
+ exifInterface.saveAttributes();
+ } catch (UnsupportedOperationException e) {
+ // Expected. saveAttributes is not supported with an ExifInterface object which was
+ // created with InputStream.
+ return;
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ fail("Should not reach here!");
}
private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
@@ -366,30 +436,9 @@
testExifInterfaceCommon(imageFile, expectedValue);
// Test for saving attributes.
- ExifInterface exifInterface;
- try {
- FileDescriptor fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
- exifInterface = new ExifInterface(fd);
- exifInterface.saveAttributes();
- fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
- exifInterface = new ExifInterface(fd);
- if (VERBOSE) {
- printExifTagsAndValues(fileName, exifInterface);
- }
- compareWithExpectedValue(exifInterface, expectedValue);
- } catch (ErrnoException e) {
- e.rethrowAsIOException();
- }
-
- // Test for modifying one attribute.
- exifInterface = new ExifInterface(imageFile.getAbsolutePath());
- exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
- exifInterface.saveAttributes();
- exifInterface = new ExifInterface(imageFile.getAbsolutePath());
- if (VERBOSE) {
- printExifTagsAndValues(fileName, exifInterface);
- }
- assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+ testSaveAttributes_withFileName(imageFile, expectedValue);
+ testSaveAttributes_withFileDescriptor(imageFile, expectedValue);
+ testSaveAttributes_withInputStream(imageFile, expectedValue);
}
private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId)
diff --git a/packages/DocumentsUI/perf-tests/Android.mk b/packages/DocumentsUI/perf-tests/Android.mk
index 919fc56..5ebf85f 100644
--- a/packages/DocumentsUI/perf-tests/Android.mk
+++ b/packages/DocumentsUI/perf-tests/Android.mk
@@ -2,8 +2,8 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
-#LOCAL_SDK_VERSION := current
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-java-files-under, ../tests/src/com/android/documentsui/bots) \
../tests/src/com/android/documentsui/ActivityTest.java \
diff --git a/packages/DocumentsUI/perf-tests/res/raw/earth_small.jpg b/packages/DocumentsUI/perf-tests/res/raw/earth_small.jpg
new file mode 100644
index 0000000..dd2da3e
--- /dev/null
+++ b/packages/DocumentsUI/perf-tests/res/raw/earth_small.jpg
Binary files differ
diff --git a/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java b/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java
index 6147a71..f9b06f8 100644
--- a/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java
+++ b/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java
@@ -18,9 +18,11 @@
import android.content.Context;
import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor.RowBuilder;
import android.database.MatrixCursor;
+import android.graphics.Point;
import android.os.CancellationSignal;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
@@ -31,6 +33,7 @@
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -102,7 +105,14 @@
children = new ArrayList<StubDocument>();
mChildDocuments.put(STRESS_ROOT_2_DOC_ID, children);
for (int i = 0; i < STRESS_ROOT_2_ITEMS; i++) {
- document = StubDocument.createFile(STRESS_ROOT_1_ITEMS + i);
+ try {
+ document = StubDocument.createFile(
+ getContext(), MIME_TYPE_IMAGE,
+ com.android.documentsui.perftests.R.raw.earth_small,
+ STRESS_ROOT_1_ITEMS + i);
+ } catch (IOException e) {
+ return false;
+ }
mDocuments.put(document.id, document);
children.add(document);
}
@@ -151,6 +161,14 @@
}
@Override
+ public AssetFileDescriptor openDocumentThumbnail(String docId, Point sizeHint,
+ CancellationSignal signal)
+ throws FileNotFoundException {
+ final StubDocument document = mDocuments.get(docId);
+ return getContext().getResources().openRawResourceFd(document.thumbnail);
+ }
+
+ @Override
public ParcelFileDescriptor openDocument(String docId, String mode,
CancellationSignal signal)
throws FileNotFoundException {
@@ -171,7 +189,8 @@
row.add(Document.COLUMN_DISPLAY_NAME, document.id);
row.add(Document.COLUMN_SIZE, document.size);
row.add(Document.COLUMN_MIME_TYPE, document.mimeType);
- row.add(Document.COLUMN_FLAGS, 0);
+ row.add(Document.COLUMN_FLAGS,
+ document.thumbnail != -1 ? Document.FLAG_SUPPORTS_THUMBNAIL : 0);
row.add(Document.COLUMN_LAST_MODIFIED, document.lastModified);
}
@@ -184,28 +203,32 @@
final String id;
final int size;
final long lastModified;
+ final int thumbnail;
- private StubDocument(String mimeType, String id, int size, long lastModified) {
+ private StubDocument(String mimeType, String id, int size, long lastModified,
+ int thumbnail) {
this.mimeType = mimeType;
this.id = id;
this.size = size;
this.lastModified = lastModified;
+ this.thumbnail = thumbnail;
}
public static StubDocument createDirectory(int index) {
return new StubDocument(
DocumentsContract.Document.MIME_TYPE_DIR, createRandomId(index), 0,
- createRandomTime(index));
+ createRandomTime(index), -1);
}
public static StubDocument createDirectory(String id) {
- return new StubDocument(DocumentsContract.Document.MIME_TYPE_DIR, id, 0, 0);
+ return new StubDocument(DocumentsContract.Document.MIME_TYPE_DIR, id, 0, 0, -1);
}
- public static StubDocument createFile(int index) {
+ public static StubDocument createFile(Context context, String mimeType, int thumbnail,
+ int index) throws IOException {
return new StubDocument(
- MIME_TYPE_IMAGE, createRandomId(index), createRandomSize(index),
- createRandomTime(index));
+ mimeType, createRandomId(index), createRandomSize(index),
+ createRandomTime(index), thumbnail);
}
private static String createRandomId(int index) {
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index b26ee97..fb557ca 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -204,6 +204,9 @@
<string name="open_external_dialog_request">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g>
access to <xliff:g id="directory" example="Pictures"><i>^2</i></xliff:g> directory on
<xliff:g id="storage" example="SD Card"><i>^3</i></xliff:g>?</string>
+ <!-- Text in an alert dialog asking user to grant app access to a given directory in the internal storage -->
+ <string name="open_external_dialog_request_primary_volume">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g>
+ access to <xliff:g id="directory" example="Pictures"><i>^2</i></xliff:g> directory?</string>
<!-- Text in an alert dialog asking user to grant app access to all data in an external storage volume -->
<string name="open_external_dialog_root_request">Grant <xliff:g id="appName" example="System Settings"><b>^1</b></xliff:g>
access to your data, including photos and videos, on <xliff:g id="storage" example="SD Card"><i>^2</i></xliff:g>?</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index ebc9082..5b5a96e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -153,10 +153,12 @@
if (result != null) {
// Navigate into newly created child
mActivity.onDirectoryCreated(result);
+ Metrics.logCreateDirOperation(getContext());
} else {
- Snackbars.makeSnackbar(mActivity, R.string.create_error, Snackbar.LENGTH_SHORT).show();
+ Snackbars.makeSnackbar(mActivity, R.string.create_error, Snackbar.LENGTH_SHORT)
+ .show();
+ Metrics.logCreateDirError(getContext());
}
-
mActivity.setPending(false);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
index 2dbb730..7a4099a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
@@ -18,6 +18,7 @@
import static com.android.documentsui.Shared.DEBUG;
+import android.annotation.IntDef;
import android.app.Activity;
import android.content.Context;
import android.support.v4.app.ActionBarDrawerToggle;
@@ -27,16 +28,44 @@
import android.view.View;
import android.widget.Toolbar;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A facade over the various pieces comprising "roots fragment in a Drawer".
*
* @see DrawerController#create(DrawerLayout)
*/
abstract class DrawerController implements DrawerListener {
-
public static final String TAG = "DrawerController";
+ // Drawer opening triggered by tapping the navigation icon
+ public static final int OPENED_HAMBURGER = 0;
+ // Drawer opening triggered by swiping right from the edge of the screen
+ public static final int OPENED_SWIPE = 1;
+ // Mostly programmatically forced drawer opening
+ public static final int OPENED_OTHER = 2;
+
+ @IntDef(flag = true, value = {
+ OPENED_HAMBURGER,
+ OPENED_SWIPE,
+ OPENED_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Trigger {}
+
+ /**
+ * Toggles the drawer and sets the OPENED_OTHER as the action that causes opening the drawer.
+ * @param open
+ */
abstract void setOpen(boolean open);
+
+ /**
+ * Toggles the drawer.
+ * @param open
+ * @param trigger Indicates what action caused opening the drawer. It is ignored for closing.
+ */
+ abstract void setOpen(boolean open, @Trigger int trigger);
abstract boolean isPresent();
abstract boolean isOpen();
abstract void setTitle(String title);
@@ -92,11 +121,11 @@
* Runtime controller that manages a real drawer.
*/
private static final class RuntimeDrawerController extends DrawerController {
-
private final ActionBarDrawerToggle mToggle;
private DrawerLayout mLayout;
private View mDrawer;
private Toolbar mToolbar;
+ private @Trigger int mTrigger = OPENED_OTHER;
public RuntimeDrawerController(
DrawerLayout layout, View drawer, ActionBarDrawerToggle toggle,
@@ -113,8 +142,14 @@
@Override
void setOpen(boolean open) {
+ setOpen(open, OPENED_OTHER);
+ }
+
+ @Override
+ void setOpen(boolean open, @Trigger int trigger) {
if (open) {
mLayout.openDrawer(mDrawer);
+ mTrigger = trigger;
} else {
mLayout.closeDrawer(mDrawer);
}
@@ -148,16 +183,21 @@
@Override
public void onDrawerOpened(View drawerView) {
mToggle.onDrawerOpened(drawerView);
+ Metrics.logDrawerOpened(mToolbar.getContext(), mTrigger);
}
@Override
public void onDrawerClosed(View drawerView) {
mToggle.onDrawerClosed(drawerView);
+ mTrigger = OPENED_OTHER;
}
@Override
public void onDrawerStateChanged(int newState) {
mToggle.onDrawerStateChanged(newState);
+ if (newState == DrawerLayout.STATE_DRAGGING) {
+ mTrigger = OPENED_SWIPE;
+ }
}
}
@@ -169,6 +209,8 @@
@Override
void setOpen(boolean open) {}
+ @Override
+ void setOpen(boolean open, @Trigger int trigger) {}
@Override
boolean isOpen() {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 3e04e2a..68c0c2a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -235,7 +235,7 @@
// With new multi-window mode we have to pick how we are launched.
// By default we'd be launched in-place above the existing app.
// By setting launch-to-side ActivityManager will open us to side.
- if (inMultiWindow()) {
+ if (isInMultiWindowMode()) {
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
index 7ad4a09..f1f47c8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
@@ -66,6 +66,7 @@
private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external";
private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled";
private static final String COUNT_STARTUP_MS = "docsui_startup_ms";
+ private static final String COUNT_DRAWER_OPENED = "docsui_drawer_opened";
// Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
// root that is not explicitly recognized by the Metrics code (see {@link
@@ -146,10 +147,14 @@
private static final int FILEOP_MOVE_SYSTEM_PROVIDER = 6; // Move to a system provider.
private static final int FILEOP_MOVE_EXTERNAL_PROVIDER = 7; // Move to a 3rd-party provider.
private static final int FILEOP_DELETE = 8;
+ private static final int FILEOP_RENAME = 9;
+ private static final int FILEOP_CREATE_DIR = 10;
private static final int FILEOP_OTHER_ERROR = 100;
private static final int FILEOP_DELETE_ERROR = 101;
private static final int FILEOP_MOVE_ERROR = 102;
private static final int FILEOP_COPY_ERROR = 103;
+ private static final int FILEOP_RENAME_ERROR = 104;
+ private static final int FILEOP_CREATE_DIR_ERROR = 105;
@IntDef(flag = true, value = {
FILEOP_OTHER,
@@ -160,10 +165,14 @@
FILEOP_MOVE_SYSTEM_PROVIDER,
FILEOP_MOVE_EXTERNAL_PROVIDER,
FILEOP_DELETE,
+ FILEOP_RENAME,
+ FILEOP_CREATE_DIR,
FILEOP_OTHER_ERROR,
FILEOP_COPY_ERROR,
FILEOP_MOVE_ERROR,
- FILEOP_DELETE_ERROR
+ FILEOP_DELETE_ERROR,
+ FILEOP_RENAME_ERROR,
+ FILEOP_CREATE_DIR_ERROR
})
@Retention(RetentionPolicy.SOURCE)
public @interface FileOp {}
@@ -227,6 +236,21 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Provider {}
+ // Codes representing different actions to open the drawer. They are used for bucketing stats in
+ // the COUNT_DRAWER_OPENED histogram.
+ // Do not change or rearrange these values, that will break historical data. Only add to the
+ // list.
+ // Do not use negative numbers or zero; clearcut only handles positive integers.
+ private static final int DRAWER_OPENED_HAMBURGER = 1;
+ private static final int DRAWER_OPENED_SWIPE = 2;
+
+ @IntDef(flag = true, value = {
+ DRAWER_OPENED_HAMBURGER,
+ DRAWER_OPENED_SWIPE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DrawerTrigger {}
+
/**
* Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
*
@@ -287,6 +311,20 @@
}
/**
+ * Logs a drawer opened event. Call this when the user opens drawer by swipe or by clicking the
+ * hamburger icon.
+ * @param context
+ * @param trigger type of action that opened the drawer
+ */
+ public static void logDrawerOpened(Context context, @DrawerController.Trigger int trigger) {
+ if (trigger == DrawerController.OPENED_HAMBURGER) {
+ logHistogram(context, COUNT_DRAWER_OPENED, DRAWER_OPENED_HAMBURGER);
+ } else if (trigger == DrawerController.OPENED_SWIPE) {
+ logHistogram(context, COUNT_DRAWER_OPENED, DRAWER_OPENED_SWIPE);
+ }
+ }
+
+ /**
* Logs file operation stats. Call this when a file operation has completed. The given
* DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one
* provider to another vs copying within a given provider). No PII is logged.
@@ -317,6 +355,28 @@
}
/**
+ * Logs create directory operation. It is a part of file operation stats. We do not
+ * differentiate between internal and external locations, all create directory operations are
+ * logged under COUNT_FILEOP_SYSTEM. Call this when a create directory operation has completed.
+ *
+ * @param context
+ */
+ public static void logCreateDirOperation(Context context) {
+ logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR);
+ }
+
+ /**
+ * Logs rename file operation. It is a part of file operation stats. We do not differentiate
+ * between internal and external locations, all rename operations are logged under
+ * COUNT_FILEOP_SYSTEM. Call this when a rename file operation has completed.
+ *
+ * @param context
+ */
+ public static void logRenameFileOperation(Context context) {
+ logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME);
+ }
+
+ /**
* Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete)
* fails.
*
@@ -349,6 +409,28 @@
}
/**
+ * Logs create directory operation error. We do not differentiate between internal and external
+ * locations, all create directory errors are logged under COUNT_FILEOP_SYSTEM. Call this when a
+ * create directory operation fails.
+ *
+ * @param context
+ */
+ public static void logCreateDirError(Context context) {
+ logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_CREATE_DIR);
+ }
+
+ /**
+ * Logs rename file operation error. We do not differentiate between internal and external
+ * locations, all rename errors are logged under COUNT_FILEOP_SYSTEM. Call this
+ * when a rename file operation fails.
+ *
+ * @param context
+ */
+ public static void logRenameFileError(Context context) {
+ logHistogram(context, COUNT_FILEOP_SYSTEM, FILEOP_RENAME_ERROR);
+ }
+
+ /**
* Logs the cancellation of a file operation. Call this when a Job is canceled.
* @param context
* @param operationType
diff --git a/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
index 30c1020..f6fe47b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
@@ -91,7 +91,7 @@
private void onNavigationIconClicked() {
if (mDrawer.isPresent()) {
- mDrawer.setOpen(true);
+ mDrawer.setOpen(true, DrawerController.OPENED_HAMBURGER);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
index 2fe2756..854be0b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
@@ -86,6 +86,7 @@
private static final String EXTRA_VOLUME_LABEL = "com.android.documentsui.VOLUME_LABEL";
private static final String EXTRA_VOLUME_UUID = "com.android.documentsui.VOLUME_UUID";
private static final String EXTRA_IS_ROOT = "com.android.documentsui.IS_ROOT";
+ private static final String EXTRA_IS_PRIMARY = "com.android.documentsui.IS_PRIMARY";
// Special directory name representing the full volume
static final String DIRECTORY_ROOT = "ROOT_DIRECTORY";
@@ -157,6 +158,13 @@
Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
+ directoryName + ", and user " + userId);
final boolean isRoot = directoryName.equals(DIRECTORY_ROOT);
+ final boolean isPrimary = storageVolume.isPrimary();
+
+ if (isRoot && isPrimary) {
+ if (DEBUG) Log.d(TAG, "root access requested on primary volume");
+ return false;
+ }
+
final File volumeRoot = storageVolume.getPathFile();
File file;
try {
@@ -235,6 +243,7 @@
args.putString(EXTRA_VOLUME_UUID, volumeUuid);
args.putString(EXTRA_APP_LABEL, appLabel);
args.putBoolean(EXTRA_IS_ROOT, isRoot);
+ args.putBoolean(EXTRA_IS_PRIMARY, isPrimary);
final FragmentManager fm = activity.getFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
@@ -352,6 +361,7 @@
private String mVolumeLabel;
private String mAppLabel;
private boolean mIsRoot;
+ private boolean mIsPrimary;
private CheckBox mDontAskAgain;
private OpenExternalDirectoryActivity mActivity;
private AlertDialog mDialog;
@@ -367,6 +377,7 @@
mVolumeLabel = args.getString(EXTRA_VOLUME_LABEL);
mAppLabel = args.getString(EXTRA_APP_LABEL);
mIsRoot = args.getBoolean(EXTRA_IS_ROOT);
+ mIsPrimary= args.getBoolean(EXTRA_IS_PRIMARY);
}
mActivity = (OpenExternalDirectoryActivity) getActivity();
}
@@ -435,7 +446,9 @@
message = TextUtils.expandTemplate(getText(
R.string.open_external_dialog_root_request), mAppLabel, mVolumeLabel);
} else {
- message = TextUtils.expandTemplate(getText(R.string.open_external_dialog_request),
+ message = TextUtils.expandTemplate(
+ getText(mIsPrimary ? R.string.open_external_dialog_request_primary_volume
+ : R.string.open_external_dialog_request),
mAppLabel, directory, mVolumeLabel);
}
final TextView messageField = (TextView) view.findViewById(R.id.message);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index 6efe9c8..c035d92 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -65,8 +65,6 @@
private static final String TAG = "RootsCache";
- private static final boolean ENABLE_SYSTEM_CACHE = true;
-
private final Context mContext;
private final ContentObserver mObserver;
private OnCacheUpdateListener mCacheUpdateListener;
@@ -200,7 +198,7 @@
synchronized (mLock) {
for (String authority : mStoppedAuthorities) {
if (DEBUG) Log.d(TAG, "Loading stopped authority " + authority);
- mRoots.putAll(authority, loadRootsForAuthority(resolver, authority));
+ mRoots.putAll(authority, loadRootsForAuthority(resolver, authority, true));
}
mStoppedAuthorities.clear();
}
@@ -219,13 +217,13 @@
if (DEBUG) {
Log.d(TAG, "Loading stopped authority " + authority);
}
- mRoots.putAll(authority, loadRootsForAuthority(resolver, authority));
+ mRoots.putAll(authority, loadRootsForAuthority(resolver, authority, true));
mStoppedAuthorities.remove(authority);
}
}
private class UpdateTask extends AsyncTask<Void, Void, Void> {
- private final String mFilterPackage;
+ private final String mForceRefreshPackage;
private final Multimap<String, RootInfo> mTaskRoots = ArrayListMultimap.create();
private final HashSet<String> mTaskStoppedAuthorities = new HashSet<>();
@@ -238,18 +236,18 @@
}
/**
- * Only update roots belonging to given package name. Other roots will
+ * Force update roots belonging to given package name. Other roots will
* be copied from cached {@link #mRoots} values.
*/
- public UpdateTask(String filterPackage) {
- mFilterPackage = filterPackage;
+ public UpdateTask(String forceRefreshPackage) {
+ mForceRefreshPackage = forceRefreshPackage;
}
@Override
protected Void doInBackground(Void... params) {
final long start = SystemClock.elapsedRealtime();
- if (mFilterPackage != null) {
+ if (mForceRefreshPackage != null) {
// We must have previously cached values to fill in non-matching
// packages, so wait around for successful first load.
if (!waitForFirstLoad()) {
@@ -302,29 +300,17 @@
return;
}
- // Try using cached roots if filtering
- boolean cacheHit = false;
- if (mFilterPackage != null && !mFilterPackage.equals(info.packageName)) {
- synchronized (mLock) {
- if (mTaskRoots.putAll(info.authority, mRoots.get(info.authority))) {
- if (DEBUG) Log.d(TAG, "Used cached roots for " + info.authority);
- cacheHit = true;
- }
- }
- }
-
- // Cache miss, or loading everything
- if (!cacheHit) {
- mTaskRoots.putAll(info.authority,
- loadRootsForAuthority(mContext.getContentResolver(), info.authority));
- }
+ final boolean forceRefresh = Objects.equals(mForceRefreshPackage, info.packageName);
+ mTaskRoots.putAll(info.authority, loadRootsForAuthority(mContext.getContentResolver(),
+ info.authority, forceRefresh));
}
}
/**
* Bring up requested provider and query for all active roots.
*/
- private Collection<RootInfo> loadRootsForAuthority(ContentResolver resolver, String authority) {
+ private Collection<RootInfo> loadRootsForAuthority(ContentResolver resolver, String authority,
+ boolean forceRefresh) {
if (DEBUG) Log.d(TAG, "Loading roots for " + authority);
synchronized (mObservedAuthorities) {
@@ -336,7 +322,7 @@
}
final Uri rootsUri = DocumentsContract.buildRootsUri(authority);
- if (ENABLE_SYSTEM_CACHE) {
+ if (!forceRefresh) {
// Look for roots data that we might have cached for ourselves in the
// long-lived system process.
final Bundle systemCache = resolver.getCache(rootsUri);
@@ -363,14 +349,12 @@
ContentProviderClient.releaseQuietly(client);
}
- if (ENABLE_SYSTEM_CACHE) {
- // Cache these freshly parsed roots over in the long-lived system
- // process, in case our process goes away. The system takes care of
- // invalidating the cache if the package or Uri changes.
- final Bundle systemCache = new Bundle();
- systemCache.putParcelableArrayList(TAG, roots);
- resolver.putCache(rootsUri, systemCache);
- }
+ // Cache these freshly parsed roots over in the long-lived system
+ // process, in case our process goes away. The system takes care of
+ // invalidating the cache if the package or Uri changes.
+ final Bundle systemCache = new Bundle();
+ systemCache.putParcelableArrayList(TAG, roots);
+ resolver.putCache(rootsUri, systemCache);
return roots;
}
@@ -384,8 +368,8 @@
synchronized (mLock) {
RootInfo root = getRootLocked(authority, rootId);
if (root == null) {
- mRoots.putAll(
- authority, loadRootsForAuthority(mContext.getContentResolver(), authority));
+ mRoots.putAll(authority,
+ loadRootsForAuthority(mContext.getContentResolver(), authority, false));
root = getRootLocked(authority, rootId);
}
return root;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 4acf85e..73ce0e1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -1084,7 +1084,7 @@
}
// Make all items draggable.
- view.setOnLongClickListener(mDragHelper);
+ view.setOnLongClickListener(onLongClickListener);
}
private View.OnDragListener mOnDragListener = new View.OnDragListener() {
@@ -1387,9 +1387,10 @@
}
}
- private DragStartHelper mDragHelper = new DragStartHelper(null) {
+ private DragStartHelper.OnDragStartListener mOnDragStartListener =
+ new DragStartHelper.OnDragStartListener() {
@Override
- protected boolean onDragStart(View v) {
+ public boolean onDragStart(View v, DragStartHelper helper) {
if (isSelected(getModelId(v))) {
List<DocumentInfo> docs = getDraggableDocuments(v);
if (docs.isEmpty()) {
@@ -1409,6 +1410,15 @@
}
};
+ private DragStartHelper mDragHelper = new DragStartHelper(null, mOnDragStartListener);
+
+ private View.OnLongClickListener onLongClickListener = new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ return mDragHelper.onLongClick(v);
+ }
+ };
+
// Previously we listened to events with one class, only to bounce them forward
// to GestureDetector. We're still doing that here, but with a single class
// that reduces overall complexity in our glue code.
@@ -1439,7 +1449,7 @@
// Detect drag events. When a drag is detected, intercept the rest of the gesture.
View itemView = rv.findChildViewUnder(e.getX(), e.getY());
- if (itemView != null && mDragHelper.handleTouch(itemView, e)) {
+ if (itemView != null && mDragHelper.onTouch(itemView, e)) {
return true;
}
// Forward unhandled events to the GestureDetector.
@@ -1451,7 +1461,7 @@
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
View itemView = rv.findChildViewUnder(e.getX(), e.getY());
- mDragHelper.handleTouch(itemView, e);
+ mDragHelper.onTouch(itemView, e);
// Note: even though this event is being handled as part of a drag gesture, continue
// forwarding to the GestureDetector. The detector needs to see the entire cluster of
// events in order to properly interpret gestures.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
index 884abbb..73aa366 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
@@ -44,6 +44,7 @@
import com.android.documentsui.BaseActivity;
import com.android.documentsui.DocumentsApplication;
+import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.Shared;
import com.android.documentsui.Snackbars;
@@ -227,11 +228,13 @@
@Override
protected void onPostExecute(DocumentInfo result) {
- if (result == null) {
+ if (result != null) {
+ Metrics.logRenameFileOperation(getContext());
+ } else {
Snackbars.makeSnackbar(mActivity, R.string.rename_error, Snackbar.LENGTH_SHORT)
.show();
+ Metrics.logRenameFileError(getContext());
}
-
mActivity.setPending(false);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
index 9ed2abf..f10af43 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
@@ -45,6 +45,8 @@
import android.os.RemoteException;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
+import android.system.ErrnoException;
+import android.system.Os;
import android.text.format.DateUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
@@ -451,7 +453,7 @@
ParcelFileDescriptor srcFile = null;
ParcelFileDescriptor dstFile = null;
InputStream in = null;
- OutputStream out = null;
+ ParcelFileDescriptor.AutoCloseOutputStream out = null;
boolean success = false;
try {
@@ -502,6 +504,8 @@
makeCopyProgress(len);
}
+ // Need to invoke IoUtils.close explicitly to avoid from ignoring errors at flush.
+ IoUtils.close(dstFile.getFileDescriptor());
srcFile.checkError();
} catch (IOException e) {
throw new ResourceException(
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
index b1d42e7..a7e4e12 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -464,7 +464,7 @@
return 0;
}
- private int getLayoutIdFor(SecurityMode securityMode) {
+ protected int getLayoutIdFor(SecurityMode securityMode) {
switch (securityMode) {
case Pattern: return R.layout.keyguard_pattern_view;
case PIN: return R.layout.keyguard_pin_view;
diff --git a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
index 1d4ed1d..eb96015 100644
--- a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
+++ b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
@@ -52,6 +52,8 @@
static jmethodID app_fuse_get_file_size;
static jmethodID app_fuse_read_object_bytes;
static jmethodID app_fuse_write_object_bytes;
+static jmethodID app_fuse_flush_file_handle;
+static jmethodID app_fuse_close_file_handle;
static jfieldID app_fuse_buffer;
// NOTE:
@@ -307,7 +309,8 @@
const uint32_t size = in->size;
const void* const buffer = reinterpret_cast<const uint8_t*>(in) + sizeof(fuse_write_in);
uint32_t written_size;
- const int result = write_object_bytes(it->second, offset, size, buffer, &written_size);
+ const int result = write_object_bytes(
+ in->fh, it->second, offset, size, buffer, &written_size);
if (result < 0) {
return result;
}
@@ -320,13 +323,13 @@
const fuse_release_in* in,
FuseResponse<void>* /* out */) {
handles_.erase(in->fh);
- return 0;
+ return env_->CallIntMethod(self_, app_fuse_close_file_handle, file_handle_to_jlong(in->fh));
}
int handle_fuse_flush(const fuse_in_header& /* header */,
- const void* /* in */,
+ const fuse_flush_in* in,
FuseResponse<void>* /* out */) {
- return 0;
+ return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh));
}
template <typename T, typename S>
@@ -382,8 +385,10 @@
return read_size;
}
- int write_object_bytes(int inode, uint64_t offset, uint32_t size, const void* buffer,
- uint32_t* written_size) {
+ int write_object_bytes(uint64_t handle, int inode, uint64_t offset, uint32_t size,
+ const void* buffer, uint32_t* written_size) {
+ static_assert(sizeof(uint64_t) <= sizeof(jlong),
+ "jlong must be able to express any uint64_t values");
ScopedLocalRef<jbyteArray> array(
env_,
static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer)));
@@ -394,15 +399,28 @@
}
memcpy(bytes.get(), buffer, size);
}
- *written_size = env_->CallIntMethod(
- self_, app_fuse_write_object_bytes, inode, offset, size, array.get());
- if (env_->ExceptionCheck()) {
- env_->ExceptionClear();
- return -EIO;
+ const int result = env_->CallIntMethod(
+ self_,
+ app_fuse_write_object_bytes,
+ file_handle_to_jlong(handle),
+ inode,
+ offset,
+ size,
+ array.get());
+ if (result < 0) {
+ return result;
}
+ *written_size = result;
return 0;
}
+ static jlong file_handle_to_jlong(uint64_t handle) {
+ static_assert(
+ sizeof(uint64_t) <= sizeof(jlong),
+ "jlong must be able to express any uint64_t values");
+ return static_cast<jlong>(handle);
+ }
+
static void fuse_reply(int fd, int unique, int reply_code, void* reply_data,
size_t reply_size) {
// Don't send any data for error case.
@@ -511,15 +529,21 @@
return -1;
}
- app_fuse_buffer = env->GetFieldID(app_fuse_class, "mBuffer", "[B");
- if (app_fuse_buffer == nullptr) {
- ALOGE("Can't find mBuffer");
+ app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(JIJI[B)I");
+ if (app_fuse_write_object_bytes == nullptr) {
+ ALOGE("Can't find writeObjectBytes");
return -1;
}
- app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(IJI[B)I");
- if (app_fuse_write_object_bytes == nullptr) {
- ALOGE("Can't find getWriteObjectBytes");
+ app_fuse_flush_file_handle = env->GetMethodID(app_fuse_class, "flushFileHandle", "(J)I");
+ if (app_fuse_flush_file_handle == nullptr) {
+ ALOGE("Can't find flushFileHandle");
+ return -1;
+ }
+
+ app_fuse_close_file_handle = env->GetMethodID(app_fuse_class, "closeFileHandle", "(J)I");
+ if (app_fuse_close_file_handle == nullptr) {
+ ALOGE("Can't find closeFileHandle");
return -1;
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
index 777dc60..88858a8 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
@@ -20,6 +20,7 @@
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.storage.StorageManager;
+import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -34,6 +35,8 @@
System.loadLibrary("appfuse_jni");
}
+ private static final boolean DEBUG = false;
+
/**
* Max read amount specified at the FUSE kernel implementation.
* The value is copied from sdcard.c.
@@ -94,7 +97,8 @@
public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException {
Preconditions.checkArgument(
mode == ParcelFileDescriptor.MODE_READ_ONLY ||
- mode == ParcelFileDescriptor.MODE_WRITE_ONLY);
+ mode == (ParcelFileDescriptor.MODE_WRITE_ONLY |
+ ParcelFileDescriptor.MODE_TRUNCATE));
return ParcelFileDescriptor.open(new File(
getMountPoint(),
Integer.toString(i)),
@@ -127,6 +131,7 @@
/**
* Handles writing bytes for the give inode.
+ * @param fileHandle
* @param inode
* @param offset Offset for file bytes.
* @param size Size for file bytes.
@@ -134,7 +139,23 @@
* @return Number of read bytes. Must not be negative.
* @throws IOException
*/
- int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException;
+ int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)
+ throws IOException, ErrnoException;
+
+ /**
+ * Flushes bytes for file handle.
+ * @param fileHandle
+ * @throws IOException
+ * @throws ErrnoException
+ */
+ void flushFileHandle(long fileHandle) throws IOException, ErrnoException;
+
+ /**
+ * Closes file handle.
+ * @param fileHandle
+ * @throws IOException
+ */
+ void closeFileHandle(long fileHandle) throws IOException, ErrnoException;
}
@UsedByNative("com_android_mtp_AppFuse.cpp")
@@ -142,10 +163,8 @@
private long getFileSize(int inode) {
try {
return mCallback.getFileSize(inode);
- } catch (FileNotFoundException e) {
- return -OsConstants.ENOENT;
- } catch (UnsupportedOperationException e) {
- return -OsConstants.ENOTSUP;
+ } catch (Exception error) {
+ return -getErrnoFromException(error);
}
}
@@ -159,20 +178,62 @@
// It's OK to share the same mBuffer among requests because the requests are processed
// by AppFuseMessageThread sequentially.
return mCallback.readObjectBytes(inode, offset, size, mBuffer);
- } catch (IOException e) {
- return -OsConstants.EIO;
- } catch (UnsupportedOperationException e) {
- return -OsConstants.ENOTSUP;
+ } catch (Exception error) {
+ return -getErrnoFromException(error);
}
}
@UsedByNative("com_android_mtp_AppFuse.cpp")
@WorkerThread
- private /* unsgined */ int writeObjectBytes(int inode,
+ private /* unsgined */ int writeObjectBytes(long fileHandler,
+ int inode,
/* unsigned */ long offset,
/* unsigned */ int size,
- byte[] bytes) throws IOException {
- return mCallback.writeObjectBytes(inode, offset, size, bytes);
+ byte[] bytes) {
+ try {
+ return mCallback.writeObjectBytes(fileHandler, inode, offset, size, bytes);
+ } catch (Exception error) {
+ return -getErrnoFromException(error);
+ }
+ }
+
+ @UsedByNative("com_android_mtp_AppFuse.cpp")
+ @WorkerThread
+ private int flushFileHandle(long fileHandle) {
+ try {
+ mCallback.flushFileHandle(fileHandle);
+ return 0;
+ } catch (Exception error) {
+ return -getErrnoFromException(error);
+ }
+ }
+
+ @UsedByNative("com_android_mtp_AppFuse.cpp")
+ @WorkerThread
+ private int closeFileHandle(long fileHandle) {
+ try {
+ mCallback.closeFileHandle(fileHandle);
+ return 0;
+ } catch (Exception error) {
+ return -getErrnoFromException(error);
+ }
+ }
+
+ private static int getErrnoFromException(Exception error) {
+ if (DEBUG) {
+ Log.e(MtpDocumentsProvider.TAG, "AppFuse callbacks", error);
+ }
+ if (error instanceof FileNotFoundException) {
+ return OsConstants.ENOENT;
+ } else if (error instanceof IOException) {
+ return OsConstants.EIO;
+ } else if (error instanceof UnsupportedOperationException) {
+ return OsConstants.ENOTSUP;
+ } else if (error instanceof IllegalArgumentException) {
+ return OsConstants.EINVAL;
+ } else {
+ return OsConstants.EIO;
+ }
}
private native boolean native_start_app_fuse_loop(int fd);
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 9f64046ce..50781bf 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -17,6 +17,7 @@
package com.android.mtp;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.UriPermission;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
@@ -38,11 +39,16 @@
import android.provider.DocumentsContract;
import android.provider.DocumentsProvider;
import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
@@ -82,6 +88,7 @@
private MtpDatabase mDatabase;
private AppFuse mAppFuse;
private ServiceIntentSender mIntentSender;
+ private Context mContext;
/**
* Provides singleton instance to MtpDocumentsService.
@@ -93,6 +100,7 @@
@Override
public boolean onCreate() {
sSingleton = this;
+ mContext = getContext();
mResources = getContext().getResources();
mMtpManager = new MtpManager(getContext());
mResolver = getContext().getContentResolver();
@@ -137,12 +145,14 @@
@VisibleForTesting
boolean onCreateForTesting(
+ Context context,
Resources resources,
MtpManager mtpManager,
ContentResolver resolver,
MtpDatabase database,
StorageManager storageManager,
ServiceIntentSender intentSender) {
+ mContext = context;
mResources = resources;
mMtpManager = mtpManager;
mResolver = resolver;
@@ -232,43 +242,43 @@
try {
openDevice(identifier.mDeviceId);
final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
- switch (mode) {
- case "r":
- long fileSize;
- try {
- fileSize = getFileSize(documentId);
- } catch (UnsupportedOperationException exception) {
- fileSize = -1;
- }
- // MTP getPartialObject operation does not support files that are larger than
- // 4GB. Fallback to non-seekable file descriptor.
- if (MtpDeviceRecord.isPartialReadSupported(
- device.operationsSupported, fileSize)) {
- return mAppFuse.openFile(
- Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY);
- } else {
- return getPipeManager(identifier).readDocument(mMtpManager, identifier);
- }
- case "w":
- // TODO: Clear the parent document loader task (if exists) and call notify
- // when writing is completed.
- if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
- return getPipeManager(identifier).writeDocument(
- getContext(), mMtpManager, identifier, device.operationsSupported);
- } else {
- throw new UnsupportedOperationException(
- "The device does not support writing operation.");
- }
- case "rw":
- // TODO: Add support for "rw" mode.
+ // Turn off MODE_CREATE because openDocument does not allow to create new files.
+ final int modeFlag =
+ ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE;
+ if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) {
+ long fileSize;
+ try {
+ fileSize = getFileSize(documentId);
+ } catch (UnsupportedOperationException exception) {
+ fileSize = -1;
+ }
+ if (MtpDeviceRecord.isPartialReadSupported(
+ device.operationsSupported, fileSize)) {
+ return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
+ } else {
+ // If getPartialObject{|64} are not supported for the device, returns
+ // non-seekable pipe FD instead.
+ return getPipeManager(identifier).readDocument(mMtpManager, identifier);
+ }
+ } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) {
+ // TODO: Clear the parent document loader task (if exists) and call notify
+ // when writing is completed.
+ if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
+ return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
+ } else {
throw new UnsupportedOperationException(
- "The provider does not support 'rw' mode.");
- default:
- throw new IllegalArgumentException("Unknown mode for openDocument: " + mode);
+ "The device does not support writing operation.");
+ }
+ } else {
+ // TODO: Add support for "rw" mode.
+ throw new UnsupportedOperationException("The provider does not support 'rw' mode.");
}
+ } catch (FileNotFoundException | RuntimeException error) {
+ Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
+ throw error;
} catch (IOException error) {
Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
- throw new FileNotFoundException(error.getMessage());
+ throw new IllegalStateException(error);
}
}
@@ -595,6 +605,13 @@
}
private class AppFuseCallback implements AppFuse.Callback {
+ private final Map<Long, MtpFileWriter> mWriters = new HashMap<>();
+
+ @Override
+ public long getFileSize(int inode) throws FileNotFoundException {
+ return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
+ }
+
@Override
public long readObjectBytes(
int inode, long offset, long size, byte[] buffer) throws IOException {
@@ -617,15 +634,43 @@
}
@Override
- public long getFileSize(int inode) throws FileNotFoundException {
- return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
+ public int writeObjectBytes(
+ long fileHandle, int inode, long offset, int size, byte[] bytes)
+ throws IOException, ErrnoException {
+ final MtpFileWriter writer;
+ if (mWriters.containsKey(fileHandle)) {
+ writer = mWriters.get(fileHandle);
+ } else {
+ writer = new MtpFileWriter(mContext, String.valueOf(inode));
+ mWriters.put(fileHandle, writer);
+ }
+ return writer.write(offset, size, bytes);
}
@Override
- public int writeObjectBytes(int inode, long offset, int size, byte[] bytes)
- throws IOException {
- // TODO: Implement it.
- throw new IOException();
+ public void flushFileHandle(long fileHandle) throws IOException, ErrnoException {
+ final MtpFileWriter writer = mWriters.get(fileHandle);
+ if (writer == null) {
+ // File handle for reading.
+ return;
+ }
+ final MtpDeviceRecord device = getDeviceToolkit(
+ mDatabase.createIdentifier(writer.getDocumentId()).mDeviceId).mDeviceRecord;
+ writer.flush(mMtpManager, mDatabase, device.operationsSupported);
+ }
+
+ @Override
+ public void closeFileHandle(long fileHandle) throws IOException, ErrnoException {
+ final MtpFileWriter writer = mWriters.get(fileHandle);
+ if (writer == null) {
+ // File handle for reading.
+ return;
+ }
+ try {
+ writer.close();
+ } finally {
+ mWriters.remove(fileHandle);
+ }
}
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java
new file mode 100644
index 0000000..3e1bedc
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.mtp;
+
+import android.content.Context;
+import android.mtp.MtpObjectInfo;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.IOException;
+
+class MtpFileWriter implements AutoCloseable {
+ final ParcelFileDescriptor mCacheFd;
+ final String mDocumentId;
+ boolean mDirty;
+
+ MtpFileWriter(Context context, String documentId) throws IOException {
+ mDocumentId = documentId;
+ mDirty = false;
+ final File tempFile = File.createTempFile("mtp", "tmp", context.getCacheDir());
+ mCacheFd = ParcelFileDescriptor.open(
+ tempFile,
+ ParcelFileDescriptor.MODE_READ_WRITE |
+ ParcelFileDescriptor.MODE_TRUNCATE |
+ ParcelFileDescriptor.MODE_CREATE);
+ tempFile.delete();
+ }
+
+ String getDocumentId() {
+ return mDocumentId;
+ }
+
+ int write(long offset, int size, byte[] bytes) throws IOException, ErrnoException {
+ Preconditions.checkArgumentNonnegative(offset, "offset");
+ Preconditions.checkArgumentNonnegative(size, "size");
+ Preconditions.checkArgument(size <= bytes.length);
+ if (size == 0) {
+ return 0;
+ }
+ mDirty = true;
+ Os.lseek(mCacheFd.getFileDescriptor(), offset, OsConstants.SEEK_SET);
+ return Os.write(mCacheFd.getFileDescriptor(), bytes, 0, size);
+ }
+
+ void flush(MtpManager manager, MtpDatabase database, int[] operationsSupported)
+ throws IOException, ErrnoException {
+ // Skip unnecessary flush.
+ if (!mDirty) {
+ return;
+ }
+
+ // Get the placeholder object info.
+ final Identifier identifier = database.createIdentifier(mDocumentId);
+ final MtpObjectInfo placeholderObjectInfo =
+ manager.getObjectInfo(identifier.mDeviceId, identifier.mObjectHandle);
+
+ // Delete the target object info if it already exists (as a placeholder).
+ manager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
+
+ // Create the target object info with a correct file size and upload the file.
+ final long size = Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_END);
+ final MtpObjectInfo targetObjectInfo = new MtpObjectInfo.Builder(placeholderObjectInfo)
+ .setCompressedSize(size)
+ .build();
+
+ Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+ final int newObjectHandle = manager.createDocument(
+ identifier.mDeviceId, targetObjectInfo, mCacheFd);
+
+ final MtpObjectInfo newObjectInfo = manager.getObjectInfo(
+ identifier.mDeviceId, newObjectHandle);
+ final Identifier parentIdentifier =
+ database.getParentIdentifier(identifier.mDocumentId);
+ database.updateObject(
+ identifier.mDocumentId,
+ identifier.mDeviceId,
+ parentIdentifier.mDocumentId,
+ operationsSupported,
+ newObjectInfo,
+ size);
+
+ mDirty = false;
+ }
+
+ @Override
+ public void close() throws IOException {
+ mCacheFd.close();
+ }
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
index 1520f3b..795bbc1 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
@@ -16,13 +16,9 @@
package com.android.mtp;
-import android.content.Context;
-import android.mtp.MtpObjectInfo;
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -52,15 +48,6 @@
return task.getReadingFileDescriptor();
}
- ParcelFileDescriptor writeDocument(Context context, MtpManager model, Identifier identifier,
- int[] operationsSupported)
- throws IOException {
- final Task task = new WriteDocumentTask(
- context, model, identifier, operationsSupported, mDatabase);
- mExecutor.execute(task);
- return task.getWritingFileDescriptor();
- }
-
ParcelFileDescriptor readThumbnail(MtpManager model, Identifier identifier) throws IOException {
final Task task = new GetThumbnailTask(model, identifier);
mExecutor.execute(task);
@@ -81,10 +68,6 @@
ParcelFileDescriptor getReadingFileDescriptor() {
return mDescriptors[0];
}
-
- ParcelFileDescriptor getWritingFileDescriptor() {
- return mDescriptors[1];
- }
}
private static class ImportFileTask extends Task {
@@ -108,85 +91,6 @@
}
}
- private static class WriteDocumentTask extends Task {
- private final Context mContext;
- private final MtpDatabase mDatabase;
- private final int[] mOperationsSupported;
-
- WriteDocumentTask(Context context,
- MtpManager model,
- Identifier identifier,
- int[] supportedOperations,
- MtpDatabase database)
- throws IOException {
- super(model, identifier);
- mContext = context;
- mDatabase = database;
- mOperationsSupported = supportedOperations;
- }
-
- @Override
- public void run() {
- File tempFile = null;
- try {
- // Obtain a temporary file and copy the data to it.
- tempFile = File.createTempFile("mtp", "tmp", mContext.getCacheDir());
- try (
- final FileOutputStream tempOutputStream =
- new ParcelFileDescriptor.AutoCloseOutputStream(
- ParcelFileDescriptor.open(
- tempFile, ParcelFileDescriptor.MODE_WRITE_ONLY));
- final ParcelFileDescriptor.AutoCloseInputStream inputStream =
- new ParcelFileDescriptor.AutoCloseInputStream(mDescriptors[0])
- ) {
- final byte[] buffer = new byte[32 * 1024];
- int bytes;
- while ((bytes = inputStream.read(buffer)) != -1) {
- mDescriptors[0].checkError();
- tempOutputStream.write(buffer, 0, bytes);
- }
- tempOutputStream.flush();
- }
-
- // Get the placeholder object info.
- final MtpObjectInfo placeholderObjectInfo =
- mManager.getObjectInfo(mIdentifier.mDeviceId, mIdentifier.mObjectHandle);
-
- // Delete the target object info if it already exists (as a placeholder).
- mManager.deleteDocument(mIdentifier.mDeviceId, mIdentifier.mObjectHandle);
-
- // Create the target object info with a correct file size and upload the file.
- final MtpObjectInfo targetObjectInfo =
- new MtpObjectInfo.Builder(placeholderObjectInfo)
- .setCompressedSize(tempFile.length())
- .build();
- final ParcelFileDescriptor tempInputDescriptor = ParcelFileDescriptor.open(
- tempFile, ParcelFileDescriptor.MODE_READ_ONLY);
- final int newObjectHandle = mManager.createDocument(
- mIdentifier.mDeviceId, targetObjectInfo, tempInputDescriptor);
-
- final MtpObjectInfo newObjectInfo = mManager.getObjectInfo(
- mIdentifier.mDeviceId, newObjectHandle);
- final Identifier parentIdentifier =
- mDatabase.getParentIdentifier(mIdentifier.mDocumentId);
- mDatabase.updateObject(
- mIdentifier.mDocumentId,
- mIdentifier.mDeviceId,
- parentIdentifier.mDocumentId,
- mOperationsSupported,
- newObjectInfo,
- tempFile.length());
- } catch (IOException error) {
- Log.w(MtpDocumentsProvider.TAG,
- "Failed to send a file because of: " + error.getMessage());
- } finally {
- if (tempFile != null) {
- tempFile.delete();
- }
- }
- }
- }
-
private static class GetThumbnailTask extends Task {
GetThumbnailTask(MtpManager model, Identifier identifier) throws IOException {
super(model, identifier);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
index 3b92506..e421de71 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
@@ -23,6 +23,8 @@
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
+import libcore.io.IoUtils;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -143,7 +145,8 @@
}
@Override
- public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) {
+ public int writeObjectBytes(
+ long fileHandle, int inode, long offset, int size, byte[] bytes) {
for (int i = 0; i < size; i++) {
resultBytes[(int)(offset + i)] = bytes[i];
}
@@ -152,7 +155,7 @@
});
appFuse.mount(storageManager);
final ParcelFileDescriptor fd = appFuse.openFile(
- INODE, ParcelFileDescriptor.MODE_WRITE_ONLY);
+ INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
stream.write('a');
@@ -182,7 +185,7 @@
});
appFuse.mount(storageManager);
final ParcelFileDescriptor fd = appFuse.openFile(
- INODE, ParcelFileDescriptor.MODE_WRITE_ONLY);
+ INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
stream.write('a');
@@ -192,6 +195,46 @@
appFuse.close();
}
+ public void testWriteFile_flushError() throws IOException {
+ final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
+ final int INODE = 10;
+ final AppFuse appFuse = new AppFuse(
+ "test",
+ new TestCallback() {
+ @Override
+ public long getFileSize(int inode) throws FileNotFoundException {
+ if (inode != INODE) {
+ throw new FileNotFoundException();
+ }
+ return 5;
+ }
+
+ @Override
+ public int writeObjectBytes(
+ long fileHandle, int inode, long offset, int size, byte[] bytes) {
+ return size;
+ }
+
+ @Override
+ public void flushFileHandle(long fileHandle) throws IOException {
+ throw new IOException();
+ }
+ });
+ appFuse.mount(storageManager);
+ final ParcelFileDescriptor fd = appFuse.openFile(
+ INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
+ try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
+ stream.write('a');
+ try {
+ IoUtils.close(fd.getFileDescriptor());
+ fail();
+ } catch (IOException e) {
+ }
+ }
+ appFuse.close();
+ }
+
private static class TestCallback implements AppFuse.Callback {
@Override
public long getFileSize(int inode) throws FileNotFoundException {
@@ -205,9 +248,15 @@
}
@Override
- public int writeObjectBytes(int inode, long offset, int size, byte[] bytes)
+ public int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)
throws IOException {
throw new IOException();
}
+
+ @Override
+ public void flushFileHandle(long fileHandle) throws IOException {}
+
+ @Override
+ public void closeFileHandle(long fileHandle) {}
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 0de761cb..9ed15c8 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -21,6 +21,7 @@
import android.mtp.MtpObjectInfo;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
@@ -533,6 +534,30 @@
}
}
+ public void testOpenDocument_writing() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+ setupRoots(0, new MtpRoot[] {
+ new MtpRoot(0, 0, "Storage", 0, 0, "")
+ });
+ final String documentId = mProvider.createDocument("2", "text/plain", "test.txt");
+ {
+ final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null);
+ try (ParcelFileDescriptor.AutoCloseOutputStream stream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
+ stream.write("Hello".getBytes());
+ }
+ }
+ {
+ final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null);
+ try (ParcelFileDescriptor.AutoCloseInputStream stream =
+ new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
+ final byte[] bytes = new byte[5];
+ stream.read(bytes);
+ assertTrue(Arrays.equals("Hello".getBytes(), bytes));
+ }
+ }
+ }
+
public void testBusyDevice() throws Exception {
mMtpManager = new TestMtpManager(getContext()) {
@Override
@@ -740,6 +765,7 @@
mProvider = new MtpDocumentsProvider();
final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
assertTrue(mProvider.onCreateForTesting(
+ getContext(),
mResources,
mMtpManager,
mResolver,
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
index 8611797..53dc3db 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
@@ -16,10 +16,7 @@
package com.android.mtp;
-import android.database.Cursor;
-import android.mtp.MtpObjectInfo;
import android.os.ParcelFileDescriptor;
-import android.provider.DocumentsContract.Document;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
@@ -66,64 +63,6 @@
assertDescriptorError(descriptor);
}
- public void testWriteDocument_basic() throws Exception {
- TestUtil.addTestDevice(mDatabase);
- TestUtil.addTestStorage(mDatabase, "1");
-
- final MtpObjectInfo info =
- new MtpObjectInfo.Builder().setObjectHandle(1).setName("note.txt").build();
- mDatabase.getMapper().startAddingDocuments("2");
- mDatabase.getMapper().putChildDocuments(
- 0, "2", TestUtil.OPERATIONS_SUPPORTED,
- new MtpObjectInfo[] { info },
- new long[] { 0L });
- mDatabase.getMapper().stopAddingDocuments("2");
- // Create a placeholder file which should be replaced by a real file later.
- mtpManager.setObjectInfo(0, info);
-
- // Upload testing bytes.
- final ParcelFileDescriptor descriptor = mPipeManager.writeDocument(
- getContext(),
- mtpManager,
- new Identifier(0, 0, 1, "2", MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT),
- TestUtil.OPERATIONS_SUPPORTED);
- final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
- new ParcelFileDescriptor.AutoCloseOutputStream(descriptor);
- outputStream.write(HELLO_BYTES, 0, HELLO_BYTES.length);
- outputStream.close();
- mExecutor.shutdown();
- assertTrue(mExecutor.awaitTermination(1000, TimeUnit.MILLISECONDS));
-
- // Check if the placeholder file is removed.
- try {
- mtpManager.getObjectInfo(0, 1);
- fail(); // The placeholder file has not been deleted.
- } catch (IOException e) {
- // Expected error, as the file is gone.
- }
-
- // Confirm that the target file is created.
- final MtpObjectInfo targetDocument = mtpManager.getObjectInfo(
- 0, TestMtpManager.CREATED_DOCUMENT_HANDLE);
- assertTrue(targetDocument != null);
-
- // Confirm the object handle is updated.
- try (final Cursor cursor = mDatabase.queryDocument(
- "2", new String[] { MtpDatabaseConstants.COLUMN_OBJECT_HANDLE })) {
- assertEquals(1, cursor.getCount());
- cursor.moveToNext();
- assertEquals(TestMtpManager.CREATED_DOCUMENT_HANDLE, cursor.getInt(0));
- }
-
- // Verify uploaded bytes.
- final byte[] uploadedBytes = mtpManager.getImportFileBytes(
- 0, TestMtpManager.CREATED_DOCUMENT_HANDLE);
- assertEquals(HELLO_BYTES.length, uploadedBytes.length);
- for (int i = 0; i < HELLO_BYTES.length; i++) {
- assertEquals(HELLO_BYTES[i], uploadedBytes[i]);
- }
- }
-
public void testReadThumbnail_basic() throws Exception {
mtpManager.setThumbnail(0, 1, HELLO_BYTES);
final ParcelFileDescriptor descriptor = mPipeManager.readThumbnail(
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
index 2d3935b..99145b7b 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -178,6 +178,8 @@
}
if (mState == STATE_FAILED) {
Log.w(LOG_TAG, "Failed before start.");
+ } else if (mState == STATE_DESTROYED) {
+ Log.w(LOG_TAG, "Destroyed before start.");
} else {
if (mState != STATE_INITIAL) {
throw new IllegalStateException("Cannot start in state:" + stateToString(mState));
@@ -267,7 +269,7 @@
}
if (mState != STATE_STARTED && mState != STATE_UPDATED
&& mState != STATE_FAILED && mState != STATE_CANCELING
- && mState != STATE_CANCELED) {
+ && mState != STATE_CANCELED && mState != STATE_DESTROYED) {
throw new IllegalStateException("Cannot finish in state:"
+ stateToString(mState));
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index eebb60c..c1a3f86 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -206,6 +206,7 @@
int documentPageCount, MediaSize mediaSize, Margins minMargins) {
boolean documentChanged = false;
boolean updatePreviewAreaAndPageSize = false;
+ boolean clearSelectedPages = false;
// If the app does not tell how many pages are in the document we cannot
// optimize and ask for all pages whose count we get from the renderer.
@@ -225,30 +226,41 @@
}
}
- if (!Arrays.equals(mSelectedPages, selectedPages)) {
- mSelectedPages = selectedPages;
- mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
- mSelectedPages, documentPageCount);
- setConfirmedPages(mSelectedPages, documentPageCount);
- updatePreviewAreaAndPageSize = true;
- documentChanged = true;
- }
-
if (mDocumentPageCount != documentPageCount) {
mDocumentPageCount = documentPageCount;
documentChanged = true;
+ clearSelectedPages = true;
}
if (mMediaSize == null || !mMediaSize.equals(mediaSize)) {
mMediaSize = mediaSize;
updatePreviewAreaAndPageSize = true;
documentChanged = true;
+
+ clearSelectedPages = true;
}
if (mMinMargins == null || !mMinMargins.equals(minMargins)) {
mMinMargins = minMargins;
updatePreviewAreaAndPageSize = true;
documentChanged = true;
+
+ clearSelectedPages = true;
+ }
+
+ if (clearSelectedPages) {
+ mSelectedPages = PageRange.ALL_PAGES_ARRAY;
+ mSelectedPageCount = documentPageCount;
+ setConfirmedPages(mSelectedPages, documentPageCount);
+ updatePreviewAreaAndPageSize = true;
+ documentChanged = true;
+ } else if (!Arrays.equals(mSelectedPages, selectedPages)) {
+ mSelectedPages = selectedPages;
+ mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
+ mSelectedPages, documentPageCount);
+ setConfirmedPages(mSelectedPages, documentPageCount);
+ updatePreviewAreaAndPageSize = true;
+ documentChanged = true;
}
// If *all pages* is selected we need to convert that to absolute
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index a24d664..dba6a8b 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -128,8 +128,6 @@
private static final boolean DEBUG = false;
- public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
-
private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
private static final String HAS_PRINTED_PREF = "has_printed";
@@ -176,8 +174,6 @@
"[\\s]*[0-9]+[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
+ "[\\s]*[0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
- public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[]{PageRange.ALL_PAGES};
-
private boolean mIsOptionsUiBound = false;
private final PrinterAvailabilityDetector mPrinterAvailabilityDetector =
@@ -597,14 +593,6 @@
@Override
public void onOptionsClosed() {
- PageRange[] selectedPages = computeSelectedPages();
- if (!Arrays.equals(mSelectedPages, selectedPages)) {
- mSelectedPages = selectedPages;
-
- // Update preview.
- updatePrintPreviewController(false);
- }
-
// Make sure the IME is not on the way of preview as
// the user may have used it to type copies or range.
InputMethodManager imm = getSystemService(InputMethodManager.class);
@@ -717,21 +705,17 @@
private void onSelectPrinterActivityResult(int resultCode, Intent data) {
if (resultCode == RESULT_OK && data != null) {
- PrinterId printerId = data.getParcelableExtra(INTENT_EXTRA_PRINTER_ID);
- if (printerId != null) {
- mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerId);
- final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
- if (index != AdapterView.INVALID_POSITION) {
- mDestinationSpinner.setSelection(index);
- return;
- }
+ PrinterInfo printerInfo = data.getParcelableExtra(
+ SelectPrinterActivity.INTENT_EXTRA_PRINTER);
+ if (printerInfo != null) {
+ mCurrentPrinter = printerInfo;
+ mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerInfo);
}
}
if (mCurrentPrinter != null) {
- PrinterId printerId = mCurrentPrinter.getId();
- final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
- mDestinationSpinner.setSelection(index);
+ // Trigger PrintersObserver.onChanged() to adjust selection back to current printer
+ mDestinationSpinnerAdapter.notifyDataSetChanged();
}
}
@@ -950,7 +934,7 @@
mSelectedPages = selectedPages;
mPrintJob.setPages(selectedPages);
- if (Arrays.equals(selectedPages, ALL_PAGES_ARRAY)) {
+ if (Arrays.equals(selectedPages, PageRange.ALL_PAGES_ARRAY)) {
if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
mRangeOptionsSpinner.setSelection(0);
mPageRangeEditText.setText("");
@@ -1049,7 +1033,22 @@
}
}
+ /**
+ * Clear the selected page range and update the preview if needed.
+ */
+ private void clearPageRanges() {
+ mRangeOptionsSpinner.setSelection(0);
+ mPageRangeEditText.setError(null);
+ mPageRangeEditText.setText("");
+ mSelectedPages = PageRange.ALL_PAGES_ARRAY;
+
+ if (!Arrays.equals(mSelectedPages, mPrintPreviewController.getSelectedPages())) {
+ updatePrintPreviewController(false);
+ }
+ }
+
private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) {
+ boolean clearRanges = false;
PrintAttributes defaults = capabilities.getDefaults();
// Sort the media sizes based on the current locale.
@@ -1061,6 +1060,7 @@
// Media size.
MediaSize currMediaSize = attributes.getMediaSize();
if (currMediaSize == null) {
+ clearRanges = true;
attributes.setMediaSize(defaults.getMediaSize());
} else {
MediaSize newMediaSize = null;
@@ -1079,6 +1079,7 @@
}
// If we did not find the current media size fall back to default.
if (newMediaSize == null) {
+ clearRanges = true;
newMediaSize = defaults.getMediaSize();
}
@@ -1110,7 +1111,14 @@
}
// Margins.
+ if (!Objects.equals(attributes.getMinMargins(), defaults.getMinMargins())) {
+ clearRanges = true;
+ }
attributes.setMinMargins(defaults.getMinMargins());
+
+ if (clearRanges) {
+ clearPageRanges();
+ }
}
private boolean updateDocument(boolean clearLastError) {
@@ -1262,6 +1270,7 @@
// Page range
mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
+ mPageRangeEditText.setVisibility(View.INVISIBLE);
mPageRangeEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
mPageRangeEditText.addTextChangedListener(new RangeTextWatcher());
@@ -1753,36 +1762,38 @@
// Range options
PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
final int pageCount = getAdjustedPageCount(info);
- if (info != null && pageCount > 0) {
- if (pageCount == 1) {
- mRangeOptionsSpinner.setEnabled(false);
- } else {
- mRangeOptionsSpinner.setEnabled(true);
- if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
- if (!mPageRangeEditText.isEnabled()) {
- mPageRangeEditText.setEnabled(true);
- mPageRangeEditText.setVisibility(View.VISIBLE);
- mPageRangeTitle.setVisibility(View.VISIBLE);
- mPageRangeEditText.requestFocus();
- InputMethodManager imm = (InputMethodManager)
- getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(mPageRangeEditText, 0);
- }
+ if (pageCount > 0) {
+ if (info != null) {
+ if (pageCount == 1) {
+ mRangeOptionsSpinner.setEnabled(false);
} else {
- mPageRangeEditText.setEnabled(false);
- mPageRangeEditText.setVisibility(View.INVISIBLE);
- mPageRangeTitle.setVisibility(View.INVISIBLE);
+ mRangeOptionsSpinner.setEnabled(true);
+ if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+ if (!mPageRangeEditText.isEnabled()) {
+ mPageRangeEditText.setEnabled(true);
+ mPageRangeEditText.setVisibility(View.VISIBLE);
+ mPageRangeTitle.setVisibility(View.VISIBLE);
+ mPageRangeEditText.requestFocus();
+ InputMethodManager imm = (InputMethodManager)
+ getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mPageRangeEditText, 0);
+ }
+ } else {
+ mPageRangeEditText.setEnabled(false);
+ mPageRangeEditText.setVisibility(View.INVISIBLE);
+ mPageRangeTitle.setVisibility(View.INVISIBLE);
+ }
}
+ } else {
+ if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+ mRangeOptionsSpinner.setSelection(0);
+ mPageRangeEditText.setText("");
+ }
+ mRangeOptionsSpinner.setEnabled(false);
+ mPageRangeEditText.setEnabled(false);
+ mPageRangeEditText.setVisibility(View.INVISIBLE);
+ mPageRangeTitle.setVisibility(View.INVISIBLE);
}
- } else {
- if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
- mRangeOptionsSpinner.setSelection(0);
- mPageRangeEditText.setText("");
- }
- mRangeOptionsSpinner.setEnabled(false);
- mPageRangeEditText.setEnabled(false);
- mPageRangeEditText.setVisibility(View.INVISIBLE);
- mPageRangeTitle.setVisibility(View.INVISIBLE);
}
final int newPageCount = getAdjustedPageCount(info);
@@ -1933,7 +1944,7 @@
return PageRangeUtils.normalize(pageRangesArray);
}
- return ALL_PAGES_ARRAY;
+ return PageRange.ALL_PAGES_ARRAY;
}
private int getAdjustedPageCount(PrintDocumentInfo info) {
@@ -2229,23 +2240,36 @@
return AdapterView.INVALID_POSITION;
}
- public void ensurePrinterInVisibleAdapterPosition(PrinterId printerId) {
+ public void ensurePrinterInVisibleAdapterPosition(PrinterInfo printer) {
final int printerCount = mPrinterHolders.size();
+ boolean isKnownPrinter = false;
for (int i = 0; i < printerCount; i++) {
PrinterHolder printerHolder = mPrinterHolders.get(i);
- if (printerHolder.printer.getId().equals(printerId)) {
+
+ if (printerHolder.printer.getId().equals(printer.getId())) {
+ isKnownPrinter = true;
+
// If already in the list - do nothing.
if (i < getCount() - 2) {
- return;
+ break;
}
// Else replace the last one (two items are not printers).
final int lastPrinterIndex = getCount() - 3;
mPrinterHolders.set(i, mPrinterHolders.get(lastPrinterIndex));
mPrinterHolders.set(lastPrinterIndex, printerHolder);
- notifyDataSetChanged();
- return;
+ break;
}
}
+
+ if (!isKnownPrinter) {
+ PrinterHolder printerHolder = new PrinterHolder(printer);
+ printerHolder.removed = true;
+
+ mPrinterHolders.add(Math.max(0, getCount() - 3), printerHolder);
+ }
+
+ // Force reload to adjust selection in PrintersObserver.onChanged()
+ notifyDataSetChanged();
}
@Override
@@ -2428,8 +2452,7 @@
List<PrinterHolder> newPrinterHolders = new ArrayList<>();
// Update printers we already have which are either updated or removed.
- // We do not remove printers if the currently selected printer is removed
- // to prevent the user printing to a wrong printer.
+ // We do not remove the currently selected printer.
final int oldPrinterCount = mPrinterHolders.size();
for (int i = 0; i < oldPrinterCount; i++) {
PrinterHolder printerHolder = mPrinterHolders.get(i);
@@ -2438,10 +2461,11 @@
if (updatedPrinter != null) {
printerHolder.printer = updatedPrinter;
printerHolder.removed = false;
- } else {
+ newPrinterHolders.add(printerHolder);
+ } else if (mCurrentPrinter != null && mCurrentPrinter.getId().equals(oldPrinterId)){
printerHolder.removed = true;
+ newPrinterHolders.add(printerHolder);
}
- newPrinterHolders.add(printerHolder);
}
// Add the rest of the new printers, i.e. what is left.
@@ -2473,14 +2497,25 @@
return null;
}
- public void pruneRemovedPrinters() {
+ /**
+ * Remove a printer from the holders if it is marked as removed.
+ *
+ * @param printerId the id of the printer to remove.
+ *
+ * @return true iff the printer was removed.
+ */
+ public boolean pruneRemovedPrinter(PrinterId printerId) {
final int holderCounts = mPrinterHolders.size();
for (int i = holderCounts - 1; i >= 0; i--) {
PrinterHolder printerHolder = mPrinterHolders.get(i);
- if (printerHolder.removed) {
+
+ if (printerHolder.printer.getId().equals(printerId) && printerHolder.removed) {
mPrinterHolders.remove(i);
+ return true;
}
}
+
+ return false;
}
private void addPrinters(List<PrinterHolder> list, Collection<PrinterInfo> printers) {
@@ -2525,17 +2560,17 @@
PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
oldPrinterState.getId());
- if (printerHolder == null) {
- return;
- }
PrinterInfo newPrinterState = printerHolder.printer;
- if (!printerHolder.removed) {
- mDestinationSpinnerAdapter.pruneRemovedPrinters();
- } else {
+ if (printerHolder.removed) {
onPrinterUnavailable(newPrinterState);
}
+ if (mDestinationSpinner.getSelectedItem() != printerHolder) {
+ mDestinationSpinner.setSelection(
+ mDestinationSpinnerAdapter.getPrinterIndex(newPrinterState.getId()));
+ }
+
if (oldPrinterState.equals(newPrinterState)) {
return;
}
@@ -2607,6 +2642,8 @@
private final class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
@Override
public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
+ boolean clearRanges = false;
+
if (spinner == mDestinationSpinner) {
if (position == AdapterView.INVALID_POSITION) {
return;
@@ -2625,13 +2662,28 @@
return;
}
+ PrinterId oldId = null;
+ if (mCurrentPrinter != null) {
+ oldId = mCurrentPrinter.getId();
+ }
+
mCurrentPrinter = currentPrinter;
+ if (oldId != null) {
+ boolean printerRemoved = mDestinationSpinnerAdapter.pruneRemovedPrinter(oldId);
+
+ if (printerRemoved) {
+ // Trigger PrinterObserver.onChanged to adjust selection. This will call
+ // this function again.
+ mDestinationSpinnerAdapter.notifyDataSetChanged();
+ return;
+ }
+ }
+
PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
currentPrinter.getId());
if (!printerHolder.removed) {
setState(STATE_CONFIGURING);
- mDestinationSpinnerAdapter.pruneRemovedPrinters();
ensurePreviewUiShown();
}
@@ -2653,10 +2705,17 @@
} else if (spinner == mMediaSizeSpinner) {
SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
PrintAttributes attributes = mPrintJob.getAttributes();
+
+ MediaSize newMediaSize;
if (mOrientationSpinner.getSelectedItemPosition() == 0) {
- attributes.setMediaSize(mediaItem.value.asPortrait());
+ newMediaSize = mediaItem.value.asPortrait();
} else {
- attributes.setMediaSize(mediaItem.value.asLandscape());
+ newMediaSize = mediaItem.value.asLandscape();
+ }
+
+ if (newMediaSize != attributes.getMediaSize()) {
+ clearRanges = true;
+ attributes.setMediaSize(newMediaSize);
}
} else if (spinner == mColorModeSpinner) {
SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position);
@@ -2668,25 +2727,35 @@
SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position);
PrintAttributes attributes = mPrintJob.getAttributes();
if (mMediaSizeSpinner.getSelectedItem() != null) {
- if (orientationItem.value == ORIENTATION_PORTRAIT) {
- attributes.copyFrom(attributes.asPortrait());
- } else {
- attributes.copyFrom(attributes.asLandscape());
+ boolean isPortrait = attributes.isPortrait();
+
+ if (isPortrait != (orientationItem.value == ORIENTATION_PORTRAIT)) {
+ clearRanges = true;
+ if (orientationItem.value == ORIENTATION_PORTRAIT) {
+ attributes.copyFrom(attributes.asPortrait());
+ } else {
+ attributes.copyFrom(attributes.asLandscape());
+ }
}
}
} else if (spinner == mRangeOptionsSpinner) {
if (mRangeOptionsSpinner.getSelectedItemPosition() == 0) {
+ clearRanges = true;
mPageRangeEditText.setText("");
} else if (TextUtils.isEmpty(mPageRangeEditText.getText())) {
mPageRangeEditText.setError("");
}
}
- if (canUpdateDocument()) {
- updateDocument(false);
+ if (clearRanges) {
+ clearPageRanges();
}
updateOptionsUi();
+
+ if (canUpdateDocument()) {
+ updateDocument(false);
+ }
}
@Override
@@ -2702,6 +2771,16 @@
if (!TextUtils.isEmpty(editText.getText())) {
editText.setSelection(editText.getText().length());
}
+
+ if (view == mPageRangeEditText && !hasFocus) {
+ PageRange[] selectedPages = computeSelectedPages();
+ if (selectedPages != null && !Arrays.equals(mSelectedPages, selectedPages)) {
+ mSelectedPages = selectedPages;
+
+ // Update preview.
+ updatePrintPreviewController(false);
+ }
+ }
}
}
@@ -2719,19 +2798,22 @@
@Override
public void afterTextChanged(Editable editable) {
final boolean hadErrors = hasErrors();
-
String text = editable.toString();
if (TextUtils.isEmpty(text)) {
- mPageRangeEditText.setError("");
- updateOptionsUi();
+ if (mPageRangeEditText.getError() == null) {
+ mPageRangeEditText.setError("");
+ updateOptionsUi();
+ }
return;
}
String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
- mPageRangeEditText.setError("");
- updateOptionsUi();
+ if (mPageRangeEditText.getError() == null) {
+ mPageRangeEditText.setError("");
+ updateOptionsUi();
+ }
return;
}
@@ -2747,8 +2829,10 @@
}
final int pageIndex = Integer.parseInt(numericString);
if (pageIndex < 1 || pageIndex > pageCount) {
- mPageRangeEditText.setError("");
- updateOptionsUi();
+ if (mPageRangeEditText.getError() == null) {
+ mPageRangeEditText.setError("");
+ updateOptionsUi();
+ }
return;
}
}
@@ -2757,13 +2841,14 @@
// greater than the to page. When computing the requested pages
// we just swap them if necessary.
- mPageRangeEditText.setError(null);
- mPrintButton.setEnabled(true);
- updateOptionsUi();
-
- if (hadErrors && !hasErrors()) {
+ if (mPageRangeEditText.getError() != null) {
+ mPageRangeEditText.setError(null);
updateOptionsUi();
}
+
+ if (hadErrors && canUpdateDocument()) {
+ updateDocument(false);
+ }
}
}
@@ -2783,8 +2868,10 @@
final boolean hadErrors = hasErrors();
if (editable.length() == 0) {
- mCopiesEditText.setError("");
- updateOptionsUi();
+ if (mCopiesEditText.getError() == null) {
+ mCopiesEditText.setError("");
+ updateOptionsUi();
+ }
return;
}
@@ -2796,16 +2883,19 @@
}
if (copies < MIN_COPIES) {
- mCopiesEditText.setError("");
- updateOptionsUi();
+ if (mCopiesEditText.getError() == null) {
+ mCopiesEditText.setError("");
+ updateOptionsUi();
+ }
return;
}
mPrintJob.setCopies(copies);
- mCopiesEditText.setError(null);
-
- updateOptionsUi();
+ if (mCopiesEditText.getError() != null) {
+ mCopiesEditText.setError(null);
+ updateOptionsUi();
+ }
if (hadErrors && canUpdateDocument()) {
updateDocument(false);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index e53a522..cee16c8 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -73,8 +73,9 @@
private static final int LOADER_ID_PRINT_REGISTRY_INT = 2;
private static final int LOADER_ID_ENABLED_PRINT_SERVICES = 3;
- public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
+ public static final String INTENT_EXTRA_PRINTER = "INTENT_EXTRA_PRINTER";
+ private static final String EXTRA_PRINTER = "EXTRA_PRINTER";
private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
private static final String KEY_NOT_FIRST_CREATE = "KEY_NOT_FIRST_CREATE";
@@ -134,7 +135,7 @@
if (printer == null) {
startAddPrinterActivity();
} else {
- onPrinterSelected(printer.getId());
+ onPrinterSelected(printer);
}
}
});
@@ -244,7 +245,7 @@
MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer,
Menu.NONE, R.string.print_select_printer);
Intent intent = new Intent();
- intent.putExtra(EXTRA_PRINTER_ID, printer.getId());
+ intent.putExtra(EXTRA_PRINTER, printer);
selectItem.setIntent(intent);
}
@@ -263,8 +264,8 @@
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.string.print_select_printer: {
- PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID);
- onPrinterSelected(printerId);
+ PrinterInfo printer = item.getIntent().getParcelableExtra(EXTRA_PRINTER);
+ onPrinterSelected(printer);
} return true;
case R.string.print_forget_printer: {
@@ -302,9 +303,9 @@
super.onStop();
}
- private void onPrinterSelected(PrinterId printerId) {
+ private void onPrinterSelected(PrinterInfo printer) {
Intent intent = new Intent();
- intent.putExtra(INTENT_EXTRA_PRINTER_ID, printerId);
+ intent.putExtra(INTENT_EXTRA_PRINTER, printer);
setResult(RESULT_OK, intent);
finish();
}
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
new file mode 100755
index 0000000..299a5b7
--- /dev/null
+++ b/packages/SettingsLib/res/values/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, 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.
+*/
+-->
+<resources>
+ <!-- Configuration for automotive -->
+ <bool name="enable_pbap_pce_profile">false</bool>
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 6226b23..6052ccd 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -24,12 +24,14 @@
import android.bluetooth.BluetoothMap;
import android.bluetooth.BluetoothInputDevice;
import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothPbapClient;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.content.Intent;
import android.os.ParcelUuid;
import android.util.Log;
+import com.android.settingslib.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -82,7 +84,9 @@
private final HidProfile mHidProfile;
private OppProfile mOppProfile;
private final PanProfile mPanProfile;
+ private PbapClientProfile mPbapClientProfile;
private final PbapServerProfile mPbapProfile;
+ private final boolean mUsePbapPce;
/**
* Mapping from profile name, e.g. "HEADSET" to profile object.
@@ -99,6 +103,7 @@
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mEventManager = eventManager;
+ mUsePbapPce = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile);
// pass this reference to adapter and event manager (circular dependency)
mLocalAdapter.setProfileManager(this);
mEventManager.setProfileManager(this);
@@ -205,9 +210,24 @@
} else if (mOppProfile != null) {
Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
}
+
+ //PBAP Client
+ if (mUsePbapPce) {
+ if (mPbapClientProfile == null) {
+ if(DEBUG) Log.d(TAG, "Adding local PBAP Client profile");
+ mPbapClientProfile = new PbapClientProfile(mContext, mLocalAdapter, mDeviceManager,
+ this);
+ addProfile(mPbapClientProfile, PbapClientProfile.NAME,
+ BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ } else if (mPbapClientProfile != null) {
+ Log.w(TAG,
+ "Warning: PBAP Client profile was previously added but the UUID is now missing.");
+ }
+
mEventManager.registerProfileIntentReceiver();
- // There is no local SDP record for HID and Settings app doesn't control PBAP
+ // There is no local SDP record for HID and Settings app doesn't control PBAP Server.
}
private final Collection<ServiceListener> mServiceListeners =
@@ -351,6 +371,10 @@
}
}
+ public PbapClientProfile getPbapClientProfile() {
+ return mPbapClientProfile;
+ }
+
public PbapServerProfile getPbapProfile(){
return mPbapProfile;
}
@@ -430,6 +454,12 @@
removedProfiles.remove(mMapProfile);
mMapProfile.setPreferred(device, true);
}
- }
+ if (mUsePbapPce) {
+ profiles.add(mPbapClientProfile);
+ removedProfiles.remove(mPbapClientProfile);
+ profiles.remove(mPbapProfile);
+ removedProfiles.add(mPbapProfile);
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
new file mode 100755
index 0000000..aa95be2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothPbapClient;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+final class PbapClientProfile implements LocalBluetoothProfile {
+ private static final String TAG = "PbapClientProfile";
+ private static boolean V = false;
+
+ private BluetoothPbapClient mService;
+ private boolean mIsProfileReady;
+
+ private final LocalBluetoothAdapter mLocalAdapter;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+
+ static final ParcelUuid[] SRC_UUIDS = {
+ BluetoothUuid.PBAP_PSE,
+ };
+
+ static final String NAME = "PbapClient";
+ private final LocalBluetoothProfileManager mProfileManager;
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 6;
+
+ // These callbacks run on the main thread.
+ private final class PbapClientServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (V) {
+ Log.d(TAG,"Bluetooth service connected");
+ }
+ mService = (BluetoothPbapClient) proxy;
+ // We just bound to the service, so refresh the UI for any connected PBAP devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ Log.w(TAG, "PbapClientProfile found new device: " + nextDevice);
+ device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ }
+ device.onProfileStateChanged(PbapClientProfile.this, BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+ mIsProfileReady = true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (V) {
+ Log.d(TAG,"Bluetooth service disconnected");
+ }
+ mIsProfileReady = false;
+ }
+ }
+
+ private void refreshProfiles() {
+ Collection<CachedBluetoothDevice> cachedDevices = mDeviceManager.getCachedDevicesCopy();
+ for (CachedBluetoothDevice device : cachedDevices) {
+ device.onUuidChanged();
+ }
+ }
+
+ public boolean pbapClientExists() {
+ return (mService != null);
+ }
+
+ public boolean isProfileReady() {
+ return mIsProfileReady;
+ }
+
+ PbapClientProfile(Context context, LocalBluetoothAdapter adapter,
+ CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mLocalAdapter = adapter;
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+ mLocalAdapter.getProfileProxy(context, new PbapClientServiceListener(),
+ BluetoothProfile.PBAP_CLIENT);
+ }
+
+ public boolean isConnectable() {
+ return true;
+ }
+
+ public boolean isAutoConnectable() {
+ return true;
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (mService == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return mService.getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ if (V) {
+ Log.d(TAG,"PBAPClientProfile got connect request");
+ }
+ if (mService == null) {
+ return false;
+ }
+ List<BluetoothDevice> srcs = getConnectedDevices();
+ if (srcs != null) {
+ for (BluetoothDevice src : srcs) {
+ if (src.equals(device)) {
+ // Connect to same device, Ignore it
+ Log.d(TAG,"Ignoring Connect");
+ return true;
+ }
+ }
+ mService.disconnect(device);
+ }
+ Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
+
+ return mService.connect(device);
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ if (V) {
+ Log.d(TAG,"PBAPClientProfile got disconnect request");
+ }
+ if (mService == null) {
+ return false;
+ }
+ return mService.disconnect(device);
+ }
+
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return mService.getConnectionState(device);
+ }
+
+ public boolean isPreferred(BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ }
+
+ public int getPreferred(BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothProfile.PRIORITY_OFF;
+ }
+ return mService.getPriority(device);
+ }
+
+ public void setPreferred(BluetoothDevice device, boolean preferred) {
+ if (mService == null) {
+ return;
+ }
+ if (preferred) {
+ if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ } else {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ }
+ }
+
+ public String toString() {
+ return NAME;
+ }
+
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ public int getNameResource(BluetoothDevice device) {
+ // we need to have same string in UI as the server side.
+ return R.string.bluetooth_profile_pbap;
+ }
+
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ return R.string.bluetooth_profile_pbap_summary;
+ }
+
+ public int getDrawableResource(BluetoothClass btClass) {
+ return R.drawable.ic_bt_cellphone;
+ }
+
+ protected void finalize() {
+ if (V) {
+ Log.d(TAG, "finalize()");
+ }
+ if (mService != null) {
+ try {
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
+ BluetoothProfile.PBAP_CLIENT,mService);
+ mService = null;
+ } catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up PBAP Client proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index c3a5089..ff70190 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -176,8 +176,11 @@
@Override
public void setContentView(@LayoutRes int layoutResID) {
- LayoutInflater.from(this).inflate(layoutResID,
- (ViewGroup) findViewById(R.id.content_frame));
+ final ViewGroup parent = (ViewGroup) findViewById(R.id.content_frame);
+ if (parent != null) {
+ parent.removeAllViews();
+ }
+ LayoutInflater.from(this).inflate(layoutResID, parent);
}
@Override
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index cb861ec..b88846b 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -26,9 +26,6 @@
android:clipChildren="false"
android:clipToPadding="false"
android:baselineAligned="false"
- android:background="@drawable/quick_header_bg"
- android:clickable="true"
- android:focusable="true"
>
<LinearLayout
@@ -83,6 +80,10 @@
android:id="@+id/expand_indicator"
android:layout_width="48dp"
android:layout_height="48dp"
+ android:clipToPadding="false"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
android:padding="12dp" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/recents_stack_action_button.xml b/packages/SystemUI/res/layout/recents_stack_action_button.xml
index 8783261..2b2ce4e 100644
--- a/packages/SystemUI/res/layout/recents_stack_action_button.xml
+++ b/packages/SystemUI/res/layout/recents_stack_action_button.xml
@@ -27,8 +27,6 @@
android:textSize="14sp"
android:textColor="#FFFFFF"
android:textAllCaps="true"
- android:drawableStart="@drawable/ic_history"
- android:drawablePadding="6dp"
android:shadowColor="#99000000"
android:shadowDx="0"
android:shadowDy="2"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 622ae71..fd051b1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -153,6 +153,9 @@
<!-- The animation duration for animating the removal of a task view. -->
<integer name="recents_animate_task_view_remove_duration">175</integer>
+ <!-- The base animation duration for animating the removal of all task views. -->
+ <integer name="recents_animate_task_views_remove_all_duration">300</integer>
+
<!-- The animation duration for scrolling the stack to a particular item. -->
<integer name="recents_animate_task_stack_scroll_duration">200</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d1faa4a..c094da9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -574,10 +574,12 @@
<dimen name="recents_layout_bottom_margin">16dp</dimen>
<dimen name="recents_layout_side_margin_phone">16dp</dimen>
<dimen name="recents_layout_side_margin_tablet">48dp</dimen>
+ <dimen name="recents_layout_side_margin_tablet_docked">16dp</dimen>
<dimen name="recents_layout_side_margin_tablet_xlarge">64dp</dimen>
+ <dimen name="recents_layout_side_margin_tablet_xlarge_docked">16dp</dimen>
<!-- The height between the top margin and the top of the focused task. -->
- <dimen name="recents_layout_top_peek_size">56dp</dimen>
+ <dimen name="recents_layout_top_peek_size">48dp</dimen>
<!-- The height between the bottom margin and the top of task in front of the focused task. -->
<dimen name="recents_layout_bottom_peek_size">56dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java
index 5e33a9f..17f4dab 100644
--- a/packages/SystemUI/src/com/android/systemui/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -17,6 +17,7 @@
package com.android.systemui;
import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
@@ -32,6 +33,7 @@
public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
public static final Interpolator LINEAR = new LinearInterpolator();
+ public static final Interpolator ACCELERATE = new AccelerateInterpolator();
public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index fd43aa0..115c9d0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -1,13 +1,13 @@
package com.android.systemui.qs;
import android.content.Context;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import com.android.internal.widget.PagerAdapter;
-import com.android.internal.widget.ViewPager;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanel.TileRecord;
@@ -37,7 +37,8 @@
public void onPageSelected(int position) {
if (mPageIndicator == null) return;
if (mPageListener != null) {
- mPageListener.onPageChanged(position == 0);
+ mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
+ : position == 0);
}
}
@@ -47,7 +48,8 @@
if (mPageIndicator == null) return;
mPageIndicator.setLocation(position + positionOffset);
if (mPageListener != null) {
- mPageListener.onPageChanged(position == 0 && positionOffsetPixels == 0);
+ mPageListener.onPageChanged(positionOffsetPixels == 0 &&
+ (isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
}
}
@@ -59,6 +61,21 @@
}
@Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ setAdapter(mAdapter);
+ setCurrentItem(0, false);
+ }
+
+ @Override
+ public void setCurrentItem(int item, boolean smoothScroll) {
+ if (isLayoutRtl()) {
+ item = mPages.size() - 1 - item;
+ }
+ super.setCurrentItem(item, smoothScroll);
+ }
+
+ @Override
public boolean hasOverlappingRendering() {
return false;
}
@@ -128,6 +145,7 @@
mNumPages = index + 1;
mPageIndicator.setNumPages(mNumPages);
mAdapter.notifyDataSetChanged();
+ setCurrentItem(0, false);
}
}
@@ -206,6 +224,9 @@
public Object instantiateItem(ViewGroup container, int position) {
if (DEBUG) Log.d(TAG, "Instantiating " + position);
+ if (isLayoutRtl()) {
+ position = mPages.size() - 1 - position;
+ }
ViewGroup view = mPages.get(position);
container.addView(view);
return view;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 0b6eaba..58d7c81 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -64,6 +64,7 @@
private boolean mAllowFancy;
private boolean mFullRows;
private int mNumQuickTiles;
+ private float mLastPosition;
public QSAnimator(QSContainer container, QuickQSPanel quickPanel, QSPanel panel) {
mQsContainer = container;
@@ -80,6 +81,10 @@
}
}
+ public void onRtlChanged() {
+ updateAnimators();
+ }
+
public void setOnKeyguard(boolean onKeyguard) {
mOnKeyguard = onKeyguard;
if (mOnKeyguard) {
@@ -246,7 +251,7 @@
private void getRelativePositionInt(int[] loc1, View view, View parent) {
if(view == parent || view == null) return;
- loc1[0] += view.getLeft();
+ loc1[0] += view.getX();
loc1[1] += view.getTop();
getRelativePositionInt(loc1, (View) view.getParent(), parent);
}
@@ -256,6 +261,7 @@
if (mOnKeyguard) {
return;
}
+ mLastPosition = position;
if (mOnFirstPage && mAllowFancy) {
mQuickQsPanel.setAlpha(1);
mFirstPageAnimator.setPosition(position);
@@ -296,7 +302,7 @@
private void clearAnimationState() {
final int N = mAllViews.size();
mQuickQsPanel.setAlpha(0);
- mQuickQsPanel.setVisibility(View.INVISIBLE);
+ mQuickQsPanel.setVisibility(View.VISIBLE);
for (int i = 0; i < N; i++) {
View v = mAllViews.get(i);
v.setAlpha(1);
@@ -312,7 +318,7 @@
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
- updateAnimators();
+ mQsPanel.post(mUpdateAnimators);
}
@Override
@@ -334,6 +340,7 @@
@Override
public void run() {
updateAnimators();
+ setPosition(mLastPosition);
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
index 43ebe04..5d06aeb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Point;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -43,6 +44,7 @@
private static final boolean DEBUG = false;
private final Point mSizePoint = new Point();
+ private final Rect mQsBounds = new Rect();
private int mHeightOverride = -1;
private QSPanel mQSPanel;
@@ -76,6 +78,12 @@
mQSCustomizer.setQsContainer(this);
}
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ mQSAnimator.onRtlChanged();
+ }
+
public void setHost(QSTileHost qsh) {
mQSPanel.setHost(qsh, mQSCustomizer);
mHeader.setQSPanel(mQSPanel);
@@ -226,6 +234,12 @@
mQSDetail.setFullyExpanded(expansion == 1);
mQSAnimator.setPosition(expansion);
updateBottom();
+
+ // Set bounds on the QS panel so it doesn't run over the header.
+ mQsBounds.top = (int) (mQSPanel.getHeight() * (1 - expansion));
+ mQsBounds.right = mQSPanel.getWidth();
+ mQsBounds.bottom = mQSPanel.getHeight();
+ mQSPanel.setClipBounds(mQsBounds);
}
public void animateHeaderSlidingIn(long delay) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 899b0ef..a05818a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -119,12 +119,18 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
TunerService.get(mContext).addTunable(this, QS_SHOW_BRIGHTNESS);
+ if (mHost != null) {
+ setTiles(mHost.getTiles());
+ }
}
@Override
protected void onDetachedFromWindow() {
TunerService.get(mContext).removeTunable(this);
mHost.removeCallback(this);
+ for (TileRecord record : mRecords) {
+ record.tile.removeCallbacks();
+ }
super.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 1659888..8777a91 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -24,11 +24,15 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.qs.QSTile.State;
import com.android.systemui.qs.external.TileServices;
@@ -81,6 +85,8 @@
abstract protected void handleClick();
abstract protected void handleUpdateState(TState state, Object arg);
+ private UserManager mUserManager;
+
/**
* Declare the category of this tile.
*
@@ -93,6 +99,7 @@
mHost = host;
mContext = host.getContext();
mHandler = new H(host.getLooper());
+ mUserManager = UserManager.get(mContext);
}
public String getTileSpec() {
@@ -212,6 +219,7 @@
}
protected void handleLongClick() {
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_QS_LONG_PRESS, getTileSpec());
mHost.startActivityDismissingKeyguard(getLongClickIntent());
}
@@ -282,12 +290,11 @@
}
protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
- EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
- userRestriction, ActivityManager.getCurrentUser());
- if (admin != null && !RestrictedLockUtils.hasBaseUserRestriction(mContext,
- userRestriction, ActivityManager.getCurrentUser())) {
+ UserHandle user = UserHandle.of(ActivityManager.getCurrentUser());
+ if (mUserManager.hasUserRestriction(userRestriction, user)
+ && !mUserManager.hasBaseUserRestriction(userRestriction, user)) {
state.disabledByPolicy = true;
- state.enforcedAdmin = admin;
+ state.enforcedAdmin = EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
} else {
state.disabledByPolicy = false;
state.enforcedAdmin = null;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index aeca840..83ac45c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -32,6 +32,8 @@
import android.widget.LinearLayout;
import android.widget.Toolbar;
import android.widget.Toolbar.OnMenuItemClickListener;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto;
import com.android.systemui.R;
import com.android.systemui.qs.QSContainer;
import com.android.systemui.qs.QSDetailClipper;
@@ -116,6 +118,7 @@
public void show(int x, int y) {
if (!isShown) {
+ MetricsLogger.visible(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
isShown = true;
setTileSpecs();
setVisibility(View.VISIBLE);
@@ -127,6 +130,7 @@
public void hide(int x, int y) {
if (isShown) {
+ MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
isShown = false;
mToolbar.dismissPopupMenus();
setCustomizing(false);
@@ -149,6 +153,7 @@
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MENU_RESET:
+ MetricsLogger.action(getContext(), MetricsProto.MetricsEvent.ACTION_QS_EDIT_RESET);
reset();
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 353c6b6..4c13451 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -16,6 +16,7 @@
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
@@ -36,12 +37,15 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto;
import com.android.systemui.R;
import com.android.systemui.qs.QSIconView;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.customize.TileAdapter.Holder;
import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
+import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -304,12 +308,24 @@
notifyItemMoved(from, to);
CharSequence announcement;
if (to >= mDividerIndex) {
+ MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE_SPEC,
+ strip(mTiles.get(to)));
+ MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE,
+ from);
announcement = mContext.getString(R.string.accessibility_qs_edit_tile_removed,
fromLabel);
} else if (from >= mDividerIndex) {
+ MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD_SPEC,
+ strip(mTiles.get(to)));
+ MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD,
+ to);
announcement = mContext.getString(R.string.accessibility_qs_edit_tile_added,
fromLabel, (to + 1));
} else {
+ MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_MOVE_SPEC,
+ strip(mTiles.get(to)));
+ MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_MOVE,
+ to);
announcement = mContext.getString(R.string.accessibility_qs_edit_tile_moved,
fromLabel, (to + 1));
}
@@ -317,6 +333,15 @@
return true;
}
+ private String strip(TileInfo tileInfo) {
+ String spec = tileInfo.spec;
+ if (spec.startsWith(CustomTile.PREFIX)) {
+ ComponentName component = CustomTile.getComponentFromSpec(spec);
+ return component.getPackageName();
+ }
+ return spec;
+ }
+
private <T> void move(int from, int to, List<T> list) {
list.add(from > to ? to : to + 1, list.get(from));
list.remove(from > to ? from + 1 : from);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index b89c2f6..fff7d78 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -90,6 +90,7 @@
private RecentsPackageMonitor mPackageMonitor;
private long mLastTabKeyEventTime;
+ private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
private boolean mFinishedOnStartup;
private boolean mIgnoreAltTabRelease;
private boolean mIsVisible;
@@ -266,6 +267,7 @@
getWindow().getAttributes().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+ mLastOrientation = getResources().getConfiguration().orientation;
mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration);
mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() {
@Override
@@ -274,6 +276,9 @@
}
});
+ // Set the window background
+ getWindow().setBackgroundDrawable(mRecentsView.getBackgroundScrim());
+
// Create the home intent runnable
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
mHomeIntent.addCategory(Intent.CATEGORY_HOME);
@@ -415,13 +420,18 @@
// Update the nav bar for the current orientation
updateNavBarScrim(false /* animateNavBarScrim */, AnimationProps.IMMEDIATE);
- EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */));
+ // Notify of the config change
+ int newOrientation = getResources().getConfiguration().orientation;
+ EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */,
+ (mLastOrientation != newOrientation)));
+ mLastOrientation = newOrientation;
}
@Override
- public void onMultiWindowChanged(boolean inMultiWindow) {
- super.onMultiWindowChanged(inMultiWindow);
- EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */));
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ super.onMultiWindowModeChanged(isInMultiWindowMode);
+ EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */,
+ false /* fromOrientationChange */));
if (mRecentsView != null) {
// Reload the task stack completely
@@ -439,7 +449,7 @@
mRecentsView.updateStack(loadPlan.getTaskStack());
}
- EventBus.getDefault().send(new MultiWindowStateChangedEvent(inMultiWindow));
+ EventBus.getDefault().send(new MultiWindowStateChangedEvent(isInMultiWindowMode));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 043510e..1715356 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -36,7 +36,7 @@
// Enables the task affiliations
public static final boolean EnableAffiliatedTaskGroups = false;
// TODO: To be repurposed
- public static final boolean EnableStackActionButton = false;
+ public static final boolean EnableStackActionButton = true;
// Overrides the Tuner flags and enables the timeout
private static final boolean EnableFastToggleTimeout = false;
// Overrides the Tuner flags and enables the paging via the Recents button
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index ffc037d..43d627d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -663,13 +663,18 @@
@Override
public void run() {
final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mThumbnailTransitionBitmapCache = transitionBitmap;
- mThumbnailTransitionBitmapCacheKey = toTask;
- }
- });
+ if (transitionBitmap != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mThumbnailTransitionBitmapCache = transitionBitmap;
+ mThumbnailTransitionBitmapCacheKey = toTask;
+ }
+ });
+ } else {
+ Log.e(TAG, "Could not load thumbnail for task: " + toTask + " at transform: " +
+ toTransform);
+ }
}
});
}
@@ -774,7 +779,7 @@
// Get the transform for the running task
stackView.updateLayoutAlgorithm(true /* boundScroll */);
stackView.updateToInitialState(true /* scrollToInitialState */);
- mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
+ stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
stackView.getScroller().getStackScroll(), mTmpTransform, null);
return mTmpTransform;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
index c234c34..8be9ca7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
@@ -24,8 +24,10 @@
public class ConfigurationChangedEvent extends EventBus.AnimatedEvent {
public final boolean fromMultiWindow;
+ public final boolean fromOrientationChange;
- public ConfigurationChangedEvent(boolean fromMultiWindow) {
+ public ConfigurationChangedEvent(boolean fromMultiWindow, boolean fromOrientationChange) {
this.fromMultiWindow = fromMultiWindow;
+ this.fromOrientationChange = fromOrientationChange;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ResetBackgroundScrimEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/recents/events/ui/ResetBackgroundScrimEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java
index 863f40b..f8b59c7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ResetBackgroundScrimEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java
@@ -17,10 +17,11 @@
package com.android.systemui.recents.events.ui;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.views.TaskView;
/**
- * This is sent to reset the background scrim back to the initial state.
+ * This event is sent to request that all the {@link TaskView}s are dismissed.
*/
-public class ResetBackgroundScrimEvent extends EventBus.Event {
+public class DismissAllTaskViewsEvent extends EventBus.AnimatedEvent {
// Simple event
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
index 1165f4e..1f8c644 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
@@ -17,7 +17,6 @@
package com.android.systemui.recents.events.ui;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.views.TaskView;
/**
@@ -26,10 +25,8 @@
public class DismissTaskViewEvent extends EventBus.AnimatedEvent {
public final TaskView taskView;
- public final Task task;
- public DismissTaskViewEvent(TaskView taskView, Task task) {
+ public DismissTaskViewEvent(TaskView taskView) {
this.taskView = taskView;
- this.task = task;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateBackgroundScrimEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateBackgroundScrimEvent.java
deleted file mode 100644
index fdd4c67..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateBackgroundScrimEvent.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-package com.android.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent to request an update to the background scrim.
- */
-public class UpdateBackgroundScrimEvent extends EventBus.Event {
-
- public final float alpha;
-
- public UpdateBackgroundScrimEvent(float alpha) {
- this.alpha = alpha;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 5a2507d..df3f56c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -222,6 +222,11 @@
Task newFrontMostTask, AnimationProps animation, boolean fromDockGesture);
/**
+ * Notifies when all tasks have been removed from the stack.
+ */
+ void onStackTasksRemoved(TaskStack stack);
+
+ /**
* Notifies when tasks in the stack have been updated.
*/
void onStackTasksUpdated(TaskStack stack);
@@ -510,6 +515,22 @@
}
/**
+ * Removes all tasks from the stack.
+ */
+ public void removeAllTasks() {
+ ArrayList<Task> tasks = mStackTaskList.getTasks();
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ Task t = tasks.get(i);
+ removeTaskImpl(mStackTaskList, t);
+ mRawTaskList.remove(t);
+ }
+ if (mCb != null) {
+ // Notify that all tasks have been removed
+ mCb.onStackTasksRemoved(this);
+ }
+ }
+
+ /**
* Sets a few tasks in one go, without calling any callbacks.
*
* @param tasks the new set of tasks to replace the current set.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
index 3d0e75a..5eb9fda 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
@@ -158,6 +158,11 @@
}
@Override
+ public void onStackTasksRemoved(TaskStack stack) {
+ // Do nothing
+ }
+
+ @Override
public void onStackTasksUpdated(TaskStack stack) {
// Do nothing
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 9dc3fb1..9eec2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -28,11 +28,13 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.util.Log;
import android.view.AppTransitionAnimationSpec;
import android.view.IAppTransitionAnimationSpecsFuture;
@@ -252,12 +254,13 @@
/**
* Composes the transition spec when docking a task, which includes a full task bitmap.
*/
- public List<AppTransitionAnimationSpec> composeDockAnimationSpec(
- TaskView taskView, Rect transform) {
- TaskViewTransform viewTransform = new TaskViewTransform();
- viewTransform.fillIn(taskView);
- return Collections.singletonList(new AppTransitionAnimationSpec(taskView.getTask().key.id,
- RecentsTransitionHelper.composeTaskBitmap(taskView, viewTransform), transform));
+ public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView,
+ Rect bounds) {
+ mTmpTransform.fillIn(taskView);
+ Task task = taskView.getTask();
+ Bitmap thumbnail = RecentsTransitionHelper.composeTaskBitmap(taskView, mTmpTransform);
+ return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, thumbnail,
+ bounds));
}
/**
@@ -336,18 +339,27 @@
float scale = transform.scale;
int fromWidth = (int) (transform.rect.width() * scale);
int fromHeight = (int) (transform.rect.height() * scale);
- Bitmap b = Bitmap.createBitmap(fromWidth, fromHeight,
- Bitmap.Config.ARGB_8888);
+ if (fromWidth == 0 || fromHeight == 0) {
+ Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
+ " at transform: " + transform);
- if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- b.eraseColor(0xFFff0000);
+ Bitmap b = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ b.eraseColor(Color.TRANSPARENT);
+ return b;
} else {
- Canvas c = new Canvas(b);
- c.scale(scale, scale);
- taskView.draw(c);
- c.setBitmap(null);
+ Bitmap b = Bitmap.createBitmap(fromWidth, fromHeight,
+ Bitmap.Config.ARGB_8888);
+
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+ b.eraseColor(0xFFff0000);
+ } else {
+ Canvas c = new Canvas(b);
+ c.scale(scale, scale);
+ taskView.draw(c);
+ c.setBitmap(null);
+ }
+ return b.createAshmemBitmap();
}
- return b.createAshmemBitmap();
}
private static Bitmap composeHeaderBitmap(TaskView taskView,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index b23b01f..da2f721 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -58,10 +58,11 @@
import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
+import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
+import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
+import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
-import com.android.systemui.recents.events.ui.ResetBackgroundScrimEvent;
-import com.android.systemui.recents.events.ui.UpdateBackgroundScrimEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -87,6 +88,9 @@
private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
private static final float DEFAULT_SCRIM_ALPHA = 0.33f;
+ private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 150;
+ private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
+
private TaskStack mStack;
private TaskStackView mTaskStackView;
private TextView mStackActionButton;
@@ -99,7 +103,8 @@
private Rect mSystemInsets = new Rect();
private int mDividerSize;
- private ColorDrawable mBackgroundScrim = new ColorDrawable(Color.BLACK);
+ private Drawable mBackgroundScrim = new ColorDrawable(
+ Color.argb((int) (DEFAULT_SCRIM_ALPHA * 255), 0, 0, 0)).mutate();
private Animator mBackgroundScrimAnimator;
private RecentsTransitionHelper mTransitionHelper;
@@ -135,10 +140,11 @@
R.dimen.recents_task_view_rounded_corners_radius);
mStackActionButton = (TextView) inflater.inflate(R.layout.recents_stack_action_button,
this, false);
+ mStackActionButton.forceHasOverlappingRendering(false);
mStackActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- // TODO: To be implemented
+ EventBus.getDefault().send(new DismissAllTaskViewsEvent());
}
});
addView(mStackActionButton);
@@ -152,8 +158,6 @@
}
mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
addView(mEmptyView);
-
- setBackground(mBackgroundScrim);
}
/**
@@ -179,14 +183,14 @@
if (isResumingFromVisible) {
// If we are already visible, then restore the background scrim
- animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, DEFAULT_UPDATE_SCRIM_DURATION);
+ animateBackgroundScrim(1f, DEFAULT_UPDATE_SCRIM_DURATION);
} else {
// If we are already occluded by the app, then set the final background scrim alpha now.
// Otherwise, defer until the enter animation completes to animate the scrim alpha with
// the tasks for the home animation.
if (launchState.launchedViaDockGesture || launchState.launchedFromApp
|| isTaskStackEmpty) {
- mBackgroundScrim.setAlpha((int) (DEFAULT_SCRIM_ALPHA * 255));
+ mBackgroundScrim.setAlpha(255);
} else {
mBackgroundScrim.setAlpha(0);
}
@@ -215,6 +219,13 @@
return mStack;
}
+ /*
+ * Returns the window background scrim.
+ */
+ public Drawable getBackgroundScrim() {
+ return mBackgroundScrim;
+ }
+
/**
* Returns whether the last task launched was in the freeform stack or not.
*/
@@ -566,17 +577,21 @@
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
&& mStack.getTaskCount() > 0) {
- animateBackgroundScrim(DEFAULT_SCRIM_ALPHA,
+ animateBackgroundScrim(1f,
TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
}
}
- public final void onBusEvent(UpdateBackgroundScrimEvent event) {
- animateBackgroundScrim(event.alpha, DEFAULT_UPDATE_SCRIM_DURATION);
+ public final void onBusEvent(AllTaskViewsDismissedEvent event) {
+ hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
}
- public final void onBusEvent(ResetBackgroundScrimEvent event) {
- animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, DEFAULT_UPDATE_SCRIM_DURATION);
+ public final void onBusEvent(DismissAllTaskViewsEvent event) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (!ssp.hasDockedTask()) {
+ // Animate the background away only if we are dismissing Recents to home
+ animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION);
+ }
}
public final void onBusEvent(ShowStackActionButtonEvent event) {
@@ -584,7 +599,7 @@
return;
}
- showStackActionButton(150, event.translate);
+ showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
}
public final void onBusEvent(HideStackActionButtonEvent event) {
@@ -592,7 +607,7 @@
return;
}
- hideStackActionButton(100, true /* translate */);
+ hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
}
/**
@@ -623,7 +638,6 @@
.alpha(1f)
.setDuration(duration)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .withLayer()
.start();
}
});
@@ -669,7 +683,6 @@
postAnimationTrigger.decrement();
}
})
- .withLayer()
.start();
postAnimationTrigger.increment();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index 9c8189a..13ad9a5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -24,6 +24,7 @@
import com.android.systemui.R;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
/** Manages the scrims for the various system bars. */
public class SystemBarScrimViews {
@@ -107,4 +108,14 @@
animateNavBarScrimVisibility(false, animation);
}
}
+
+ public final void onBusEvent(DismissAllTaskViewsEvent event) {
+ if (mHasNavBarScrim) {
+ AnimationProps animation = new AnimationProps()
+ .setDuration(AnimationProps.BOUNDS,
+ TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION)
+ .setInterpolator(AnimationProps.BOUNDS, Interpolators.FAST_OUT_SLOW_IN);
+ animateNavBarScrimVisibility(false, animation);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 8db81f7..1c433d8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -31,6 +31,7 @@
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
@@ -277,7 +278,6 @@
public void startExitToHomeAnimation(boolean animated,
ReferenceCountedTrigger postAnimationTrigger) {
TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
- TaskStackViewScroller stackScroller = mStackView.getScroller();
TaskStack stack = mStackView.getStack();
// Break early if there are no tasks
@@ -313,8 +313,7 @@
taskAnimation = AnimationProps.IMMEDIATE;
}
- stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
- null);
+ mTmpTransform.fillIn(tv);
mTmpTransform.alpha = 0f;
mTmpTransform.rect.offset(0, offscreenYOffset);
mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
@@ -328,8 +327,6 @@
public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested,
final ReferenceCountedTrigger postAnimationTrigger) {
Resources res = mStackView.getResources();
- TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
- TaskStackViewScroller stackScroller = mStackView.getScroller();
int taskViewExitToAppDuration = res.getInteger(
R.integer.recents_task_exit_to_app_duration);
@@ -362,8 +359,7 @@
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
- stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
- null);
+ mTmpTransform.fillIn(tv);
mTmpTransform.alpha = 0f;
mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
@@ -374,16 +370,14 @@
/**
* Starts the delete animation for the specified {@link TaskView}.
*/
- public void startDeleteTaskAnimation(Task deleteTask, final TaskView deleteTaskView,
+ public void startDeleteTaskAnimation(final TaskView deleteTaskView,
final ReferenceCountedTrigger postAnimationTrigger) {
Resources res = mStackView.getResources();
TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
- TaskStackViewScroller stackScroller = mStackView.getScroller();
int taskViewRemoveAnimDuration = res.getInteger(
R.integer.recents_animate_task_view_remove_duration);
- int taskViewRemoveAnimTranslationXPx = res.getDimensionPixelSize(
- R.dimen.recents_task_view_remove_anim_translation_x);
+ int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.mTaskRect.left;
// Disabling clipping with the stack while the view is animating away, this will get
// restored when the task is next picked up from the view pool
@@ -399,14 +393,58 @@
});
postAnimationTrigger.increment();
- stackLayout.getStackTransform(deleteTask, stackScroller.getStackScroll(), mTmpTransform,
- null);
+ mTmpTransform.fillIn(deleteTaskView);
mTmpTransform.alpha = 0f;
- mTmpTransform.rect.offset(taskViewRemoveAnimTranslationXPx, 0);
+ mTmpTransform.rect.offset(offscreenXOffset, 0);
mStackView.updateTaskViewToTransform(deleteTaskView, mTmpTransform, taskAnimation);
}
/**
+ * Starts the delete animation for all the {@link TaskView}s.
+ */
+ public void startDeleteAllTasksAnimation(final List<TaskView> taskViews,
+ final ReferenceCountedTrigger postAnimationTrigger) {
+ Resources res = mStackView.getResources();
+ TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
+
+ int taskViewRemoveAnimDuration = res.getInteger(
+ R.integer.recents_animate_task_views_remove_all_duration);
+ int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.mTaskRect.left;
+
+ int taskViewCount = taskViews.size();
+ int startDelayMax = 125;
+
+ for (int i = taskViewCount - 1; i >= 0; i--) {
+ TaskView tv = taskViews.get(i);
+ int indexFromFront = taskViewCount - i - 1;
+ float x = Interpolators.ACCELERATE.getInterpolation((float) indexFromFront /
+ taskViewCount);
+ int startDelay = (int) Utilities.mapRange(x, 0, startDelayMax);
+
+ // Disabling clipping with the stack while the view is animating away
+ tv.setClipViewInStack(false);
+
+ // Compose the new animation and transform and star the animation
+ AnimationProps taskAnimation = new AnimationProps(startDelay,
+ taskViewRemoveAnimDuration, Interpolators.FAST_OUT_LINEAR_IN,
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ postAnimationTrigger.decrement();
+
+ // Re-enable clipping with the stack (we will reuse this view)
+ tv.setClipViewInStack(true);
+ }
+ });
+ postAnimationTrigger.increment();
+
+ mTmpTransform.fillIn(tv);
+ mTmpTransform.rect.offset(offscreenXOffset, 0);
+ mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
+ }
+ }
+
+ /**
* Starts the animation to focus the next {@link TaskView} when paging through recents.
*
* @return whether or not this will trigger a scroll in the stack
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 4b1faf3..2508304 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -557,15 +557,17 @@
mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
mInitialNormX = null;
} else {
- float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
- mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2)) -
- Math.max(0, mUnfocusedRange.getAbsoluteX(normX)));
+ // We are overriding the initial two task positions, so set the initial scroll
+ // position to match the second task (aka focused task) position
+ float initialTopNormX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
+ mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2))
+ - Math.max(0, mUnfocusedRange.getAbsoluteX(initialTopNormX)));
// Set the initial scroll to the predefined state (which differs from the stack)
mInitialNormX = new float[] {
getNormalizedXFromUnfocusedY(mSystemInsets.bottom + mInitialBottomOffset,
FROM_BOTTOM),
- normX
+ initialTopNormX
};
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 04f153f..5416a48 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -72,6 +72,7 @@
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
+import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
@@ -1322,7 +1323,8 @@
}
// Update the stack action button visibility
- if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
+ if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
+ mStack.getTaskCount() > 0) {
EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
} else {
EventBus.getDefault().send(new HideStackActionButtonEvent());
@@ -1447,6 +1449,26 @@
}
@Override
+ public void onStackTasksRemoved(TaskStack stack) {
+ // Reset the focused task
+ resetFocusedTask(getFocusedTask());
+
+ // Return all the views to the pool
+ List<TaskView> taskViews = new ArrayList<>();
+ taskViews.addAll(getTaskViews());
+ for (int i = taskViews.size() - 1; i >= 0; i--) {
+ mViewPool.returnViewToPool(taskViews.get(i));
+ }
+
+ // Remove all the ignore tasks
+ mIgnoreTasks.clear();
+
+ // If there are no remaining tasks, then just close recents
+ EventBus.getDefault().send(new AllTaskViewsDismissedEvent(
+ R.string.recents_empty_message_dismissed_all));
+ }
+
+ @Override
public void onStackTasksUpdated(TaskStack stack) {
// Update the layout and immediately layout
updateLayoutAlgorithm(false /* boundScroll */);
@@ -1597,7 +1619,8 @@
if (mEnterAnimationComplete) {
if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
- curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
+ curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
+ mStack.getTaskCount() > 0) {
EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */));
} else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
@@ -1705,14 +1728,42 @@
}
}
- public final void onBusEvent(final DismissTaskViewEvent event) {
+ public final void onBusEvent(DismissTaskViewEvent event) {
// For visible children, defer removing the task until after the animation
- mAnimationHelper.startDeleteTaskAnimation(event.task, event.taskView,
- event.getAnimationTrigger());
+ mAnimationHelper.startDeleteTaskAnimation(event.taskView, event.getAnimationTrigger());
+ }
+
+ public final void onBusEvent(final DismissAllTaskViewsEvent event) {
+ // Keep track of the tasks which will have their data removed
+ ArrayList<Task> tasks = new ArrayList<>(mStack.getStackTasks());
+ mAnimationHelper.startDeleteAllTasksAnimation(getTaskViews(), event.getAnimationTrigger());
+ event.addPostAnimationCallback(new Runnable() {
+ @Override
+ public void run() {
+ // Announce for accessibility
+ announceForAccessibility(getContext().getString(
+ R.string.accessibility_recents_all_items_dismissed));
+
+ // Remove all tasks and delete the task data for all tasks
+ mStack.removeAllTasks();
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i)));
+ }
+
+ MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS_ALL);
+ }
+ });
+
}
public final void onBusEvent(TaskViewDismissedEvent event) {
- removeTaskViewFromStack(event.taskView, event.task);
+ // Announce for accessibility
+ announceForAccessibility(getContext().getString(
+ R.string.accessibility_recents_item_dismissed, event.task.title));
+
+ // Remove the task from the stack
+ mStack.removeTask(event.task, new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
+ Interpolators.FAST_OUT_SLOW_IN), false /* fromDockGesture */);
EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS,
@@ -1948,28 +1999,16 @@
}
}
- // Trigger a new layout and scroll to the initial state
- mInitialState = event.fromMultiWindow
- ? INITIAL_STATE_UPDATE_ALL
- : INITIAL_STATE_UPDATE_LAYOUT_ONLY;
+ // Trigger a new layout and update to the initial state if necessary
+ if (event.fromMultiWindow) {
+ mInitialState = INITIAL_STATE_UPDATE_ALL;
+ } else if (event.fromOrientationChange) {
+ mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY;
+ }
requestLayout();
}
/**
- * Removes the task from the stack, and updates the focus to the next task in the stack if the
- * removed TaskView was focused.
- */
- private void removeTaskViewFromStack(TaskView tv, Task task) {
- // Announce for accessibility
- tv.announceForAccessibility(getContext().getString(
- R.string.accessibility_recents_item_dismissed, task.title));
-
- // Remove the task from the stack
- mStack.removeTask(task, new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
- Interpolators.FAST_OUT_SLOW_IN), false /* fromDockGesture */);
- }
-
- /**
* Starts an alpha animation on the freeform workspace background.
*/
private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index aed19c3..ee0de1a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -356,6 +356,11 @@
return;
}
+ // Disallow tapping above and below the stack to dismiss recents
+ if (x > mSv.mLayoutAlgorithm.mStackRect.left && x < mSv.mLayoutAlgorithm.mStackRect.right) {
+ return;
+ }
+
// If tapping on the freeform workspace background, just launch the first freeform task
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.hasFreeformWorkspaceSupport()) {
@@ -507,13 +512,13 @@
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
tv.setTouchEnabled(true);
+ // Remove the task view from the stack
+ EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
// Update the scroll to the final scroll position from onBeginDrag()
mSv.getScroller().setStackScroll(mTargetStackScroll, null);
// Update the focus state to the final focus state
mSv.getStackAlgorithm().setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
mSv.getStackAlgorithm().clearUnfocusedTaskOverrides();
- // Remove the task view from the stack
- EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
// Stop tracking this deletion animation
mSwipeHelperAnimations.remove(v);
// Keep track of deletions by keyboard
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index c085d80..f8ed700 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -384,7 +384,7 @@
void dismissTask() {
// Animate out the view and call the callback
final TaskView tv = this;
- DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv, mTask);
+ DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv);
dismissEvent.addPostAnimationCallback(new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index dc76e61..b512393 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -226,4 +226,9 @@
v.getViewBounds().setClipBottom(0);
v.setLeftTopRightBottom(0, 0, 0, 0);
}
+
+ @Override
+ public String toString() {
+ return "R: " + rect + " V: " + visible;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 023c4d0..45e94f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -225,7 +225,8 @@
public void onInflated(View v) {
mQsContainer = (QSContainer) v.findViewById(R.id.quick_settings_container);
mQsContainer.setPanelView(NotificationPanelView.this);
- mQsContainer.getHeader().setOnClickListener(NotificationPanelView.this);
+ mQsContainer.getHeader().findViewById(R.id.expand_indicator)
+ .setOnClickListener(NotificationPanelView.this);
}
});
mClockView = (TextView) findViewById(R.id.clock_view);
@@ -1760,7 +1761,7 @@
@Override
public void onClick(View v) {
- if (v == mQsContainer.getHeader()) {
+ if (v.getId() == R.id.expand_indicator) {
onQsExpansionStarted();
if (mQsExpanded) {
flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 8f329c4..b507903 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -124,7 +124,6 @@
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
- ((RippleDrawable) getBackground()).setForceSoftware(true);
((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
updateResources();
@@ -136,6 +135,12 @@
updateResources();
}
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ updateResources();
+ }
+
private void updateResources() {
FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
FontSizeUtils.updateFontSize(mEmergencyOnly, R.dimen.qs_emergency_calls_only_text_size);
@@ -175,6 +180,20 @@
.addFloat(mMultiUserSwitch, "alpha", 0, 1)
.setStartDelay(QSAnimator.EXPANDED_TILE_DELAY)
.build();
+
+ final boolean isRtl = isLayoutRtl();
+ if (isRtl && mDateTimeGroup.getWidth() == 0) {
+ mDateTimeGroup.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ mDateTimeGroup.setPivotX(getWidth());
+ mDateTimeGroup.removeOnLayoutChangeListener(this);
+ }
+ });
+ } else {
+ mDateTimeGroup.setPivotX(isRtl ? mDateTimeGroup.getWidth() : 0);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 95f26d4c..6a2ecf4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -654,7 +654,7 @@
}
@Override
- public void onMultiWindowChanged() {
+ public void onMultiWindowModeChanged() {
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index ab44b6a..84e785d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -654,12 +654,11 @@
}
private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) {
- EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
- UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser());
- if (admin != null && !RestrictedLockUtils.hasBaseUserRestriction(mContext,
- UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser())) {
+ UserHandle user = UserHandle.of(ActivityManager.getCurrentUser());
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)
+ && !mUserManager.hasBaseUserRestriction(UserManager.DISALLOW_ADD_USER, user)) {
record.isDisabledByAdmin = true;
- record.enforcedAdmin = admin;
+ record.enforcedAdmin = EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
} else {
record.isDisabledByAdmin = false;
record.enforcedAdmin = null;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 6976c0b..e3ed92c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -86,7 +86,7 @@
= new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
private final Context mContext;
- private final LayoutInflater mInflater;
+ protected final LayoutInflater mInflater;
private final H mHandler = new H();
private final ZenPrefs mPrefs;
private final TransitionHelper mTransitionHelper = new TransitionHelper();
@@ -95,12 +95,12 @@
private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
- private SegmentedButtons mZenButtons;
+ protected SegmentedButtons mZenButtons;
private View mZenIntroduction;
private TextView mZenIntroductionMessage;
private View mZenIntroductionConfirm;
private TextView mZenIntroductionCustomize;
- private LinearLayout mZenConditions;
+ protected LinearLayout mZenConditions;
private TextView mZenAlarmWarning;
private Callback mCallback;
@@ -148,10 +148,7 @@
mTransitionHelper.dump(fd, pw, args);
}
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
+ protected void createZenButtons() {
mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
mZenButtons.addButton(R.string.interruption_level_none_twoline,
R.string.interruption_level_none_with_warning,
@@ -163,7 +160,12 @@
R.string.interruption_level_priority,
Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
mZenButtons.setCallback(mZenButtonsCallback);
+ }
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ createZenButtons();
mZenIntroduction = findViewById(R.id.zen_introduction);
mZenIntroductionMessage = (TextView) findViewById(R.id.zen_introduction_message);
mSpTexts.add(mZenIntroductionMessage);
@@ -302,14 +304,18 @@
}
}
+ protected void addZenConditions(int count) {
+ for (int i = 0; i < count; i++) {
+ mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
+ }
+ }
+
public void init(ZenModeController controller) {
mController = controller;
mCountdownConditionSupported = mController.isCountdownConditionSupported();
final int countdownDelta = mCountdownConditionSupported ? COUNTDOWN_CONDITION_COUNT : 0;
final int minConditions = 1 /*forever*/ + countdownDelta;
- for (int i = 0; i < minConditions; i++) {
- mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
- }
+ addZenConditions(minConditions);
mSessionZen = getSelectedZen(-1);
handleUpdateManualRule(mController.getManualRule());
if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
@@ -917,7 +923,7 @@
}
}
- private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
+ protected final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
@Override
public void onSelected(final Object value, boolean fromClick) {
if (value != null && mZenButtons.isShown() && isAttachedToWindow()) {
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 5389c804..53a9976 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -23,6 +23,8 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTests.java
index 1d81fd4..e9b85b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTests.java
@@ -14,11 +14,13 @@
package com.android.systemui.qs;
+import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.TouchAnimator.Listener;
import org.mockito.Mockito;
+@SmallTest
public class TouchAnimatorTests extends SysuiTestCase {
private Listener mTouchListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
index f86c6a4..6a81659 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
@@ -32,9 +32,11 @@
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
import android.util.Log;
+@SmallTest
public class TileLifecycleManagerTests extends AndroidTestCase {
public static final String TILE_UPDATE_BROADCAST = "com.android.systemui.tests.TILE_UPDATE";
public static final String EXTRA_CALLBACK = "callback";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
index efdb50d..f24b541 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
@@ -19,10 +19,12 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.service.quicksettings.TileService;
+import android.test.suitebuilder.annotation.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
+@SmallTest
public class TileServiceManagerTests extends SysuiTestCase {
private TileServices mTileServices;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java
index 01514646b..94c98d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java
@@ -17,6 +17,7 @@
import android.content.ComponentName;
import android.os.Looper;
+import android.test.suitebuilder.annotation.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.policy.DataSaverController;
@@ -27,6 +28,7 @@
import java.util.ArrayList;
+@SmallTest
public class TileServicesTests extends SysuiTestCase {
private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2;
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index d6f1499..8e4bf24 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2017,6 +2017,69 @@
// action pass package name of calling package.
ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE = 356;
+ // Logged when a user dismisses all task in overview
+ OVERVIEW_DISMISS_ALL = 357;
+
+ // Quick Settings -> Edit
+ QS_EDIT = 358;
+
+ // Quick Settings -> Edit -> Overflow -> Reset
+ ACTION_QS_EDIT_RESET = 359;
+
+ // QS -> Edit - Drag a tile out of the active tiles.
+ // The _SPEC contains either the spec of the tile or
+ // the package of the 3rd party app in the PKG field.
+ ACTION_QS_EDIT_REMOVE_SPEC = 360;
+ ACTION_QS_EDIT_REMOVE = 361;
+
+ // QS -> Edit - Drag a tile into the active tiles.
+ // The _SPEC contains either the spec of the tile or
+ // the package of the 3rd party app in the PKG field.
+ ACTION_QS_EDIT_ADD_SPEC = 362;
+ ACTION_QS_EDIT_ADD = 363;
+
+ // QS -> Edit - Drag a tile within the active tiles.
+ // The _SPEC contains either the spec of the tile or
+ // the package of the 3rd party app in the PKG field.
+ ACTION_QS_EDIT_MOVE_SPEC = 364;
+ ACTION_QS_EDIT_MOVE = 365;
+
+ // Long-press on a QS tile. Tile spec in package field.
+ ACTION_QS_LONG_PRESS = 366;
+
+ // OPEN: SUW Welcome Screen -> Vision Settings
+ // CATEGORY: SETTINGS
+ // OS: N
+ SUW_ACCESSIBILITY = 367;
+
+ // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification gesture
+ // ACTION: New magnification gesture configuration is chosen
+ // SUBTYPE: 0 is off, 1 is on
+ // CATEGORY: SETTINGS
+ // OS: N
+ SUW_ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION = 368;
+
+ // OPEN: SUW Welcome Screen -> Vision Settings -> Font size
+ // ACTION: New font size is chosen
+ // SUBTYPE: 0 is small, 1 is default, 2 is large, 3 is largest
+ // CATEGORY: SETTINGS
+ // OS: N
+ SUW_ACCESSIBILITY_FONT_SIZE = 369;
+
+ // OPEN: SUW Welcome Screen -> Vision Settings -> Display size
+ // ACTION: New display size is chosen
+ // SUBTYPE: 0 is small, 1 is default, 2 is large, 3 is larger, 4 is largest
+ // CATEGORY: SETTINGS
+ // OS: N
+ SUW_ACCESSIBILITY_DISPLAY_SIZE = 370;
+
+ // OPEN: SUW Welcome Screen -> Vision Settings -> TalkBack
+ // ACTION: New screen reader configuration is chosen
+ // SUBTYPE: 0 is off, 1 is on
+ // CATEGORY: SETTINGS
+ // OS: N
+ SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER = 371;
+
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java
index f2ef065..81f63ae 100644
--- a/rs/java/android/renderscript/Allocation.java
+++ b/rs/java/android/renderscript/Allocation.java
@@ -296,8 +296,13 @@
}
/**
- * Enable/Disable AutoPadding for Vec3 elements.
- * By default: Diabled.
+ * Enable/Disable AutoPadding for Vec3 Elements.
+ *
+ * <p> Vec3 Elements, such as {@link Element#U8_3} are treated as Vec4 Elements
+ * with the fourth vector element used as padding. Enabling the AutoPadding feature
+ * will automatically add/remove the padding when you copy to/from an Allocation
+ * with a Vec3 Element.
+ * <p> By default: Disabled.
*
* @param useAutoPadding True: enable AutoPadding; False: disable AutoPadding
*
diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java
index 2b06780..f6f93cb 100644
--- a/rs/java/android/renderscript/Script.java
+++ b/rs/java/android/renderscript/Script.java
@@ -541,21 +541,22 @@
/**
* Class for specifying the specifics about how a kernel will be
- * launched
+ * launched.
*
* This class can specify a potential range of cells on which to
* run a kernel. If no set is called for a dimension then this
* class will have no impact on that dimension when the kernel
* is executed.
*
- * The forEach launch will operate over the intersection of the
- * dimensions.
+ * The forEach kernel launch will operate over the intersection of
+ * the dimensions.
*
* Example:
* LaunchOptions with setX(5, 15)
* Allocation with dimension X=10, Y=10
- * The resulting forEach run would execute over x = 5 to 10 and
- * y = 0 to 10.
+ * The resulting forEach run would execute over:
+ * x = 5 to 9 (inclusive) and
+ * y = 0 to 9 (inclusive).
*
*
*/
@@ -569,11 +570,11 @@
private int strategy;
/**
- * Set the X range. If the end value is set to 0 the X dimension is not
- * clipped.
+ * Set the X range. xstartArg is the lowest coordinate of the range,
+ * and xendArg-1 is the highest coordinate of the range.
*
* @param xstartArg Must be >= 0
- * @param xendArg Must be >= xstartArg
+ * @param xendArg Must be > xstartArg
*
* @return LaunchOptions
*/
@@ -587,11 +588,11 @@
}
/**
- * Set the Y range. If the end value is set to 0 the Y dimension is not
- * clipped.
+ * Set the Y range. ystartArg is the lowest coordinate of the range,
+ * and yendArg-1 is the highest coordinate of the range.
*
* @param ystartArg Must be >= 0
- * @param yendArg Must be >= ystartArg
+ * @param yendArg Must be > ystartArg
*
* @return LaunchOptions
*/
@@ -605,11 +606,11 @@
}
/**
- * Set the Z range. If the end value is set to 0 the Z dimension is not
- * clipped.
+ * Set the Z range. zstartArg is the lowest coordinate of the range,
+ * and zendArg-1 is the highest coordinate of the range.
*
* @param zstartArg Must be >= 0
- * @param zendArg Must be >= zstartArg
+ * @param zendArg Must be > zstartArg
*
* @return LaunchOptions
*/
diff --git a/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java b/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java
index 76da781..339e0e9 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java
@@ -32,10 +32,9 @@
* Supported elements types are {@link Element#U8}, {@link
* Element#U8_2}, {@link Element#U8_3}, {@link Element#U8_4},
* {@link Element#F32}, {@link Element#F32_2}, {@link
- * Element#F32_3}, and {@link Element#F32_4}
+ * Element#F32_3}, and {@link Element#F32_4}.
*
- * The default coefficients are.
- *
+ * <p> The default coefficients are:
* <code>
* <p> [ 0, 0, 0 ]
* <p> [ 0, 1, 0 ]
@@ -67,7 +66,7 @@
}
/**
- * Set the input of the blur.
+ * Set the input of the 3x3 convolve.
* Must match the element type supplied during create.
*
* @param ain The input allocation.
@@ -80,7 +79,7 @@
/**
* Set the coefficients for the convolve.
*
- * The convolve layout is
+ * <p> The convolve layout is:
* <code>
* <p> [ 0, 1, 2 ]
* <p> [ 3, 4, 5 ]
diff --git a/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java b/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
index 2d37600..a288cee 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
@@ -32,9 +32,9 @@
* Supported elements types are {@link Element#U8}, {@link
* Element#U8_2}, {@link Element#U8_3}, {@link Element#U8_4},
* {@link Element#F32}, {@link Element#F32_2}, {@link
- * Element#F32_3}, and {@link Element#F32_4}
+ * Element#F32_3}, and {@link Element#F32_4}.
*
- * The default coefficients are.
+ * <p> The default coefficients are:
* <code>
* <p> [ 0, 0, 0, 0, 0 ]
* <p> [ 0, 0, 0, 0, 0 ]
@@ -66,7 +66,7 @@
}
/**
- * Set the input of the blur.
+ * Set the input of the 5x5 convolve.
* Must match the element type supplied during create.
*
* @param ain The input allocation.
@@ -79,7 +79,7 @@
/**
* Set the coefficients for the convolve.
*
- * The convolve layout is
+ * <p> The convolve layout is:
* <code>
* <p> [ 0, 1, 2, 3, 4 ]
* <p> [ 5, 6, 7, 8, 9 ]
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e3dac28..4b96e7a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1774,17 +1774,22 @@
private void updateSoftKeyboardShowModeLocked(UserState userState) {
final int userId = userState.mUserId;
- if (userId == mCurrentUserId) {
- // Check whether any Accessibility Services are still enabled and, if not, remove flag
- // requesting no soft keyboard
- final boolean accessibilityRequestingNoIme = userState.mSoftKeyboardShowMode == 1;
- if (accessibilityRequestingNoIme && !userState.isHandlingAccessibilityEvents()) {
- // No active Accessibility Services can be requesting the soft keyboard to be hidden
+ // Only check whether we need to reset the soft keyboard mode if it is not set to the
+ // default.
+ if ((userId == mCurrentUserId) && (userState.mSoftKeyboardShowMode != 0)) {
+ // Check whether the last Accessibility Service that changed the soft keyboard mode to
+ // something other than the default is still enabled and, if not, remove flag and
+ // reset to the default soft keyboard behavior.
+ boolean serviceChangingSoftKeyboardModeIsEnabled =
+ userState.mEnabledServices.contains(userState.mServiceChangingSoftKeyboardMode);
+
+ if (!serviceChangingSoftKeyboardModeIsEnabled) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
0,
userState.mUserId);
userState.mSoftKeyboardShowMode = 0;
+ userState.mServiceChangingSoftKeyboardMode = null;
}
notifySoftKeyboardShowModeChangedLocked(userState.mSoftKeyboardShowMode);
@@ -2966,6 +2971,14 @@
final long identity = Binder.clearCallingIdentity();
try {
+ // Keep track of the last service to request a non-default show mode. The show mode
+ // should be restored to default should this service be disabled.
+ if (showMode == Settings.Secure.SHOW_MODE_AUTO) {
+ userState.mServiceChangingSoftKeyboardMode = null;
+ } else {
+ userState.mServiceChangingSoftKeyboardMode = mComponentName;
+ }
+
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, showMode,
userState.mUserId);
@@ -4115,6 +4128,8 @@
public final Set<ComponentName> mTouchExplorationGrantedServices =
new HashSet<>();
+ public ComponentName mServiceChangingSoftKeyboardMode;
+
public int mLastSentClientState = -1;
public int mSoftKeyboardShowMode = 0;
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 4749417..423f945 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -108,11 +108,12 @@
private static final boolean COMPRESS_TIME = false;
- private static final int EVENT_BUFFER_SIZE = 40;
+ private static final int EVENT_BUFFER_SIZE = 100;
private AlarmManager mAlarmManager;
private IBatteryStats mBatteryStats;
private PowerManagerInternal mLocalPowerManager;
+ private PowerManager mPowerManager;
private AlarmManagerService.LocalService mLocalAlarmManager;
private INetworkPolicyManager mNetworkPolicyManager;
private DisplayManager mDisplayManager;
@@ -168,16 +169,19 @@
private static final int LIGHT_STATE_ACTIVE = 0;
/** Device is inactive (screen off) and we are waiting to for the first light idle. */
private static final int LIGHT_STATE_INACTIVE = 1;
+ /** Device is about to go idle for the first time, wait for current work to complete. */
+ private static final int LIGHT_STATE_PRE_IDLE = 3;
/** Device is in the light idle state, trying to stay asleep as much as possible. */
- private static final int LIGHT_STATE_IDLE = 2;
+ private static final int LIGHT_STATE_IDLE = 4;
/** Device is in the light idle state, but temporarily out of idle to do regular maintenance. */
- private static final int LIGHT_STATE_IDLE_MAINTENANCE = 3;
+ private static final int LIGHT_STATE_IDLE_MAINTENANCE = 5;
/** Device light idle state is overriden, now applying deep doze state. */
- private static final int LIGHT_STATE_OVERRIDE = 4;
+ private static final int LIGHT_STATE_OVERRIDE = 6;
private static String lightStateToString(int state) {
switch (state) {
case LIGHT_STATE_ACTIVE: return "ACTIVE";
case LIGHT_STATE_INACTIVE: return "INACTIVE";
+ case LIGHT_STATE_PRE_IDLE: return "PRE_IDLE";
case LIGHT_STATE_IDLE: return "IDLE";
case LIGHT_STATE_IDLE_MAINTENANCE: return "IDLE_MAINTENANCE";
case LIGHT_STATE_OVERRIDE: return "OVERRIDE";
@@ -192,11 +196,13 @@
private long mNextAlarmTime;
private long mNextIdlePendingDelay;
private long mNextIdleDelay;
+ private long mNextLightIdleDelay;
private long mNextLightAlarmTime;
private long mCurIdleBudget;
private long mMaintenanceStartTime;
private int mActiveIdleOpCount;
+ private PowerManager.WakeLock mActiveIdleWakeLock;
private IBinder mDownloadServiceActive;
private boolean mJobsActive;
private boolean mAlarmsActive;
@@ -343,19 +349,18 @@
}
};
- private final AlarmManager.OnAlarmListener mMaintenanceMinCheckListener
- = new AlarmManager.OnAlarmListener() {
- @Override
- public void onAlarm() {
- synchronized (DeviceIdleController.this) {
- exitMaintenanceEarlyIfNeededLocked();
- }
- }
- };
-
private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
- decActiveIdleOps();
+ // When coming out of a deep idle, we will add in some delay before we allow
+ // the system to settle down and finish the maintenance window. This is
+ // to give a chance for any pending work to be scheduled.
+ if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
+ mHandler.sendEmptyMessageDelayed(MSG_FINISH_IDLE_OP,
+ mConstants.MIN_DEEP_MAINTENANCE_TIME);
+ } else {
+ mHandler.sendEmptyMessageDelayed(MSG_FINISH_IDLE_OP,
+ mConstants.MIN_LIGHT_MAINTENANCE_TIME);
+ }
}
};
@@ -477,7 +482,12 @@
*/
private final class Constants extends ContentObserver {
// Key names stored in the settings value.
+ private static final String KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT
+ = "light_after_inactive_to";
+ private static final String KEY_LIGHT_PRE_IDLE_TIMEOUT = "light_pre_idle_to";
private static final String KEY_LIGHT_IDLE_TIMEOUT = "light_idle_to";
+ private static final String KEY_LIGHT_IDLE_FACTOR = "light_idle_factor";
+ private static final String KEY_LIGHT_MAX_IDLE_TIMEOUT = "light_max_idle_to";
private static final String KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET
= "light_idle_maintenance_min_budget";
private static final String KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET
@@ -505,14 +515,44 @@
"sms_temp_app_whitelist_duration";
/**
- * This is the time, after becoming inactive, that we will start going
- * in to light-weight idle mode.
+ * This is the time, after becoming inactive, that we go in to the first
+ * light-weight idle mode.
+ * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+ * @see #KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT
+ */
+ public long LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT;
+
+ /**
+ * This is amount of time we will wait from the point where we decide we would
+ * like to go idle until we actually do, while waiting for jobs and other current
+ * activity to finish.
+ * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+ * @see #KEY_LIGHT_PRE_IDLE_TIMEOUT
+ */
+ public long LIGHT_PRE_IDLE_TIMEOUT;
+
+ /**
+ * This is the initial time that we will run in idle maintenance mode.
* @see Settings.Global#DEVICE_IDLE_CONSTANTS
* @see #KEY_LIGHT_IDLE_TIMEOUT
*/
public long LIGHT_IDLE_TIMEOUT;
/**
+ * Scaling factor to apply to the light idle mode time each time we complete a cycle.
+ * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+ * @see #KEY_LIGHT_IDLE_FACTOR
+ */
+ public float LIGHT_IDLE_FACTOR;
+
+ /**
+ * This is the maximum time we will run in idle maintenence mode.
+ * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+ * @see #KEY_LIGHT_MAX_IDLE_TIMEOUT
+ */
+ public long LIGHT_MAX_IDLE_TIMEOUT;
+
+ /**
* This is the minimum amount of time we want to make available for maintenance mode
* when lightly idling. That is, we will always have at least this amount of time
* available maintenance before timing out and cutting off maintenance mode.
@@ -716,7 +756,16 @@
Slog.e(TAG, "Bad device idle settings", e);
}
+ LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(
+ KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
+ !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L);
+ LIGHT_PRE_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_PRE_IDLE_TIMEOUT,
+ !COMPRESS_TIME ? 10 * 60 * 1000L : 30 * 1000L);
LIGHT_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_TIMEOUT,
+ !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L);
+ LIGHT_IDLE_FACTOR = mParser.getFloat(KEY_LIGHT_IDLE_FACTOR,
+ 2f);
+ LIGHT_MAX_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_MAX_IDLE_TIMEOUT,
!COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L);
LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mParser.getLong(
KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,
@@ -770,10 +819,26 @@
void dump(PrintWriter pw) {
pw.println(" Settings:");
+ pw.print(" "); pw.print(KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT); pw.print("=");
+ TimeUtils.formatDuration(LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, pw);
+ pw.println();
+
+ pw.print(" "); pw.print(KEY_LIGHT_PRE_IDLE_TIMEOUT); pw.print("=");
+ TimeUtils.formatDuration(LIGHT_PRE_IDLE_TIMEOUT, pw);
+ pw.println();
+
pw.print(" "); pw.print(KEY_LIGHT_IDLE_TIMEOUT); pw.print("=");
TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT, pw);
pw.println();
+ pw.print(" "); pw.print(KEY_LIGHT_IDLE_FACTOR); pw.print("=");
+ pw.print(LIGHT_IDLE_FACTOR);
+ pw.println();
+
+ pw.print(" "); pw.print(KEY_LIGHT_MAX_IDLE_TIMEOUT); pw.print("=");
+ TimeUtils.formatDuration(LIGHT_MAX_IDLE_TIMEOUT, pw);
+ pw.println();
+
pw.print(" "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); pw.print("=");
TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, pw);
pw.println();
@@ -892,6 +957,7 @@
static final int MSG_REPORT_ACTIVE = 5;
static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6;
static final int MSG_REPORT_MAINTENANCE_ACTIVITY = 7;
+ static final int MSG_FINISH_IDLE_OP = 8;
final class MyHandler extends Handler {
MyHandler(Looper looper) {
@@ -996,6 +1062,9 @@
mMaintenanceActivityListeners.finishBroadcast();
}
} break;
+ case MSG_FINISH_IDLE_OP: {
+ decActiveIdleOps();
+ } break;
}
}
}
@@ -1252,6 +1321,10 @@
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
mBatteryStats = BatteryStatsService.getService();
mLocalPowerManager = getLocalService(PowerManagerInternal.class);
+ mPowerManager = getContext().getSystemService(PowerManager.class);
+ mActiveIdleWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "deviceidle_maint");
+ mActiveIdleWakeLock.setReferenceCounted(false);
mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class);
mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
@@ -1640,7 +1713,6 @@
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
mCurIdleBudget = 0;
mMaintenanceStartTime = 0;
- mAlarmManager.cancel(mMaintenanceMinCheckListener);
resetIdleManagementLocked();
resetLightIdleManagementLocked();
addEvent(EVENT_NORMAL);
@@ -1663,7 +1735,7 @@
mLightState = LIGHT_STATE_INACTIVE;
if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
resetLightIdleManagementLocked();
- scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
+ scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
}
}
@@ -1672,6 +1744,7 @@
void resetIdleManagementLocked() {
mNextIdlePendingDelay = 0;
mNextIdleDelay = 0;
+ mNextLightIdleDelay = 0;
cancelAlarmLocked();
cancelLocatingLocked();
stopMonitoringMotionLocked();
@@ -1704,7 +1777,19 @@
switch (mLightState) {
case LIGHT_STATE_INACTIVE:
mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+ // Reset the upcoming idle delays.
+ mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
mMaintenanceStartTime = 0;
+ if (!isOpsInactiveLocked()) {
+ // We have some active ops going on... give them a chance to finish
+ // before going in to our first idle.
+ mLightState = LIGHT_STATE_PRE_IDLE;
+ EventLogTags.writeDeviceIdleLight(mLightState, reason);
+ scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT);
+ break;
+ }
+ // Nothing active, fall through to immediately idle.
+ case LIGHT_STATE_PRE_IDLE:
case LIGHT_STATE_IDLE_MAINTENANCE:
if (mMaintenanceStartTime != 0) {
long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
@@ -1717,17 +1802,22 @@
}
}
mMaintenanceStartTime = 0;
- scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
+ scheduleLightAlarmLocked(mNextLightIdleDelay);
+ mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
+ (long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
+ if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
+ mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
+ }
if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
mLightState = LIGHT_STATE_IDLE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
addEvent(EVENT_LIGHT_IDLE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
- mAlarmManager.cancel(mMaintenanceMinCheckListener);
break;
case LIGHT_STATE_IDLE:
// We have been idling long enough, now it is time to do some work.
mActiveIdleOpCount = 1;
+ mActiveIdleWakeLock.acquire();
mMaintenanceStartTime = SystemClock.elapsedRealtime();
if (mCurIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
@@ -1741,9 +1831,6 @@
EventLogTags.writeDeviceIdleLight(mLightState, reason);
addEvent(EVENT_LIGHT_MAINTENANCE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
- mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
- mMaintenanceStartTime + mConstants.MIN_LIGHT_MAINTENANCE_TIME,
- "DeviceIdleController.maint-check", mMaintenanceMinCheckListener, mHandler);
break;
}
}
@@ -1820,6 +1907,7 @@
cancelAlarmLocked();
cancelLocatingLocked();
mAnyMotionDetector.stop();
+
case STATE_IDLE_MAINTENANCE:
scheduleAlarmLocked(mNextIdleDelay, true);
if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay +
@@ -1827,6 +1915,9 @@
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
+ if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
+ mNextIdleDelay = mConstants.IDLE_TIMEOUT;
+ }
mState = STATE_IDLE;
if (mLightState != LIGHT_STATE_OVERRIDE) {
mLightState = LIGHT_STATE_OVERRIDE;
@@ -1835,24 +1926,24 @@
EventLogTags.writeDeviceIdle(mState, reason);
addEvent(EVENT_DEEP_IDLE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
- mAlarmManager.cancel(mMaintenanceMinCheckListener);
break;
case STATE_IDLE:
// We have been idling long enough, now it is time to do some work.
mActiveIdleOpCount = 1;
+ mActiveIdleWakeLock.acquire();
scheduleAlarmLocked(mNextIdlePendingDelay, false);
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
"Next alarm in " + mNextIdlePendingDelay + " ms.");
mMaintenanceStartTime = SystemClock.elapsedRealtime();
mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
+ if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
+ mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
+ }
mState = STATE_IDLE_MAINTENANCE;
EventLogTags.writeDeviceIdle(mState, reason);
addEvent(EVENT_DEEP_MAINTENANCE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
- mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
- mMaintenanceStartTime + mConstants.MIN_DEEP_MAINTENANCE_TIME,
- "DeviceIdleController.maint-check", mMaintenanceMinCheckListener, mHandler);
break;
}
}
@@ -1868,6 +1959,7 @@
mActiveIdleOpCount--;
if (mActiveIdleOpCount <= 0) {
exitMaintenanceEarlyIfNeededLocked();
+ mActiveIdleWakeLock.release();
}
}
}
@@ -1939,10 +2031,15 @@
mHandler.sendMessage(msg);
}
+ boolean isOpsInactiveLocked() {
+ return mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
+ && !mJobsActive && !mAlarmsActive;
+ }
+
void exitMaintenanceEarlyIfNeededLocked() {
- if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) {
- if (mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
- && !mJobsActive && !mAlarmsActive) {
+ if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE
+ || mLightState == LIGHT_STATE_PRE_IDLE) {
+ if (isOpsInactiveLocked()) {
final long now = SystemClock.elapsedRealtime();
if (DEBUG) {
StringBuilder sb = new StringBuilder();
@@ -1953,13 +2050,11 @@
Slog.d(TAG, sb.toString());
}
if (mState == STATE_IDLE_MAINTENANCE) {
- if (now >= (mMaintenanceStartTime + mConstants.MIN_DEEP_MAINTENANCE_TIME)) {
- stepIdleStateLocked("s:early");
- }
+ stepIdleStateLocked("s:early");
+ } else if (mLightState == LIGHT_STATE_PRE_IDLE) {
+ stepLightIdleStateLocked("s:predone");
} else {
- if (now >= (mMaintenanceStartTime + mConstants.MIN_LIGHT_MAINTENANCE_TIME)) {
- stepLightIdleStateLocked("s:early");
- }
+ stepLightIdleStateLocked("s:early");
}
}
}
@@ -2735,6 +2830,11 @@
TimeUtils.formatDuration(mNextIdleDelay, pw);
pw.println();
}
+ if (mNextLightIdleDelay != 0) {
+ pw.print(" mNextIdleDelay=");
+ TimeUtils.formatDuration(mNextLightIdleDelay, pw);
+ pw.println();
+ }
if (mNextLightAlarmTime != 0) {
pw.print(" mNextLightAlarmTime=");
TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw);
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index e042483..77b3111 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1715,8 +1715,9 @@
// exists in the IME switcher dialog. Might be OK to remove this condition once
// SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live.
return true;
+ } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) {
+ return false;
}
- if ((visibility & InputMethodService.IME_VISIBLE) == 0) return false;
List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
final int N = imis.size();
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 45008dc..994e1d0 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -165,6 +165,7 @@
public void onStart() {
mMountService = new MountService(getContext());
publishBinderService("mount", mMountService);
+ mMountService.start();
}
@Override
@@ -430,9 +431,13 @@
= { "password", "default", "pattern", "pin" };
private final Context mContext;
+
private final NativeDaemonConnector mConnector;
private final NativeDaemonConnector mCryptConnector;
+ private final Thread mConnectorThread;
+ private final Thread mCryptConnectorThread;
+
private volatile boolean mSystemReady = false;
private volatile boolean mBootCompleted = false;
private volatile boolean mDaemonConnected = false;
@@ -1226,7 +1231,8 @@
}
final Intent intent = new Intent(DiskInfo.ACTION_DISK_SCANNED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.id);
intent.putExtra(DiskInfo.EXTRA_VOLUME_COUNT, volumeCount);
mHandler.obtainMessage(H_INTERNAL_BROADCAST, intent).sendToTarget();
@@ -1342,7 +1348,8 @@
intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_STATE, newState);
intent.putExtra(VolumeRecord.EXTRA_FS_UUID, vol.fsUuid);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mHandler.obtainMessage(H_INTERNAL_BROADCAST, intent).sendToTarget();
}
@@ -1494,17 +1501,13 @@
null);
mConnector.setDebug(true);
mConnector.setWarnIfHeld(mLock);
-
- Thread thread = new Thread(mConnector, VOLD_TAG);
- thread.start();
+ mConnectorThread = new Thread(mConnector, VOLD_TAG);
// Reuse parameters from first connector since they are tested and safe
mCryptConnector = new NativeDaemonConnector(this, "cryptd",
MAX_CONTAINERS * 2, CRYPTD_TAG, 25, null);
mCryptConnector.setDebug(true);
-
- Thread crypt_thread = new Thread(mCryptConnector, CRYPTD_TAG);
- crypt_thread.start();
+ mCryptConnectorThread = new Thread(mCryptConnector, CRYPTD_TAG);
final IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_ADDED);
@@ -1521,6 +1524,11 @@
}
}
+ private void start() {
+ mConnectorThread.start();
+ mCryptConnectorThread.start();
+ }
+
private void systemReady() {
mSystemReady = true;
mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 467fc49..42cf42f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -351,7 +351,6 @@
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_RELAUNCH;
-import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT;
@@ -1468,6 +1467,9 @@
static final int FIRST_COMPAT_MODE_MSG = 300;
static final int FIRST_SUPERVISOR_STACK_MSG = 100;
+ static ServiceThread sKillThread = null;
+ static KillHandler sKillHandler = null;
+
CompatModeDialog mCompatModeDialog;
long mLastMemUsageReportTime = 0;
@@ -1488,10 +1490,33 @@
final ServiceThread mHandlerThread;
final MainHandler mHandler;
final UiHandler mUiHandler;
- final ProcessStartLogger mProcessStartLogger;
PackageManagerInternal mPackageManagerInt;
+ final class KillHandler extends Handler {
+ static final int KILL_PROCESS_GROUP_MSG = 4000;
+
+ public KillHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case KILL_PROCESS_GROUP_MSG:
+ {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "killProcessGroup");
+ Process.killProcessGroup(msg.arg1 /* uid */, msg.arg2 /* pid */);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ break;
+
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
final class UiHandler extends Handler {
public UiHandler() {
super(com.android.server.UiThread.get().getLooper(), null, true);
@@ -2460,7 +2485,13 @@
mHandler = new MainHandler(mHandlerThread.getLooper());
mUiHandler = new UiHandler();
- mProcessStartLogger = new ProcessStartLogger();
+ /* static; one-time init here */
+ if (sKillHandler == null) {
+ sKillThread = new ServiceThread(TAG + ":kill",
+ android.os.Process.THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
+ sKillThread.start();
+ sKillHandler = new KillHandler(sKillThread.getLooper());
+ }
mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
"foreground", BROADCAST_FG_TIMEOUT, false);
@@ -3010,9 +3041,13 @@
}
static void killProcessGroup(int uid, int pid) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "killProcessGroup");
- Process.killProcessGroup(uid, pid);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (sKillHandler != null) {
+ sKillHandler.sendMessage(
+ sKillHandler.obtainMessage(KillHandler.KILL_PROCESS_GROUP_MSG, uid, pid));
+ } else {
+ Slog.w(TAG, "Asked to kill process group before system bringup!");
+ Process.killProcessGroup(uid, pid);
+ }
}
final void removeLruProcessLocked(ProcessRecord app) {
@@ -3594,7 +3629,12 @@
app.processName, hostingType,
hostingNameStr != null ? hostingNameStr : "");
- mProcessStartLogger.logIfNeededLocked(app, startResult);
+ try {
+ AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.processName, app.uid,
+ app.info.seinfo, app.info.sourceDir, startResult.pid);
+ } catch (RemoteException ex) {
+ // Ignore
+ }
if (app.persistent) {
Watchdog.getInstance().processStarted(app.processName, startResult.pid);
@@ -6537,8 +6577,6 @@
}
}, dumpheapFilter);
- mProcessStartLogger.registerListener(mContext);
-
// Let system services know.
mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -7182,8 +7220,17 @@
}
}
+ // NOTE: this is an internal method used by the OnShellCommand implementation only and should
+ // be guarded by permission checking.
+ int getUidState(int uid) {
+ synchronized (this) {
+ UidRecord uidRec = mActiveUids.get(uid);
+ return uidRec == null ? ActivityManager.PROCESS_STATE_NONEXISTENT : uidRec.curProcState;
+ }
+ }
+
@Override
- public boolean inMultiWindow(IBinder token) {
+ public boolean isInMultiWindowMode(IBinder token) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
@@ -7200,7 +7247,7 @@
}
@Override
- public boolean inPictureInPicture(IBinder token) {
+ public boolean isInPictureInPictureMode(IBinder token) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
@@ -7216,24 +7263,24 @@
}
@Override
- public void enterPictureInPicture(IBinder token) {
+ public void enterPictureInPictureMode(IBinder token) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
if (!mSupportsPictureInPicture) {
- throw new IllegalStateException("enterPictureInPicture: "
+ throw new IllegalStateException("enterPictureInPictureMode: "
+ "Device doesn't support picture-in-picture mode.");
}
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
- throw new IllegalStateException("enterPictureInPicture: "
+ throw new IllegalStateException("enterPictureInPictureMode: "
+ "Can't find activity for token=" + token);
}
if (!r.supportsPictureInPicture()) {
- throw new IllegalArgumentException("enterPictureInPicture: "
+ throw new IllegalArgumentException("enterPictureInPictureMode: "
+ "Picture-In-Picture not supported for r=" + r);
}
@@ -7242,7 +7289,7 @@
? mDefaultPinnedStackBounds : null;
mStackSupervisor.moveActivityToPinnedStackLocked(
- r, "enterPictureInPicture", bounds);
+ r, "enterPictureInPictureMode", bounds);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -13205,6 +13252,7 @@
}
}
+ @Override
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {
enforceNotIsolatedCaller("getProcessesInErrorState");
// assume our apps are happy - lazy create the list
@@ -13281,6 +13329,7 @@
outInfo.processState = app.curProcState;
}
+ @Override
public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() {
enforceNotIsolatedCaller("getRunningAppProcesses");
@@ -13332,6 +13381,7 @@
return runList;
}
+ @Override
public List<ApplicationInfo> getRunningExternalApplications() {
enforceNotIsolatedCaller("getRunningExternalApplications");
List<ActivityManager.RunningAppProcessInfo> runningApps = getRunningAppProcesses();
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 0253976..d570be9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -16,10 +16,12 @@
package com.android.server.am;
+import android.app.ActivityManager;
import android.app.IActivityManager;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.util.DebugUtils;
import com.android.internal.util.ArrayUtils;
@@ -64,6 +66,8 @@
return runIsUserStopped(pw);
case "lenient-background-check":
return runLenientBackgroundCheck(pw);
+ case "get-uid-state":
+ return getUidState(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -170,6 +174,17 @@
return 0;
}
+ int getUidState(PrintWriter pw) throws RemoteException {
+ mInternal.enforceCallingPermission(android.Manifest.permission.DUMP,
+ "getUidState()");
+ int state = mInternal.getUidState(Integer.parseInt(getNextArgRequired()));
+ pw.print(state);
+ pw.print(" (");
+ pw.printf(DebugUtils.valueToString(ActivityManager.class, "PROCESS_STATE_", state));
+ pw.println(")");
+ return 0;
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -212,7 +227,7 @@
pw.println(" kill [--user <USER_ID> | all | current] <PACKAGE>");
pw.println(" Kill all processes associated with the given application.");
pw.println(" kill-all");
- pw.println(" Kill all processes that are safe to kill (cached, etc)");
+ pw.println(" Kill all processes that are safe to kill (cached, etc).");
pw.println(" write");
pw.println(" Write all pending state to storage.");
pw.println(" track-associations");
@@ -220,9 +235,11 @@
pw.println(" untrack-associations");
pw.println(" Disable and clear association tracking.");
pw.println(" is-user-stopped <USER_ID>");
- pw.println(" returns whether <USER_ID> has been stopped or not");
+ pw.println(" Returns whether <USER_ID> has been stopped or not.");
pw.println(" lenient-background-check [<true|false>]");
- pw.println(" optionally controls lenient background check mode, returns current mode.");
+ pw.println(" Optionally controls lenient background check mode, returns current mode.");
+ pw.println(" get-uid-state <UID>");
+ pw.println(" Gets the process state of an app given its <UID>.");
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 5219827..48f87b6 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -23,7 +23,6 @@
import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SAVED_STATE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_THUMBNAILS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES;
@@ -452,24 +451,24 @@
}
}
- void scheduleMultiWindowChanged() {
+ void scheduleMultiWindowModeChanged() {
if (task == null || task.stack == null || app == null || app.thread == null) {
return;
}
try {
// An activity is considered to be in multi-window mode if its task isn't fullscreen.
- app.thread.scheduleMultiWindowChanged(appToken, !task.mFullscreen);
+ app.thread.scheduleMultiWindowModeChanged(appToken, !task.mFullscreen);
} catch (Exception e) {
// If process died, I don't care.
}
}
- void schedulePictureInPictureChanged() {
+ void schedulePictureInPictureModeChanged() {
if (task == null || task.stack == null || app == null || app.thread == null) {
return;
}
try {
- app.thread.schedulePictureInPictureChanged(
+ app.thread.schedulePictureInPictureModeChanged(
appToken, task.stack.mStackId == PINNED_STACK_ID);
} catch (Exception e) {
// If process died, no one cares.
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index a9ef1d6..b297938 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -4986,7 +4986,7 @@
private void postAddTask(TaskRecord task, ActivityStack prevStack) {
if (prevStack != null) {
- mStackSupervisor.scheduleReportPictureInPictureChangedIfNeeded(task, prevStack);
+ mStackSupervisor.scheduleReportPictureInPictureModeChangedIfNeeded(task, prevStack);
} else if (task.voiceSession != null) {
try {
task.voiceSession.taskStarted(task.intent, task.taskId);
@@ -5045,7 +5045,7 @@
r.setTask(task, null);
task.addActivityToTop(r);
setAppTask(r, task);
- mStackSupervisor.scheduleReportPictureInPictureChangedIfNeeded(task, prevStack);
+ mStackSupervisor.scheduleReportPictureInPictureModeChangedIfNeeded(task, prevStack);
moveToFrontAndResumeStateIfNeeded(r, wasFocused, wasResumed, "moveActivityToStack");
if (wasResumed) {
prevStack.mResumedActivity = null;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 950320e..0d70e99 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -102,7 +102,6 @@
import java.util.Objects;
import java.util.Set;
-import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.START_ANY_ACTIVITY;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
@@ -3524,7 +3523,7 @@
mActivityMetricsLogger.logWindowState();
}
- void scheduleReportMultiWindowChanged(TaskRecord task) {
+ void scheduleReportMultiWindowModeChanged(TaskRecord task) {
for (int i = task.mActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = task.mActivities.get(i);
if (r.app != null && r.app.thread != null) {
@@ -3537,7 +3536,7 @@
}
}
- void scheduleReportPictureInPictureChangedIfNeeded(TaskRecord task, ActivityStack prevStack) {
+ void scheduleReportPictureInPictureModeChangedIfNeeded(TaskRecord task, ActivityStack prevStack) {
final ActivityStack stack = task.stack;
if (prevStack == null || prevStack == stack
|| (prevStack.mStackId != PINNED_STACK_ID && stack.mStackId != PINNED_STACK_ID)) {
@@ -3575,7 +3574,7 @@
synchronized (mService) {
for (int i = mMultiWindowModeChangedActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = mMultiWindowModeChangedActivities.remove(i);
- r.scheduleMultiWindowChanged();
+ r.scheduleMultiWindowModeChanged();
}
}
} break;
@@ -3583,7 +3582,7 @@
synchronized (mService) {
for (int i = mPipModeChangedActivities.size() - 1; i >= 0; i--) {
final ActivityRecord r = mPipModeChangedActivities.remove(i);
- r.schedulePictureInPictureChanged();
+ r.schedulePictureInPictureModeChanged();
}
}
} break;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 0993ce6..93d4060 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -560,7 +560,7 @@
}
EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
Process.killProcessQuiet(pid);
- Process.killProcessGroup(uid, pid);
+ ActivityManagerService.killProcessGroup(uid, pid);
if (!persistent) {
killed = true;
killedByAm = true;
diff --git a/services/core/java/com/android/server/am/ProcessStartLogger.java b/services/core/java/com/android/server/am/ProcessStartLogger.java
deleted file mode 100644
index 39fbeb5..0000000
--- a/services/core/java/com/android/server/am/ProcessStartLogger.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package com.android.server.am;
-
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.app.AppGlobals;
-import android.app.admin.SecurityLog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.Process.ProcessStartResult;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.HashMap;
-
-/**
- * A class that logs process start information (including APK hash) to the security log.
- */
-class ProcessStartLogger {
- private static final String CLASS_NAME = "ProcessStartLogger";
- private static final String TAG = TAG_WITH_CLASS_NAME ? CLASS_NAME : TAG_AM;
-
- final HandlerThread mHandlerProcessLoggingThread;
- Handler mHandlerProcessLogging;
- // Should only access in mHandlerProcessLoggingThread
- final HashMap<String, String> mProcessLoggingApkHashes;
-
- ProcessStartLogger() {
- mHandlerProcessLoggingThread = new HandlerThread(CLASS_NAME,
- Process.THREAD_PRIORITY_BACKGROUND);
- mProcessLoggingApkHashes = new HashMap();
- }
-
- void logIfNeededLocked(ProcessRecord app, ProcessStartResult startResult) {
- if (!SecurityLog.isLoggingEnabled()) {
- return;
- }
- if (!mHandlerProcessLoggingThread.isAlive()) {
- mHandlerProcessLoggingThread.start();
- mHandlerProcessLogging = new Handler(mHandlerProcessLoggingThread.getLooper());
- }
- mHandlerProcessLogging.post(new ProcessLoggingRunnable(app, startResult,
- System.currentTimeMillis()));
- }
-
- void registerListener(Context context) {
- IntentFilter packageChangedFilter = new IntentFilter();
- packageChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- context.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
- || Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
- int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- getSendingUserId());
- String packageName = intent.getData().getSchemeSpecificPart();
- try {
- ApplicationInfo info = AppGlobals.getPackageManager().getApplicationInfo(
- packageName, 0, userHandle);
- invaildateCache(info.sourceDir);
- } catch (RemoteException e) {
- }
- }
- }
- }, packageChangedFilter);
- }
-
- private void invaildateCache(final String apkPath) {
- if (mHandlerProcessLogging != null) {
- mHandlerProcessLogging.post(new Runnable() {
- @Override
- public void run() {
- mProcessLoggingApkHashes.remove(apkPath);
- }
- });
- }
- }
-
- private class ProcessLoggingRunnable implements Runnable {
-
- private final ProcessRecord app;
- private final Process.ProcessStartResult startResult;
- private final long startTimestamp;
-
- public ProcessLoggingRunnable(ProcessRecord app, Process.ProcessStartResult startResult,
- long startTimestamp){
- this.app = app;
- this.startResult = startResult;
- this.startTimestamp = startTimestamp;
- }
-
- @Override
- public void run() {
- String apkHash = computeStringHashOfApk(app);
- SecurityLog.writeEvent(SecurityLog.TAG_APP_PROCESS_START,
- app.processName,
- startTimestamp,
- app.uid,
- startResult.pid,
- app.info.seinfo,
- apkHash);
- }
-
- private String computeStringHashOfApk(ProcessRecord app){
- final String apkFile = app.info.sourceDir;
- if(apkFile == null) {
- return "No APK";
- }
- String apkHash = mProcessLoggingApkHashes.get(apkFile);
- if (apkHash == null) {
- try {
- byte[] hash = computeHashOfApkFile(apkFile);
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < hash.length; i++) {
- sb.append(String.format("%02x", hash[i]));
- }
- apkHash = sb.toString();
- mProcessLoggingApkHashes.put(apkFile, apkHash);
- } catch (IOException | NoSuchAlgorithmException e) {
- Slog.w(TAG, "computeStringHashOfApk() failed", e);
- }
- }
- return apkHash != null ? apkHash : "Failed to count APK hash";
- }
-
- private byte[] computeHashOfApkFile(String packageArchiveLocation)
- throws IOException, NoSuchAlgorithmException {
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- FileInputStream input = new FileInputStream(new File(packageArchiveLocation));
- byte[] buffer = new byte[65536];
- int size;
- while((size = input.read(buffer)) > 0) {
- md.update(buffer, 0, size);
- }
- input.close();
- return md.digest();
- }
- }
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 0f1ebeb..b157070 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1449,7 +1449,7 @@
}
if (mFullscreen != oldFullscreen) {
- mService.mStackSupervisor.scheduleReportMultiWindowChanged(this);
+ mService.mStackSupervisor.scheduleReportMultiWindowModeChanged(this);
}
return !mOverrideConfig.equals(oldConfig) ? mOverrideConfig : null;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c59591e9..aef454e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -434,35 +434,17 @@
stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
stoppingIntent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true);
- final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
- // This is the result receiver for the final shutdown broadcast.
- final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode, String data,
- Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- finishUserStop(uss);
- }
- };
// This is the result receiver for the initial stopping broadcast.
final IIntentReceiver stoppingReceiver = new IIntentReceiver.Stub() {
@Override
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- // On to the next.
- synchronized (mService) {
- if (uss.state != UserState.STATE_STOPPING) {
- // Whoops, we are being started back up. Abort, abort!
- return;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ finishUserStopping(userId, uss);
}
- uss.setState(UserState.STATE_SHUTDOWN);
- }
- mService.mBatteryStatsService.noteEvent(
- BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
- Integer.toString(userId), userId);
- mService.mSystemServiceManager.stopUser(userId);
- mService.broadcastIntentLocked(null, null, shutdownIntent,
- null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
- null, true, false, MY_PID, SYSTEM_UID, userId);
+ });
}
};
// Kick things off.
@@ -476,7 +458,45 @@
}
}
- void finishUserStop(UserState uss) {
+ void finishUserStopping(final int userId, final UserState uss) {
+ // On to the next.
+ final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
+ // This is the result receiver for the final shutdown broadcast.
+ final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ finishUserStopped(uss);
+ }
+ });
+ }
+ };
+
+ synchronized (mService) {
+ if (uss.state != UserState.STATE_STOPPING) {
+ // Whoops, we are being started back up. Abort, abort!
+ return;
+ }
+ uss.setState(UserState.STATE_SHUTDOWN);
+ }
+
+ mService.mBatteryStatsService.noteEvent(
+ BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
+ Integer.toString(userId), userId);
+ mService.mSystemServiceManager.stopUser(userId);
+
+ synchronized (mService) {
+ mService.broadcastIntentLocked(null, null, shutdownIntent,
+ null, shutdownReceiver, 0, null, null, null,
+ AppOpsManager.OP_NONE,
+ null, true, false, MY_PID, SYSTEM_UID, userId);
+ }
+ }
+
+ void finishUserStopped(UserState uss) {
final int userId = uss.mHandle.getIdentifier();
boolean stopped;
ArrayList<IStopUserCallback> callbacks;
@@ -765,10 +785,17 @@
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mService.broadcastIntentLocked(null, null, intent, null,
new IIntentReceiver.Stub() {
+ @Override
public void performReceive(Intent intent, int resultCode,
String data, Bundle extras, boolean ordered,
boolean sticky, int sendingUser) {
- onUserInitialized(uss, foreground, oldUserId, userId);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onUserInitialized(uss, foreground,
+ oldUserId, userId);
+ }
+ });
}
}, 0, null, null, null, AppOpsManager.OP_NONE,
null, true, false, MY_PID, SYSTEM_UID, userId);
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index da9c48a..d10df02 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -32,6 +32,7 @@
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
@@ -40,6 +41,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
+import android.net.NetworkState;
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.wifi.WifiManager;
@@ -88,7 +90,7 @@
*/
public class Tethering extends BaseNetworkObserver {
- private Context mContext;
+ private final Context mContext;
private final static String TAG = "Tethering";
private final static boolean DBG = false;
private final static boolean VDBG = false;
@@ -100,7 +102,7 @@
private Collection<Integer> mUpstreamIfaceTypes;
// used to synchronize public access to members
- private Object mPublicSync;
+ private final Object mPublicSync;
private static final Integer MOBILE_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE);
private static final Integer HIPRI_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_HIPRI);
@@ -112,7 +114,7 @@
private final INetworkManagementService mNMService;
private final INetworkStatsService mStatsService;
- private Looper mLooper;
+ private final Looper mLooper;
private HashMap<String, TetherInterfaceSM> mIfaces; // all tethered/tetherable ifaces
@@ -143,7 +145,9 @@
private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4";
- private StateMachine mTetherMasterSM;
+ private final StateMachine mTetherMasterSM;
+ private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+ private String mCurrentUpstreamIface;
private Notification.Builder mTetheredNotificationBuilder;
private int mLastNotificationId;
@@ -167,6 +171,8 @@
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper);
mTetherMasterSM.start();
+ mUpstreamNetworkMonitor = new UpstreamNetworkMonitor();
+
mStateReceiver = new StateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_STATE);
@@ -505,7 +511,7 @@
};
// The following is necessary to avoid unmarshalling issues when sending the receiver
- // across proccesses.
+ // across processes.
Parcel parcel = Parcel.obtain();
rr.writeToParcel(parcel,0);
parcel.setDataPosition(0);
@@ -559,6 +565,7 @@
}
}
}
+
public int tether(String iface) {
if (DBG) Log.d(TAG, "Tethering " + iface);
TetherInterfaceSM sm = null;
@@ -1371,15 +1378,115 @@
}
+ /**
+ * A NetworkCallback class that relays information of interest to the
+ * tethering master state machine thread for subsequent processing.
+ */
+ class UpstreamNetworkCallback extends NetworkCallback {
+ @Override
+ public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
+ mTetherMasterSM.sendMessage(
+ TetherMasterSM.EVENT_UPSTREAM_LINKPROPERTIES_CHANGED,
+ new NetworkState(null, newLp, null, network, null, null));
+ }
+
+ @Override
+ public void onLost(Network network) {
+ mTetherMasterSM.sendMessage(TetherMasterSM.EVENT_UPSTREAM_LOST, network);
+ }
+ }
+
+ /**
+ * A class to centralize all the network and link properties information
+ * pertaining to the current and any potential upstream network.
+ *
+ * Calling #start() registers two callbacks: one to track the system default
+ * network and a second to specifically observe TYPE_MOBILE_DUN networks.
+ *
+ * The methods and data members of this class are only to be accessed and
+ * modified from the tethering master state machine thread. Any other
+ * access semantics would necessitate the addition of locking.
+ *
+ * TODO: Investigate whether more "upstream-specific" logic/functionality
+ * could/should be moved here.
+ */
+ class UpstreamNetworkMonitor {
+ final HashMap<Network, NetworkState> mNetworkMap = new HashMap();
+ NetworkCallback mDefaultNetworkCallback;
+ NetworkCallback mDunTetheringCallback;
+
+ void start() {
+ stop();
+
+ mDefaultNetworkCallback = new UpstreamNetworkCallback();
+ getConnectivityManager().registerDefaultNetworkCallback(mDefaultNetworkCallback);
+
+ final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+ .build();
+ mDunTetheringCallback = new UpstreamNetworkCallback();
+ getConnectivityManager().registerNetworkCallback(
+ dunTetheringRequest, mDunTetheringCallback);
+ }
+
+ void stop() {
+ if (mDefaultNetworkCallback != null) {
+ getConnectivityManager().unregisterNetworkCallback(mDefaultNetworkCallback);
+ mDefaultNetworkCallback = null;
+ }
+
+ if (mDunTetheringCallback != null) {
+ getConnectivityManager().unregisterNetworkCallback(mDunTetheringCallback);
+ mDunTetheringCallback = null;
+ }
+
+ mNetworkMap.clear();
+ }
+
+ // Returns true if these updated LinkProperties pertain to the current
+ // upstream network interface, false otherwise (or if there is not
+ // currently any upstream tethering interface).
+ boolean processLinkPropertiesChanged(NetworkState networkState) {
+ if (networkState == null ||
+ networkState.network == null ||
+ networkState.linkProperties == null) {
+ return false;
+ }
+
+ mNetworkMap.put(networkState.network, networkState);
+
+ if (mCurrentUpstreamIface != null) {
+ for (String ifname : networkState.linkProperties.getAllInterfaceNames()) {
+ if (mCurrentUpstreamIface.equals(ifname)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void processNetworkLost(Network network) {
+ if (network != null) {
+ mNetworkMap.remove(network);
+ }
+ }
+ }
+
class TetherMasterSM extends StateMachine {
// an interface SM has requested Tethering
- static final int CMD_TETHER_MODE_REQUESTED = 1;
+ static final int CMD_TETHER_MODE_REQUESTED = 1;
// an interface SM has unrequested Tethering
- static final int CMD_TETHER_MODE_UNREQUESTED = 2;
+ static final int CMD_TETHER_MODE_UNREQUESTED = 2;
// upstream connection change - do the right thing
- static final int CMD_UPSTREAM_CHANGED = 3;
+ static final int CMD_UPSTREAM_CHANGED = 3;
// we don't have a valid upstream conn, check again after a delay
- static final int CMD_RETRY_UPSTREAM = 4;
+ static final int CMD_RETRY_UPSTREAM = 4;
+ // Events from NetworkCallbacks that we process on the master state
+ // machine thread on behalf of the UpstreamNetworkMonitor.
+ static final int EVENT_UPSTREAM_LINKPROPERTIES_CHANGED = 5;
+ static final int EVENT_UPSTREAM_LOST = 6;
// This indicates what a timeout event relates to. A state that
// sends itself a delayed timeout event and handles incoming timeout events
@@ -1399,9 +1506,7 @@
private ArrayList<TetherInterfaceSM> mNotifyList;
private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
- private ConnectivityManager.NetworkCallback mMobileUpstreamCallback;
-
- private String mUpstreamIfaceName = null;
+ private NetworkCallback mMobileUpstreamCallback;
private static final int UPSTREAM_SETTLE_TIME_MS = 10000;
@@ -1430,8 +1535,6 @@
}
class TetherMasterUtilState extends State {
- protected final static boolean WAIT_FOR_NETWORK_TO_SETTLE = false;
-
@Override
public boolean processMessage(Message m) {
return false;
@@ -1461,27 +1564,27 @@
return false;
}
- NetworkRequest.Builder builder = new NetworkRequest.Builder()
+ final NetworkRequest.Builder builder = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
if (apnType == ConnectivityManager.TYPE_MOBILE_DUN) {
- builder.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
} else {
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
}
- NetworkRequest mobileUpstreamRequest = builder.build();
- // Other mechanisms notice network and interface changes and act upon them.
- // TODO, imminently: replace with a proper NetworkCallback-based scheme.
- //
+ final NetworkRequest mobileUpstreamRequest = builder.build();
+
+ // The UpstreamNetworkMonitor's callback will be notified.
+ // Therefore, to avoid duplicate notifications, we only register a no-op.
+ mMobileUpstreamCallback = new NetworkCallback();
+
// TODO: Change the timeout from 0 (no onUnavailable callback) to use some
// moderate callback time (once timeout callbacks are implemented). This might
// be useful for updating some UI. Additionally, we should definitely log a
- // message to aid in any subsequent debugging.
- mMobileUpstreamCallback = new ConnectivityManager.NetworkCallback();
+ // message to aid in any subsequent debugging
if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
getConnectivityManager().requestNetwork(
mobileUpstreamRequest, mMobileUpstreamCallback, 0, apnType);
-
return true;
}
@@ -1513,6 +1616,7 @@
}
return true;
}
+
protected boolean turnOffMasterTetherSettings() {
try {
mNMService.stopTethering();
@@ -1606,34 +1710,41 @@
}
if (iface != null) {
- String[] dnsServers = mDefaultDnsServers;
- Collection<InetAddress> dnses = linkProperties.getDnsServers();
- if (dnses != null && !dnses.isEmpty()) {
- // TODO: remove this invocation of NetworkUtils.makeStrings().
- dnsServers = NetworkUtils.makeStrings(dnses);
+ Network network = getConnectivityManager().getNetworkForType(upType);
+ if (network == null) {
+ Log.e(TAG, "No Network for upstream type " + upType + "!");
}
- try {
- Network network = getConnectivityManager().getNetworkForType(upType);
- if (network == null) {
- Log.e(TAG, "No Network for upstream type " + upType + "!");
- }
- if (VDBG) {
- Log.d(TAG, "Setting DNS forwarders: Network=" + network +
- ", dnsServers=" + Arrays.toString(dnsServers));
- }
- mNMService.setDnsForwarders(network, dnsServers);
- } catch (Exception e) {
- Log.e(TAG, "Setting DNS forwarders failed!");
- transitionTo(mSetDnsForwardersErrorState);
- }
+ setDnsForwarders(network, linkProperties);
}
}
notifyTetheredOfNewUpstreamIface(iface);
}
+ protected void setDnsForwarders(final Network network, final LinkProperties lp) {
+ String[] dnsServers = mDefaultDnsServers;
+ final Collection<InetAddress> dnses = lp.getDnsServers();
+ // TODO: Properly support the absence of DNS servers.
+ if (dnses != null && !dnses.isEmpty()) {
+ // TODO: remove this invocation of NetworkUtils.makeStrings().
+ dnsServers = NetworkUtils.makeStrings(dnses);
+ }
+ if (VDBG) {
+ Log.d(TAG, "Setting DNS forwarders: Network=" + network +
+ ", dnsServers=" + Arrays.toString(dnsServers));
+ }
+ try {
+ mNMService.setDnsForwarders(network, dnsServers);
+ } catch (Exception e) {
+ // TODO: Investigate how this can fail and what exactly
+ // happens if/when such failures occur.
+ Log.e(TAG, "Setting DNS forwarders failed!");
+ transitionTo(mSetDnsForwardersErrorState);
+ }
+ }
+
protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
if (DBG) Log.d(TAG, "notifying tethered with iface =" + ifaceName);
- mUpstreamIfaceName = ifaceName;
+ mCurrentUpstreamIface = ifaceName;
for (TetherInterfaceSM sm : mNotifyList) {
sm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
ifaceName);
@@ -1772,20 +1883,23 @@
}
class TetherModeAliveState extends TetherMasterUtilState {
- boolean mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE;
+ boolean mTryCell = true;
@Override
public void enter() {
+ // TODO: examine if we should check the return value.
turnOnMasterTetherSettings(); // may transition us out
startListeningForSimChanges();
+ mUpstreamNetworkMonitor.start();
- mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE; // better try something first pass
- // or crazy tests cases will fail
+ mTryCell = true; // better try something first pass or crazy tests cases will fail
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
}
@Override
public void exit() {
+ // TODO: examine if we should check the return value.
turnOffUpstreamMobileConnection();
+ mUpstreamNetworkMonitor.stop();
stopListeningForSimChanges();
notifyTetheredOfNewUpstreamIface(null);
}
@@ -1799,7 +1913,7 @@
if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
mNotifyList.add(who);
who.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
- mUpstreamIfaceName);
+ mCurrentUpstreamIface);
break;
case CMD_TETHER_MODE_UNREQUESTED:
who = (TetherInterfaceSM)message.obj;
@@ -1823,7 +1937,7 @@
break;
case CMD_UPSTREAM_CHANGED:
// need to try DUN immediately if Wifi goes down
- mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE;
+ mTryCell = true;
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
break;
@@ -1831,6 +1945,24 @@
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
break;
+ case EVENT_UPSTREAM_LINKPROPERTIES_CHANGED:
+ NetworkState state = (NetworkState) message.obj;
+ if (mUpstreamNetworkMonitor.processLinkPropertiesChanged(state)) {
+ setDnsForwarders(state.network, state.linkProperties);
+ } else if (mCurrentUpstreamIface == null) {
+ // If we have no upstream interface, try to run through upstream
+ // selection again. If, for example, IPv4 connectivity has shown up
+ // after IPv6 (e.g., 464xlat became available) we want the chance to
+ // notice and act accordingly.
+ chooseUpstreamType(false);
+ }
+ break;
+ case EVENT_UPSTREAM_LOST:
+ // TODO: Re-evaluate possible upstreams. Currently upstream reevaluation
+ // is triggered via received CONNECTIVITY_ACTION broadcasts that result
+ // in being passed a TetherMasterSM.CMD_UPSTREAM_CHANGED.
+ mUpstreamNetworkMonitor.processNetworkLost((Network) message.obj);
+ break;
default:
retValue = false;
break;
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 28170f2..7f7ea9d 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -26,9 +26,9 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentService;
+import android.content.ISyncStatusObserver;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.ISyncStatusObserver;
import android.content.PeriodicSync;
import android.content.SyncAdapterType;
import android.content.SyncInfo;
@@ -58,18 +58,15 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.security.InvalidParameterException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.Objects;
/**
* {@hide}
@@ -1030,14 +1027,16 @@
if (uri != null) {
for (int i = 0; i < packageCache.size();) {
- final Uri key = packageCache.keyAt(i).second;
- if (Objects.equals(key, uri)) {
+ final Pair<String, Uri> key = packageCache.keyAt(i);
+ if (key.second != null && key.second.toString().startsWith(uri.toString())) {
+ Slog.d(TAG, "Invalidating cache for key " + key);
packageCache.removeAt(i);
} else {
i++;
}
}
} else {
+ Slog.d(TAG, "Invalidating cache for package " + providerPackageName);
packageCache.clear();
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 3c04b78..e73beaa 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -948,7 +948,7 @@
// Must be called on handler.
private void showMissingKeyboardLayoutNotification(InputDevice device) {
if (!mKeyboardLayoutNotificationShown) {
- final Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
+ final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
if (device != null) {
intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, device.getIdentifier());
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index c75e287..2595864 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -49,7 +49,9 @@
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_UNKNOWN;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
@@ -1729,7 +1731,7 @@
if (wlUids.length > 0) {
for (int uid : wlUids) {
- removeRestrictBackgroundWhitelistedUidLocked(uid, false);
+ removeRestrictBackgroundWhitelistedUidLocked(uid, false, false);
}
writePolicy = true;
}
@@ -1896,10 +1898,12 @@
try {
maybeRefreshTrustedTime();
synchronized (mRulesLock) {
- mRestrictBackground = restrictBackground;
- updateRulesForRestrictBackgroundLocked();
- updateNotificationsLocked();
- writePolicyLocked();
+ if (restrictBackground == mRestrictBackground) {
+ // Ideally, UI should never allow this scenario...
+ Slog.w(TAG, "setRestrictBackground: already " + restrictBackground);
+ return;
+ }
+ setRestrictBackgroundLocked(restrictBackground);
}
} finally {
@@ -1910,17 +1914,42 @@
.sendToTarget();
}
+ private void setRestrictBackgroundLocked(boolean restrictBackground) {
+ final boolean oldRestrictBackground = mRestrictBackground;
+ mRestrictBackground = restrictBackground;
+ // Must whitelist foreground apps before turning data saver mode on.
+ // TODO: there is no need to iterate through all apps here, just those in the foreground,
+ // so it could call AM to get the UIDs of such apps, and iterate through them instead.
+ updateRulesForRestrictBackgroundLocked();
+ try {
+ if (!mNetworkManager.setDataSaverModeEnabled(mRestrictBackground)) {
+ Slog.e(TAG, "Could not change Data Saver Mode on NMS to " + mRestrictBackground);
+ mRestrictBackground = oldRestrictBackground;
+ // TODO: if it knew the foreground apps (see TODO above), it could call
+ // updateRulesForRestrictBackgroundLocked() again to restore state.
+ return;
+ }
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
+ }
+ updateNotificationsLocked();
+ writePolicyLocked();
+ }
+
@Override
public void addRestrictBackgroundWhitelistedUid(int uid) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
- if (!isUidValidForRules(uid)) return;
- final boolean changed;
+ final boolean oldStatus;
synchronized (mRulesLock) {
- final boolean oldStatus = mRestrictBackgroundWhitelistUids.get(uid);
+ oldStatus = mRestrictBackgroundWhitelistUids.get(uid);
if (oldStatus) {
if (LOGD) Slog.d(TAG, "uid " + uid + " is already whitelisted");
return;
}
+ if (!isUidValidForWhitelistRules(uid)) {
+ if (LOGD) Slog.d(TAG, "no need to whitelist uid " + uid);
+ return;
+ }
Slog.i(TAG, "adding uid " + uid + " to restrict background whitelist");
mRestrictBackgroundWhitelistUids.append(uid, true);
if (mDefaultRestrictBackgroundWhitelistUids.get(uid)
@@ -1929,13 +1958,10 @@
+ " from revoked restrict background whitelist");
mRestrictBackgroundWhitelistRevokedUids.delete(uid);
}
- changed = mRestrictBackground && !oldStatus;
- if (changed && hasInternetPermissions(uid)) {
- setUidNetworkRules(uid, false);
- }
+ updateRuleForRestrictBackgroundLocked(uid);
writePolicyLocked();
}
- if (changed) {
+ if (mRestrictBackground && !oldStatus) {
mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0)
.sendToTarget();
}
@@ -1944,10 +1970,9 @@
@Override
public void removeRestrictBackgroundWhitelistedUid(int uid) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
- if (!isUidValidForRules(uid)) return;
final boolean changed;
synchronized (mRulesLock) {
- changed = removeRestrictBackgroundWhitelistedUidLocked(uid, true);
+ changed = removeRestrictBackgroundWhitelistedUidLocked(uid, false, true);
}
if (changed) {
mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0)
@@ -1955,14 +1980,22 @@
}
}
- private boolean removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean updateNow) {
+ /**
+ * Removes a uid from the restricted background whitelist, returning whether its current
+ * {@link ConnectivityManager.RestrictBackgroundStatus} changed.
+ */
+ private boolean removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean uidDeleted,
+ boolean updateNow) {
final boolean oldStatus = mRestrictBackgroundWhitelistUids.get(uid);
if (!oldStatus) {
if (LOGD) Slog.d(TAG, "uid " + uid + " was not whitelisted before");
return false;
}
+ if (!uidDeleted && !isUidValidForWhitelistRules(uid)) {
+ if (LOGD) Slog.d(TAG, "no need to remove whitelist for uid " + uid);
+ return false;
+ }
Slog.i(TAG, "removing uid " + uid + " from restrict background whitelist");
- final boolean changed = mRestrictBackground && oldStatus;
mRestrictBackgroundWhitelistUids.delete(uid);
if (mDefaultRestrictBackgroundWhitelistUids.get(uid)
&& !mRestrictBackgroundWhitelistRevokedUids.get(uid)) {
@@ -1970,13 +2003,13 @@
+ " to revoked restrict background whitelist");
mRestrictBackgroundWhitelistRevokedUids.append(uid, true);
}
+ updateRuleForRestrictBackgroundLocked(uid, uidDeleted);
if (updateNow) {
- if (changed && hasInternetPermissions(uid)) {
- setUidNetworkRules(uid, true);
- }
writePolicyLocked();
}
- return changed;
+ // Status only changes if Data Saver is turned on (otherwise it is DISABLED, even if the
+ // app was whitelisted before).
+ return mRestrictBackground;
}
@Override
@@ -2268,7 +2301,12 @@
final int state = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
fout.print(" state=");
fout.print(state);
- fout.print(state <= ActivityManager.PROCESS_STATE_TOP ? " (fg)" : " (bg)");
+ if (state <= ActivityManager.PROCESS_STATE_TOP) {
+ fout.print(" (fg)");
+ } else {
+ fout.print(state <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ ? " (fg svc)" : " (bg)");
+ }
final int rule = mUidRules.get(uid, RULE_UNKNOWN);
fout.print(" rule=");
@@ -2305,6 +2343,11 @@
mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY));
}
+ private boolean isUidForegroundOnRestrictBackgroundLocked(int uid) {
+ final int procState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ return isProcStateAllowedWhileOnRestrictBackgroundLocked(procState);
+ }
+
private boolean isUidStateForegroundLocked(int state) {
// only really in foreground when screen is also on
return mScreenOn && state <= ActivityManager.PROCESS_STATE_TOP;
@@ -2312,7 +2355,7 @@
/**
* Process state of UID changed; if needed, will trigger
- * {@link #updateRestrictDataRulesForUidLocked(int)}.
+ * {@link #updateRuleForRestrictBackgroundLocked(int)}.
*/
private void updateUidStateLocked(int uid, int uidState) {
final int oldUidState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
@@ -2363,8 +2406,10 @@
private void updateRestrictBackgroundRulesOnUidStatusChangedLocked(int uid, int oldUidState,
int newUidState) {
- final boolean oldForeground = oldUidState <= ActivityManager.PROCESS_STATE_TOP;
- final boolean newForeground = newUidState <= ActivityManager.PROCESS_STATE_TOP;
+ final boolean oldForeground =
+ isProcStateAllowedWhileOnRestrictBackgroundLocked(oldUidState);
+ final boolean newForeground =
+ isProcStateAllowedWhileOnRestrictBackgroundLocked(newUidState);
if (oldForeground != newForeground) {
updateRuleForRestrictBackgroundLocked(uid);
}
@@ -2388,7 +2433,7 @@
// only update rules for anyone with foreground activities
final int size = mUidState.size();
for (int i = 0; i < size; i++) {
- if (mUidState.valueAt(i) <= ActivityManager.PROCESS_STATE_TOP) {
+ if (mUidState.valueAt(i) <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
final int uid = mUidState.keyAt(i);
updateRestrictionRulesForUidLocked(uid);
}
@@ -2399,6 +2444,10 @@
return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
}
+ static boolean isProcStateAllowedWhileOnRestrictBackgroundLocked(int procState) {
+ return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ }
+
void updateRulesForRestrictPowerLocked() {
updateRulesForWhitelistedPowerSaveLocked(mRestrictPower, FIREWALL_CHAIN_POWERSAVE,
mUidFirewallPowerSaveRules);
@@ -2492,7 +2541,7 @@
}
void updateRuleForAppIdleLocked(int uid) {
- if (!isUidValidForRules(uid)) return;
+ if (!isUidValidForBlacklistRules(uid)) return;
int appId = UserHandle.getAppId(uid);
if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)) {
@@ -2519,6 +2568,7 @@
updateRulesForAppIdleLocked();
updateRulesForRestrictPowerLocked();
updateRulesForRestrictBackgroundLocked();
+ setRestrictBackgroundLocked(mRestrictBackground);
// If the set of restricted networks may have changed, re-evaluate those.
if (restrictedNetworksChanged) {
@@ -2552,10 +2602,6 @@
updateRuleForRestrictBackgroundLocked(uid);
}
}
-
- // limit data usage for some internal system services
- updateRuleForRestrictBackgroundLocked(android.os.Process.MEDIA_UID);
- updateRuleForRestrictBackgroundLocked(android.os.Process.DRM_UID);
}
private void updateRulesForTempWhitelistChangeLocked() {
@@ -2572,16 +2618,22 @@
}
}
- private boolean isUidValidForRules(int uid) {
- // allow rules on specific system services, and any apps (that have network access)
+ // TODO: the MEDIA / DRM restriction might not be needed anymore, in which case both
+ // methods below could be merged into a isUidValidForRules() method.
+ private boolean isUidValidForBlacklistRules(int uid) {
+ // allow rules on specific system services, and any apps
if (uid == android.os.Process.MEDIA_UID || uid == android.os.Process.DRM_UID
- || (UserHandle.isApp(uid) && hasInternetPermissions(uid))) {
+ || (UserHandle.isApp(uid) && hasInternetPermissions(uid))) {
return true;
}
return false;
}
+ private boolean isUidValidForWhitelistRules(int uid) {
+ return UserHandle.isApp(uid) && hasInternetPermissions(uid);
+ }
+
private boolean isUidIdle(int uid) {
final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
final int userId = UserHandle.getUserId(uid);
@@ -2626,45 +2678,142 @@
updateRuleForRestrictBackgroundLocked(uid);
}
+ /**
+ * Applies network rules to bandwidth controllers based on process state and user-defined
+ * restrictions (blacklist / whitelist).
+ *
+ * <p>
+ * {@code netd} defines 3 firewall chains that govern whether an app has access to metered
+ * networks:
+ * <ul>
+ * <li>@{code bw_penalty_box}: UIDs added to this chain do not have access (blacklist).
+ * <li>@{code bw_happy_box}: UIDs added to this chain have access (whitelist), unless they're
+ * also blacklisted.
+ * <li>@{code bw_data_saver}: when enabled (through {@link #setRestrictBackground(boolean)}),
+ * no UIDs other those whitelisted will have access.
+ * <ul>
+ *
+ * <p>The @{code bw_penalty_box} and @{code bw_happy_box} are primarily managed through the
+ * {@link #setUidPolicy(int, int)} and {@link #addRestrictBackgroundWhitelistedUid(int)} /
+ * {@link #removeRestrictBackgroundWhitelistedUid(int)} methods (for blacklist and whitelist
+ * respectively): these methods set the proper internal state (blacklist / whitelist), then call
+ * this ({@link #updateRuleForRestrictBackgroundLocked(int)}) to propagate the rules to
+ * {@link INetworkManagementService}, but this method should also be called in events (like
+ * Data Saver Mode flips or UID state changes) that might affect the foreground app, since the
+ * following rules should also be applied:
+ *
+ * <ul>
+ * <li>When Data Saver mode is on, the foreground app should be temporarily added to
+ * {@code bw_happy_box} before the @{code bw_data_saver} chain is enabled.
+ * <li>If the foreground app is blacklisted by the user, it should be temporarily removed from
+ * {@code bw_penalty_box}.
+ * <li>When the app leaves foreground state, the temporary changes above should be reverted.
+ * </ul>
+ *
+ * <p>For optimization, the rules are only applied on user apps that have internet access
+ * permission, since there is no need to change the {@code iptables} rule if the app does not
+ * have permission to use the internet.
+ *
+ * <p>The {@link #mUidRules} map is used to define the transtion of states of an UID.
+ */
private void updateRuleForRestrictBackgroundLocked(int uid) {
- if (!isUidValidForRules(uid)) return;
+ updateRuleForRestrictBackgroundLocked(uid, false);
+ }
+
+ /**
+ * Overloaded version of {@link #updateRuleForRestrictBackgroundLocked(int)} called when an
+ * app is removed - it ignores the UID validity check.
+ */
+ private void updateRuleForRestrictBackgroundLocked(int uid, boolean uidDeleted) {
+ if (!uidDeleted && !isUidValidForWhitelistRules(uid)) {
+ if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
+ return;
+ }
final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
- final boolean uidForeground = isUidForegroundLocked(uid);
+ final boolean isForeground = isUidForegroundOnRestrictBackgroundLocked(uid);
+ final boolean isBlacklisted = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
+ final boolean isWhitelisted = mRestrictBackgroundWhitelistUids.get(uid);
- // Derive active rules based on policy and active state
int newRule = RULE_ALLOW_ALL;
+ final int oldRule = mUidRules.get(uid);
- if (!uidForeground) {
- // If the app is not in foreground, reject access if:
- // - app is blacklisted by policy or
- // - data saver mode is and app is not whitelisted
- if (((uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0)
- || (mRestrictBackground && !mRestrictBackgroundWhitelistUids.get(uid))) {
+ // First step: define the new rule based on user restrictions and foreground state.
+ if (isForeground) {
+ if (isBlacklisted || (mRestrictBackground && !isWhitelisted)) {
+ newRule = RULE_TEMPORARY_ALLOW_METERED;
+ }
+ } else {
+ if (isBlacklisted) {
newRule = RULE_REJECT_METERED;
+ } else if (isWhitelisted) {
+ newRule = RULE_ALLOW_METERED;
}
}
- final int oldRule = mUidRules.get(uid);
if (LOGV) {
- Log.v(TAG, "updateRulesForRestrictBackgroundLocked(" + uid + "): oldRule = "
- + ruleToString(oldRule) + ", newRule = " + ruleToString(newRule));
+ Log.v(TAG, "updateRuleForRestrictBackgroundLocked(" + uid + "):"
+ + " isForeground=" +isForeground + ", isBlacklisted: " + isBlacklisted
+ + ", isWhitelisted: " + isWhitelisted + ", newRule: " + ruleToString(newRule)
+ + ", oldRule: " + ruleToString(oldRule));
}
+
if (newRule == RULE_ALLOW_ALL) {
mUidRules.delete(uid);
} else {
mUidRules.put(uid, newRule);
}
- if (oldRule != newRule) {
- final boolean rejectMetered = (newRule == RULE_REJECT_METERED);
- setUidNetworkRules(uid, rejectMetered);
+ // Second step: apply bw changes based on change of state.
+ if (newRule != oldRule) {
+ if (newRule == RULE_TEMPORARY_ALLOW_METERED) {
+ // Temporarily whitelist foreground app, removing from blacklist if necessary
+ // (since bw_penalty_box prevails over bw_happy_box).
+
+ setMeteredNetworkWhitelist(uid, true);
+ // TODO: if statement below is used to avoid an unnecessary call to netd / iptables,
+ // but ideally it should be just:
+ // setMeteredNetworkBlacklist(uid, isBlacklisted);
+ if (isBlacklisted) {
+ setMeteredNetworkBlacklist(uid, false);
+ }
+ } else if (oldRule == RULE_TEMPORARY_ALLOW_METERED) {
+ // Remove temporary whitelist from app that is not on foreground anymore.
+
+ // TODO: if statements below are used to avoid unnecessary calls to netd / iptables,
+ // but ideally they should be just:
+ // setMeteredNetworkWhitelist(uid, isWhitelisted);
+ // setMeteredNetworkBlacklist(uid, isBlacklisted);
+ if (!isWhitelisted) {
+ setMeteredNetworkWhitelist(uid, false);
+ }
+ if (isBlacklisted) {
+ setMeteredNetworkBlacklist(uid, true);
+ }
+ } else if (newRule == RULE_REJECT_METERED || oldRule == RULE_REJECT_METERED) {
+ // Flip state because app was explicitly added or removed to blacklist.
+ setMeteredNetworkBlacklist(uid, isBlacklisted);
+ if (oldRule == RULE_REJECT_METERED && isWhitelisted) {
+ // Since blacklist prevails over whitelist, we need to handle the special case
+ // where app is whitelisted and blacklisted at the same time (although such
+ // scenario should be blocked by the UI), then blacklist is removed.
+ setMeteredNetworkWhitelist(uid, isWhitelisted);
+ }
+ } else if (newRule == RULE_ALLOW_METERED || oldRule == RULE_ALLOW_METERED) {
+ // Flip state because app was explicitly added or removed to whitelist.
+ setMeteredNetworkWhitelist(uid, isWhitelisted);
+ } else {
+ // All scenarios should have been covered above
+ Log.wtf(TAG, "Unexpected change of state for " + uid
+ + ": foreground=" + isForeground + ", whitelisted=" + isWhitelisted
+ + ", blacklisted=" + isBlacklisted + ", newRule="
+ + ruleToString(newRule) + ", oldRule=" + ruleToString(oldRule));
+ }
// dispatch changed rule to existing listeners
mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newRule).sendToTarget();
}
-
}
private class AppIdleStateChangeListener
@@ -2825,11 +2974,23 @@
}
}
- private void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
+ private void setMeteredNetworkBlacklist(int uid, boolean enable) {
+ if (LOGV) Slog.v(TAG, "setMeteredNetworkBlacklist " + uid + ": " + enable);
try {
- mNetworkManager.setUidMeteredNetworkBlacklist(uid, rejectOnQuotaInterfaces);
+ mNetworkManager.setUidMeteredNetworkBlacklist(uid, enable);
} catch (IllegalStateException e) {
- Log.wtf(TAG, "problem setting uid rules", e);
+ Log.wtf(TAG, "problem setting blacklist (" + enable + ") rules for " + uid, e);
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
+ }
+ }
+
+ private void setMeteredNetworkWhitelist(int uid, boolean enable) {
+ if (LOGV) Slog.v(TAG, "setMeteredNetworkWhitelist " + uid + ": " + enable);
+ try {
+ mNetworkManager.setUidMeteredNetworkWhitelist(uid, enable);
+ } catch (IllegalStateException e) {
+ Log.wtf(TAG, "problem setting whitelist (" + enable + ") rules for " + uid, e);
} catch (RemoteException e) {
// ignored; service lives in system_server
}
@@ -3011,7 +3172,7 @@
public void onPackageRemoved(String packageName, int uid) {
if (LOGV) Slog.v(TAG, "onPackageRemoved: " + packageName + " ->" + uid);
synchronized (mRulesLock) {
- removeRestrictBackgroundWhitelistedUidLocked(uid, true);
+ removeRestrictBackgroundWhitelistedUidLocked(uid, true, true);
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0f23fde..e9d721c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1852,10 +1852,16 @@
}
private boolean checkPolicyAccess(String pkg) {
- if (PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission(
- android.Manifest.permission.MANAGE_NOTIFICATIONS, Binder.getCallingUid(),
- -1, true)) {
- return true;
+ try {
+ int uid = getContext().getPackageManager().getPackageUidAsUser(
+ pkg, UserHandle.getCallingUserId());
+ if (PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission(
+ android.Manifest.permission.MANAGE_NOTIFICATIONS, uid,
+ -1, true)) {
+ return true;
+ }
+ } catch (NameNotFoundException e) {
+ return false;
}
return checkPackagePolicyAccess(pkg) || mListeners.isComponentEnabledForPackage(pkg);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d2fd762..926ff37 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -106,6 +106,7 @@
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.IDevicePolicyManager;
+import android.app.admin.SecurityLog;
import android.app.backup.IBackupManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -459,6 +460,8 @@
final PackageHandler mHandler;
+ private final ProcessLoggingHandler mProcessLoggingHandler;
+
/**
* Messages for {@link #mHandler} that need to wait for system ready before
* being dispatched.
@@ -1712,6 +1715,8 @@
// Send installed broadcasts if the install/update is not ephemeral
if (!isEphemeral(res.pkg)) {
+ mProcessLoggingHandler.invalidateProcessLoggingBaseApkHash(res.pkg.baseCodePath);
+
// Send added for users that see the package for the first time
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
extras, 0 /*flags*/, null /*targetPackage*/,
@@ -2096,6 +2101,7 @@
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
+ mProcessLoggingHandler = new ProcessLoggingHandler();
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
File dataDir = Environment.getDataDirectory();
@@ -6920,8 +6926,8 @@
}
@Override
- public void extractPackagesIfNeeded() {
- enforceSystemOrRoot("Only the system can request package extraction");
+ public void updatePackagesIfNeeded() {
+ enforceSystemOrRoot("Only the system can request package update");
// We need to re-extract after an OTA.
boolean causeUpgrade = isUpgrade();
@@ -6952,23 +6958,15 @@
Log.i(TAG, "Extracting app " + curr + " of " + total + ": " + pkg.packageName);
}
- if (!isFirstBoot()) {
- try {
- ActivityManagerNative.getDefault().showBootMessage(
- mContext.getResources().getString(R.string.android_upgrading_apk,
- curr, total), true);
- } catch (RemoteException e) {
- }
- }
-
if (PackageDexOptimizer.canOptimizePackage(pkg)) {
// If the cache was pruned, any compiled odex files will likely be out of date
// and would have to be patched (would be SELF_PATCHOAT, which is deprecated).
// Instead, force the extraction in this case.
- performDexOpt(pkg.packageName, null /* instructionSet */,
- false /* checkProfiles */,
- causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
- causePrunedCache);
+ performDexOpt(pkg.packageName,
+ null /* instructionSet */,
+ false /* checkProfiles */,
+ causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
+ false /* force */);
}
}
}
@@ -7241,9 +7239,9 @@
private void deleteProfilesLI(PackageParser.Package pkg, boolean destroy) {
try {
if (destroy) {
- mInstaller.clearAppProfiles(pkg.packageName);
- } else {
mInstaller.destroyAppProfiles(pkg.packageName);
+ } else {
+ mInstaller.clearAppProfiles(pkg.packageName);
}
} catch (InstallerException ex) {
Log.e(TAG, "Could not delete profiles for package " + pkg.packageName);
@@ -14515,7 +14513,6 @@
if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
res = deletePackageLI(packageName, removeForUser, true, allUsers,
flags | REMOVE_CHATTY, info, true, null);
- deleteProfilesLI(packageName, /*destroy*/ true);
synchronized (mPackages) {
if (res) {
mEphemeralApplicationRegistry.onPackageUninstalledLPw(uninstalledPs.pkg);
@@ -19458,4 +19455,26 @@
return new ArrayList<>(mPackages.values());
}
}
+
+ /**
+ * Logs process start information (including base APK hash) to the security log.
+ * @hide
+ */
+ public void logAppProcessStartIfNeeded(String processName, int uid, String seinfo,
+ String apkFile, int pid) {
+ if (!SecurityLog.isLoggingEnabled()) {
+ return;
+ }
+ Bundle data = new Bundle();
+ data.putLong("startTimestamp", System.currentTimeMillis());
+ data.putString("processName", processName);
+ data.putInt("uid", uid);
+ data.putString("seinfo", seinfo);
+ data.putString("apkFile", apkFile);
+ data.putInt("pid", pid);
+ Message msg = mProcessLoggingHandler.obtainMessage(
+ ProcessLoggingHandler.LOG_APP_PROCESS_START_MSG);
+ msg.setData(data);
+ mProcessLoggingHandler.sendMessage(msg);
+ }
}
diff --git a/services/core/java/com/android/server/pm/ProcessLoggingHandler.java b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java
new file mode 100644
index 0000000..c47dda4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.server.pm;
+
+import android.app.admin.SecurityLog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.os.BackgroundThread;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import android.util.Slog;
+
+public final class ProcessLoggingHandler extends Handler {
+
+ private static final String TAG = "ProcessLoggingHandler";
+ static final int LOG_APP_PROCESS_START_MSG = 1;
+ static final int INVALIDATE_BASE_APK_HASH_MSG = 2;
+
+ private final HashMap<String, String> mProcessLoggingBaseApkHashes = new HashMap();
+
+ ProcessLoggingHandler() {
+ super(BackgroundThread.getHandler().getLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case LOG_APP_PROCESS_START_MSG: {
+ Bundle bundle = msg.getData();
+ String processName = bundle.getString("processName");
+ int uid = bundle.getInt("uid");
+ String seinfo = bundle.getString("seinfo");
+ String apkFile = bundle.getString("apkFile");
+ int pid = bundle.getInt("pid");
+ long startTimestamp = bundle.getLong("startTimestamp");
+ String apkHash = computeStringHashOfApk(apkFile);
+ SecurityLog.writeEvent(SecurityLog.TAG_APP_PROCESS_START, processName,
+ startTimestamp, uid, pid, seinfo, apkHash);
+ break;
+ }
+ case INVALIDATE_BASE_APK_HASH_MSG: {
+ Bundle bundle = msg.getData();
+ mProcessLoggingBaseApkHashes.remove(bundle.getString("apkFile"));
+ break;
+ }
+ }
+ }
+
+ void invalidateProcessLoggingBaseApkHash(String apkPath) {
+ Bundle data = new Bundle();
+ data.putString("apkFile", apkPath);
+ Message msg = obtainMessage(INVALIDATE_BASE_APK_HASH_MSG);
+ msg.setData(data);
+ sendMessage(msg);
+ }
+
+ private String computeStringHashOfApk(String apkFile) {
+ if (apkFile == null) {
+ return "No APK";
+ }
+ String apkHash = mProcessLoggingBaseApkHashes.get(apkFile);
+ if (apkHash == null) {
+ try {
+ byte[] hash = computeHashOfApkFile(apkFile);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < hash.length; i++) {
+ sb.append(String.format("%02x", hash[i]));
+ }
+ apkHash = sb.toString();
+ mProcessLoggingBaseApkHashes.put(apkFile, apkHash);
+ } catch (IOException | NoSuchAlgorithmException e) {
+ Slog.w(TAG, "computeStringHashOfApk() failed", e);
+ }
+ }
+ return apkHash != null ? apkHash : "Failed to count APK hash";
+ }
+
+ private byte[] computeHashOfApkFile(String packageArchiveLocation)
+ throws IOException, NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ FileInputStream input = new FileInputStream(new File(packageArchiveLocation));
+ byte[] buffer = new byte[65536];
+ int size;
+ while ((size = input.read(buffer)) > 0) {
+ md.update(buffer, 0, size);
+ }
+ input.close();
+ return md.digest();
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index b759e16..7699f30 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -32,7 +32,7 @@
/**
* Launcher information used by {@link ShortcutService}.
*/
-class ShortcutLauncher implements ShortcutPackageItem {
+class ShortcutLauncher extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
static final String TAG_ROOT = "launcher-pins";
@@ -44,44 +44,34 @@
private static final String ATTR_VALUE = "value";
private static final String ATTR_PACKAGE_NAME = "package-name";
- @UserIdInt
- private final int mUserId;
-
- @NonNull
- private final String mPackageName;
-
- @UserIdInt
- private final int mLauncherUserId;
+ private final int mOwnerUserId;
/**
* Package name -> IDs.
*/
final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
- ShortcutLauncher(@UserIdInt int userId, @NonNull String packageName,
+ public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
+ @UserIdInt int launcherUserId, ShortcutPackageInfo spi) {
+ super(launcherUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
+ mOwnerUserId = ownerUserId;
+ }
+
+ public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
@UserIdInt int launcherUserId) {
- mUserId = userId;
- mPackageName = packageName;
- mLauncherUserId = launcherUserId;
+ this(launcherUserId, packageName, launcherUserId, null);
}
- @UserIdInt
- public int getUserId() {
- return mUserId;
+ @Override
+ public int getOwnerUserId() {
+ return mOwnerUserId;
}
- @UserIdInt
- public int getLauncherUserId() {
- return mLauncherUserId;
- }
+ public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId,
+ @NonNull String packageName, @NonNull List<String> ids) {
+ final ShortcutPackage packageShortcuts =
+ s.getPackageShortcutsLocked(packageName, packageUserId);
- @NonNull
- public String getPackageName() {
- return mPackageName;
- }
-
- public void pinShortcuts(@NonNull ShortcutService s, @NonNull String packageName,
- @NonNull List<String> ids) {
final int idSize = ids.size();
if (idSize == 0) {
mPinnedShortcuts.remove(packageName);
@@ -91,8 +81,6 @@
// Pin shortcuts. Make sure only pin the ones that were visible to the caller.
// i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
- final ShortcutPackage packageShortcuts =
- s.getPackageShortcutsLocked(packageName, mUserId);
final ArraySet<String> newSet = new ArraySet<>();
for (int i = 0; i < idSize; i++) {
@@ -107,7 +95,7 @@
}
mPinnedShortcuts.put(packageName, newSet);
}
- s.getPackageShortcutsLocked(packageName, mUserId).refreshPinnedFlags(s);
+ packageShortcuts.refreshPinnedFlags(s);
}
/**
@@ -124,15 +112,18 @@
/**
* Persist.
*/
- public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException {
+ @Override
+ public void saveToXml(XmlSerializer out, boolean forBackup)
+ throws IOException {
final int size = mPinnedShortcuts.size();
if (size == 0) {
return; // Nothing to write.
}
out.startTag(null, TAG_ROOT);
- ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, mPackageName);
- ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, mLauncherUserId);
+ ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName());
+ ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId());
+ getPackageInfo().saveToXml(out);
for (int i = 0; i < size; i++) {
out.startTag(null, TAG_PACKAGE);
@@ -153,16 +144,21 @@
/**
* Load.
*/
- public static ShortcutLauncher loadFromXml(XmlPullParser parser, int ownerUserId)
- throws IOException, XmlPullParserException {
+ public static ShortcutLauncher loadFromXml(XmlPullParser parser, int ownerUserId,
+ boolean fromBackup) throws IOException, XmlPullParserException {
final String launcherPackageName = ShortcutService.parseStringAttribute(parser,
ATTR_PACKAGE_NAME);
- final int launcherUserId = ShortcutService.parseIntAttribute(parser,
- ATTR_LAUNCHER_USER_ID, ownerUserId);
+
+ // If restoring, just use the real user ID.
+ final int launcherUserId =
+ fromBackup ? ownerUserId
+ : ShortcutService.parseIntAttribute(parser, ATTR_LAUNCHER_USER_ID, ownerUserId);
final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName,
launcherUserId);
+ ShortcutPackageInfo spi = null;
+
ArraySet<String> ids = null;
final int outerDepth = parser.getDepth();
int type;
@@ -173,21 +169,33 @@
}
final int depth = parser.getDepth();
final String tag = parser.getName();
- switch (tag) {
- case TAG_PACKAGE: {
- final String packageName = ShortcutService.parseStringAttribute(parser,
- ATTR_PACKAGE_NAME);
- ids = new ArraySet<>();
- ret.mPinnedShortcuts.put(packageName, ids);
- continue;
- }
- case TAG_PIN: {
- ids.add(ShortcutService.parseStringAttribute(parser,
- ATTR_VALUE));
- continue;
+ if (depth == outerDepth + 1) {
+ switch (tag) {
+ case ShortcutPackageInfo.TAG_ROOT:
+ spi = ShortcutPackageInfo.loadFromXml(parser);
+ continue;
+ case TAG_PACKAGE: {
+ final String packageName = ShortcutService.parseStringAttribute(parser,
+ ATTR_PACKAGE_NAME);
+ ids = new ArraySet<>();
+ ret.mPinnedShortcuts.put(packageName, ids);
+ continue;
+ }
}
}
- throw ShortcutService.throwForInvalidTag(depth, tag);
+ if (depth == outerDepth + 2) {
+ switch (tag) {
+ case TAG_PIN: {
+ ids.add(ShortcutService.parseStringAttribute(parser,
+ ATTR_VALUE));
+ continue;
+ }
+ }
+ }
+ ShortcutService.warnForInvalidTag(depth, tag);
+ }
+ if (spi != null) {
+ ret.replacePackageInfo(spi);
}
return ret;
}
@@ -197,9 +205,12 @@
pw.print(prefix);
pw.print("Launcher: ");
- pw.print(mPackageName);
- pw.print(" UserId: ");
- pw.print(mLauncherUserId);
+ pw.print(getPackageName());
+ pw.print(" Package user: ");
+ pw.print(getPackageUserId());
+ pw.println();
+
+ getPackageInfo().dump(s, pw, prefix + " ");
pw.println();
final int size = mPinnedShortcuts.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index e4d5787..5916202 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -17,7 +17,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
@@ -41,7 +40,7 @@
/**
* Package information used by {@link ShortcutService}.
*/
-class ShortcutPackage implements ShortcutPackageItem {
+class ShortcutPackage extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
static final String TAG_ROOT = "package";
@@ -56,6 +55,7 @@
private static final String ATTR_ID = "id";
private static final String ATTR_ACTIVITY = "activity";
private static final String ATTR_TITLE = "title";
+ private static final String ATTR_TEXT = "text";
private static final String ATTR_INTENT = "intent";
private static final String ATTR_WEIGHT = "weight";
private static final String ATTR_TIMESTAMP = "timestamp";
@@ -63,12 +63,6 @@
private static final String ATTR_ICON_RES = "icon-res";
private static final String ATTR_BITMAP_PATH = "bitmap-path";
- @UserIdInt
- private final int mUserId;
-
- @NonNull
- private final String mPackageName;
-
/**
* All the shortcuts from the package, keyed on IDs.
*/
@@ -89,19 +83,18 @@
*/
private long mLastResetTime;
- ShortcutPackage(int userId, String packageName) {
- mUserId = userId;
- mPackageName = packageName;
+ public ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
+ super(packageUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
}
- @UserIdInt
- public int getUserId() {
- return mUserId;
+ public ShortcutPackage(int packageUserId, String packageName) {
+ this(packageUserId, packageName, null);
}
- @NonNull
- public String getPackageName() {
- return mPackageName;
+ @Override
+ public int getOwnerUserId() {
+ // For packages, always owner user == package user.
+ return getPackageUserId();
}
/**
@@ -116,7 +109,7 @@
@NonNull String id) {
final ShortcutInfo shortcut = mShortcuts.remove(id);
if (shortcut != null) {
- s.removeIcon(mUserId, shortcut);
+ s.removeIcon(getPackageUserId(), shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
}
return shortcut;
@@ -124,7 +117,7 @@
void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
deleteShortcut(s, newShortcut.getId());
- s.saveIconAndFixUpShortcut(mUserId, newShortcut);
+ s.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
mShortcuts.put(newShortcut.getId(), newShortcut);
}
@@ -233,11 +226,12 @@
// Then, for the pinned set for each launcher, set the pin flag one by one.
final ArrayMap<ShortcutUser.PackageWithUser, ShortcutLauncher> launchers =
- s.getUserShortcutsLocked(mUserId).getAllLaunchers();
+ s.getUserShortcutsLocked(getPackageUserId()).getAllLaunchers();
for (int l = launchers.size() - 1; l >= 0; l--) {
final ShortcutLauncher launcherShortcuts = launchers.valueAt(l);
- final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(mPackageName);
+ final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
+ getPackageName());
if (pinned == null || pinned.size() == 0) {
continue;
@@ -321,8 +315,8 @@
// Set of pinned shortcuts by the calling launcher.
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
- : s.getLauncherShortcuts(callingLauncher, mUserId, launcherUserId)
- .getPinnedShortcutIds(mPackageName);
+ : s.getLauncherShortcuts(callingLauncher, getPackageUserId(), launcherUserId)
+ .getPinnedShortcutIds(getPackageName());
for (int i = 0; i < mShortcuts.size(); i++) {
final ShortcutInfo si = mShortcuts.valueAt(i);
@@ -362,7 +356,7 @@
pw.print(prefix);
pw.print("Package: ");
- pw.print(mPackageName);
+ pw.print(getPackageName());
pw.println();
pw.print(prefix);
@@ -380,6 +374,9 @@
pw.print(s.formatTime(mLastResetTime));
pw.println();
+ getPackageInfo().dump(s, pw, prefix + " ");
+ pw.println();
+
pw.println(" Shortcuts:");
long totalBitmapSize = 0;
final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
@@ -406,6 +403,7 @@
pw.println(")");
}
+ @Override
public void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
final int size = mShortcuts.size();
@@ -416,10 +414,11 @@
out.startTag(null, TAG_ROOT);
- ShortcutService.writeAttr(out, ATTR_NAME, mPackageName);
+ ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
+ getPackageInfo().saveToXml(out);
for (int j = 0; j < size; j++) {
saveShortcut(out, mShortcuts.valueAt(j), forBackup);
@@ -441,6 +440,7 @@
ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent());
// writeAttr(out, "icon", si.getIcon()); // We don't save it.
ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
+ ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight());
ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
@@ -464,13 +464,14 @@
out.endTag(null, TAG_SHORTCUT);
}
- public static ShortcutPackage loadFromXml(XmlPullParser parser, int userId)
+ public static ShortcutPackage loadFromXml(ShortcutService s, XmlPullParser parser,
+ int ownerUserId, boolean fromBackup)
throws IOException, XmlPullParserException {
final String packageName = ShortcutService.parseStringAttribute(parser,
ATTR_NAME);
- final ShortcutPackage ret = new ShortcutPackage(userId, packageName);
+ final ShortcutPackage ret = new ShortcutPackage(ownerUserId, packageName);
ret.mDynamicShortcutCount =
ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
@@ -478,6 +479,7 @@
ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
ret.mLastResetTime =
ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
+ ShortcutPackageInfo spi = null;
final int outerDepth = parser.getDepth();
int type;
@@ -488,15 +490,23 @@
}
final int depth = parser.getDepth();
final String tag = parser.getName();
- switch (tag) {
- case TAG_SHORTCUT:
- final ShortcutInfo si = parseShortcut(parser, packageName);
+ if (depth == outerDepth + 1) {
+ switch (tag) {
+ case ShortcutPackageInfo.TAG_ROOT:
+ spi = ShortcutPackageInfo.loadFromXml(parser);
+ continue;
+ case TAG_SHORTCUT:
+ final ShortcutInfo si = parseShortcut(parser, packageName);
- // Don't use addShortcut(), we don't need to save the icon.
- ret.mShortcuts.put(si.getId(), si);
- continue;
+ // Don't use addShortcut(), we don't need to save the icon.
+ ret.mShortcuts.put(si.getId(), si);
+ continue;
+ }
}
- throw ShortcutService.throwForInvalidTag(depth, tag);
+ ShortcutService.warnForInvalidTag(depth, tag);
+ }
+ if (spi != null) {
+ ret.replacePackageInfo(spi);
}
return ret;
}
@@ -507,6 +517,7 @@
ComponentName activityComponent;
// Icon icon;
String title;
+ String text;
Intent intent;
PersistableBundle intentPersistableExtras = null;
int weight;
@@ -520,10 +531,10 @@
activityComponent = ShortcutService.parseComponentNameAttribute(parser,
ATTR_ACTIVITY);
title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
+ text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT);
weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT);
- lastChangedTimestamp = (int) ShortcutService.parseLongAttribute(parser,
- ATTR_TIMESTAMP);
+ lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES);
bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH);
@@ -552,7 +563,7 @@
throw ShortcutService.throwForInvalidTag(depth, tag);
}
return new ShortcutInfo(
- id, packageName, activityComponent, /* icon =*/ null, title, intent,
+ id, packageName, activityComponent, /* icon =*/ null, title, text, intent,
intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
iconRes, bitmapPath);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index ab45689..5f706b8 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -15,14 +15,11 @@
*/
package com.android.server.pm;
-import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
-import android.content.pm.Signature;
import android.util.Slog;
-import com.android.internal.util.Preconditions;
+import com.android.server.backup.BackupUtils;
import libcore.io.Base64;
import libcore.util.HexEncoding;
@@ -33,32 +30,21 @@
import java.io.IOException;
import java.io.PrintWriter;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
-import java.util.Arrays;
/**
* Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
- *
- * TODO: The methods about signature hashes are copied from BackupManagerService, which is not
- * visible here. Unify the code.
*/
-class ShortcutPackageInfo implements ShortcutPackageItem {
+class ShortcutPackageInfo {
private static final String TAG = ShortcutService.TAG;
static final String TAG_ROOT = "package-info";
- private static final String ATTR_USER_ID = "user";
- private static final String ATTR_NAME = "name";
private static final String ATTR_VERSION = "version";
private static final String ATTR_SHADOW = "shadow";
private static final String TAG_SIGNATURE = "signature";
private static final String ATTR_SIGNATURE_HASH = "hash";
- private final String mPackageName;
- private final int mUserId;
-
/**
* When true, this package information was restored from the previous device, and the app hasn't
* been installed yet.
@@ -67,22 +53,14 @@
private int mVersionCode;
private ArrayList<byte[]> mSigHashes;
- private ShortcutPackageInfo(String packageName, int userId,
- int versionCode, ArrayList<byte[]> sigHashes, boolean isShadow) {
- mPackageName = Preconditions.checkNotNull(packageName);
- mUserId = userId;
+ private ShortcutPackageInfo(int versionCode, ArrayList<byte[]> sigHashes, boolean isShadow) {
mVersionCode = versionCode;
mIsShadow = isShadow;
mSigHashes = sigHashes;
}
- @NonNull
- public String getPackageName() {
- return mPackageName;
- }
-
- public int getUserId() {
- return mUserId;
+ public static ShortcutPackageInfo newEmpty() {
+ return new ShortcutPackageInfo(0, new ArrayList<>(0), /* isShadow */ false);
}
public boolean isShadow() {
@@ -101,92 +79,13 @@
return mVersionCode;
}
- private static byte[] hashSignature(Signature sig) {
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- digest.update(sig.toByteArray());
- return digest.digest();
- } catch (NoSuchAlgorithmException e) {
- Slog.w(TAG, "No SHA-256 algorithm found!");
- }
- return null;
- }
-
- private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) {
- if (sigs == null) {
- return null;
- }
-
- ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length);
- for (Signature s : sigs) {
- hashes.add(hashSignature(s));
- }
- return hashes;
- }
-
- private static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) {
- if (target == null) {
- return false;
- }
-
- // If the target resides on the system partition, we allow it to restore
- // data from the like-named package in a restore set even if the signatures
- // do not match. (Unlike general applications, those flashed to the system
- // partition will be signed with the device's platform certificate, so on
- // different phones the same system app will have different signatures.)
- if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return true;
- }
-
- // Allow unsigned apps, but not signed on one device and unsigned on the other
- // !!! TODO: is this the right policy?
- Signature[] deviceSigs = target.signatures;
- if ((storedSigHashes == null || storedSigHashes.size() == 0)
- && (deviceSigs == null || deviceSigs.length == 0)) {
- return true;
- }
- if (storedSigHashes == null || deviceSigs == null) {
- return false;
- }
-
- // !!! TODO: this demands that every stored signature match one
- // that is present on device, and does not demand the converse.
- // Is this this right policy?
- final int nStored = storedSigHashes.size();
- final int nDevice = deviceSigs.length;
-
- // hash each on-device signature
- ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice);
- for (int i = 0; i < nDevice; i++) {
- deviceHashes.add(hashSignature(deviceSigs[i]));
- }
-
- // now ensure that each stored sig (hash) matches an on-device sig (hash)
- for (int n = 0; n < nStored; n++) {
- boolean match = false;
- final byte[] storedHash = storedSigHashes.get(n);
- for (int i = 0; i < nDevice; i++) {
- if (Arrays.equals(storedHash, deviceHashes.get(i))) {
- match = true;
- break;
- }
- }
- // match is false when no on-device sig matched one of the stored ones
- if (!match) {
- return false;
- }
- }
-
- return true;
- }
-
public boolean canRestoreTo(PackageInfo target) {
if (target.versionCode < mVersionCode) {
Slog.w(TAG, String.format("Package current version %d < backed up version %d",
target.versionCode, mVersionCode));
return false;
}
- if (!signaturesMatch(mSigHashes, target)) {
+ if (!BackupUtils.signaturesMatch(mSigHashes, target)) {
Slog.w(TAG, "Package signature mismtach");
return false;
}
@@ -194,37 +93,34 @@
}
public static ShortcutPackageInfo generateForInstalledPackage(
- ShortcutService s, String packageName, @UserIdInt int userId) {
- final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, userId);
+ ShortcutService s, String packageName, @UserIdInt int packageUserId) {
+ final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, packageUserId);
if (pi.signatures == null || pi.signatures.length == 0) {
Slog.e(TAG, "Can't get signatures: package=" + packageName);
return null;
}
- final ShortcutPackageInfo ret = new ShortcutPackageInfo(packageName, userId, pi.versionCode,
- hashSignatureArray(pi.signatures), /* shadow=*/ false);
+ final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode,
+ BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
return ret;
}
- public void refreshAndSave(ShortcutService s, @UserIdInt int userId) {
- final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, userId);
+ public void refresh(ShortcutService s, ShortcutPackageItem pkg) {
+ // Note use mUserId here, rather than userId.
+ final PackageInfo pi = s.getPackageInfoWithSignatures(
+ pkg.getPackageName(), pkg.getPackageUserId());
if (pi == null) {
- Slog.w(TAG, "Package not found: " + mPackageName);
+ Slog.w(TAG, "Package not found: " + pkg.getPackageName());
return;
}
mVersionCode = pi.versionCode;
- mSigHashes = hashSignatureArray(pi.signatures);
-
- s.scheduleSaveUser(userId);
+ mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
}
- public void saveToXml(XmlSerializer out, boolean forBackup)
- throws IOException, XmlPullParserException {
+ public void saveToXml(XmlSerializer out) throws IOException {
out.startTag(null, TAG_ROOT);
- ShortcutService.writeAttr(out, ATTR_NAME, mPackageName);
- ShortcutService.writeAttr(out, ATTR_USER_ID, mUserId);
ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
@@ -236,11 +132,9 @@
out.endTag(null, TAG_ROOT);
}
- public static ShortcutPackageInfo loadFromXml(XmlPullParser parser, int ownerUserId)
+ public static ShortcutPackageInfo loadFromXml(XmlPullParser parser)
throws IOException, XmlPullParserException {
- final String packageName = ShortcutService.parseStringAttribute(parser, ATTR_NAME);
- final int userId = ShortcutService.parseIntAttribute(parser, ATTR_USER_ID, ownerUserId);
final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
@@ -256,31 +150,27 @@
}
final int depth = parser.getDepth();
final String tag = parser.getName();
- switch (tag) {
- case TAG_SIGNATURE: {
- final String hash = ShortcutService.parseStringAttribute(
- parser, ATTR_SIGNATURE_HASH);
- hashes.add(Base64.decode(hash.getBytes()));
- continue;
+
+ if (depth == outerDepth + 1) {
+ switch (tag) {
+ case TAG_SIGNATURE: {
+ final String hash = ShortcutService.parseStringAttribute(
+ parser, ATTR_SIGNATURE_HASH);
+ hashes.add(Base64.decode(hash.getBytes()));
+ continue;
+ }
}
}
- throw ShortcutService.throwForInvalidTag(depth, tag);
+ ShortcutService.warnForInvalidTag(depth, tag);
}
- return new ShortcutPackageInfo(packageName, userId, versionCode, hashes, shadow);
+ return new ShortcutPackageInfo(versionCode, hashes, shadow);
}
public void dump(ShortcutService s, PrintWriter pw, String prefix) {
pw.println();
pw.print(prefix);
- pw.print("PackageInfo: ");
- pw.print(mPackageName);
- pw.println();
-
- pw.print(prefix);
- pw.print(" User: ");
- pw.print(mUserId);
- pw.println();
+ pw.println("PackageInfo:");
pw.print(prefix);
pw.print(" IsShadow: ");
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 526c84d..de2709d 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -17,15 +17,69 @@
import android.annotation.NonNull;
+import com.android.internal.util.Preconditions;
+
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
-public interface ShortcutPackageItem {
- @NonNull
- String getPackageName();
+abstract class ShortcutPackageItem {
+ private final int mPackageUserId;
+ private final String mPackageName;
- void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
+ private ShortcutPackageInfo mPackageInfo;
+
+ protected ShortcutPackageItem(int packageUserId, @NonNull String packageName,
+ @NonNull ShortcutPackageInfo packageInfo) {
+ mPackageUserId = packageUserId;
+ mPackageName = Preconditions.checkStringNotEmpty(packageName);
+ mPackageInfo = Preconditions.checkNotNull(packageInfo);
+ }
+
+ /**
+ * ID of the user who actually has this package running on. For {@link ShortcutPackage},
+ * this is the same thing as {@link #getOwnerUserId}, but if it's a {@link ShortcutLauncher} and
+ * {@link #getOwnerUserId} is of a work profile, then this ID could be the user who owns the
+ * profile.
+ */
+ public int getPackageUserId() {
+ return mPackageUserId;
+ }
+
+ /**
+ * ID of the user who sees the shortcuts from this instance.
+ */
+ public abstract int getOwnerUserId();
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public ShortcutPackageInfo getPackageInfo() {
+ return mPackageInfo;
+ }
+
+ /**
+ * Should be only used when loading from a file.o
+ */
+ protected void replacePackageInfo(@NonNull ShortcutPackageInfo packageInfo) {
+ mPackageInfo = Preconditions.checkNotNull(packageInfo);
+ }
+
+ public void refreshPackageInfoAndSave(ShortcutService s) {
+ mPackageInfo.refresh(s, this);
+ s.scheduleSaveUser(getOwnerUserId());
+ }
+
+ public void ensureNotShadowAndSave(ShortcutService s) {
+ if (mPackageInfo.isShadow()) {
+ mPackageInfo.setShadow(false);
+ s.scheduleSaveUser(getOwnerUserId());
+ }
+ }
+
+ public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index cf11025..76a2dfa 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -72,7 +72,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
@@ -85,6 +84,10 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -92,6 +95,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
@@ -104,8 +108,6 @@
*
* - Default launcher check does take a few ms. Worth caching.
*
- * - Don't backup launcher from different profile.
- *
* - Clear data -> remove all dynamic? but not the pinned?
*
* - Scan and remove orphan bitmaps (just in case).
@@ -633,31 +635,45 @@
}
path.mkdirs();
final AtomicFile file = new AtomicFile(path);
- FileOutputStream outs = null;
+ FileOutputStream os = null;
try {
- outs = file.startWrite();
+ os = file.startWrite();
- // Write to XML
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(outs, StandardCharsets.UTF_8.name());
- out.startDocument(null, true);
+ saveUserInternalLocked(userId, os, /* forBackup= */ false);
- getUserShortcutsLocked(userId).saveToXml(this, out, /* forBackup= */ false);
-
- out.endDocument();
-
- // Close.
- file.finishWrite(outs);
- } catch (IOException|XmlPullParserException e) {
+ file.finishWrite(os);
+ } catch (XmlPullParserException|IOException e) {
Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
- file.failWrite(outs);
+ file.failWrite(os);
}
}
+ private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
+ boolean forBackup) throws IOException, XmlPullParserException {
+
+ final BufferedOutputStream bos = new BufferedOutputStream(os);
+
+ // Write to XML
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(bos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+
+ getUserShortcutsLocked(userId).saveToXml(this, out, forBackup);
+
+ out.endDocument();
+
+ bos.flush();
+ os.flush();
+ }
+
static IOException throwForInvalidTag(int depth, String tag) throws IOException {
throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
}
+ static void warnForInvalidTag(int depth, String tag) throws IOException {
+ Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
+ }
+
@Nullable
private ShortcutUser loadUserLocked(@UserIdInt int userId) {
final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
@@ -675,30 +691,8 @@
}
return null;
}
- ShortcutUser ret = null;
try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(in, StandardCharsets.UTF_8.name());
-
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
- final int depth = parser.getDepth();
-
- final String tag = parser.getName();
- if (DEBUG_LOAD) {
- Slog.d(TAG, String.format("depth=%d type=%d name=%s",
- depth, type, tag));
- }
- if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
- ret = ShortcutUser.loadFromXml(parser, userId);
- continue;
- }
- throwForInvalidTag(depth, tag);
- }
- return ret;
+ return loadUserInternal(userId, in, /* forBackup= */ false);
} catch (IOException|XmlPullParserException e) {
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
return null;
@@ -707,6 +701,36 @@
}
}
+ private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
+ boolean fromBackup) throws XmlPullParserException, IOException {
+
+ final BufferedInputStream bis = new BufferedInputStream(is);
+
+ ShortcutUser ret = null;
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(bis, StandardCharsets.UTF_8.name());
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+
+ final String tag = parser.getName();
+ if (DEBUG_LOAD) {
+ Slog.d(TAG, String.format("depth=%d type=%d name=%s",
+ depth, type, tag));
+ }
+ if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
+ ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup);
+ continue;
+ }
+ throwForInvalidTag(depth, tag);
+ }
+ return ret;
+ }
+
private void scheduleSaveBaseState() {
scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
}
@@ -1042,6 +1066,10 @@
Preconditions.checkState(isCallerShell(), "Caller must be shell");
}
+ private void enforceSystem() {
+ Preconditions.checkState(isCallerSystem(), "Caller must be system");
+ }
+
private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
Preconditions.checkStringNotEmpty(packageName, "packageName");
@@ -1182,10 +1210,10 @@
final int size = newShortcuts.size();
synchronized (mLock) {
- getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
-
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.ensureNotShadowAndSave(this);
+
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1219,10 +1247,10 @@
final int size = newShortcuts.size();
synchronized (mLock) {
- getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
-
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.ensureNotShadowAndSave(this);
+
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1258,10 +1286,10 @@
verifyCaller(packageName, userId);
synchronized (mLock) {
- getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
-
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
+ ps.ensureNotShadowAndSave(this);
+
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1466,6 +1494,9 @@
@VisibleForTesting
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
+
+ // TODO Don't remove shadow packages' information.
+
final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
final ShortcutUser mUser = getUserShortcutsLocked(owningUserId);
@@ -1492,9 +1523,6 @@
mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
}
- // Remove the package info too.
- mUser.removePackageInfo(packageUserId, packageName);
-
scheduleSaveUser(owningUserId);
if (doNotify) {
@@ -1617,11 +1645,13 @@
Preconditions.checkNotNull(shortcutIds, "shortcutIds");
synchronized (mLock) {
- getUserShortcutsLocked(userId).ensurePackageInfo(
- ShortcutService.this, callingPackage, launcherUserId);
+ final ShortcutLauncher launcher =
+ getLauncherShortcuts(callingPackage, userId, launcherUserId);
- getLauncherShortcuts(callingPackage, userId, launcherUserId).pinShortcuts(
- ShortcutService.this, packageName, shortcutIds);
+ launcher.ensureNotShadowAndSave(ShortcutService.this);
+
+ launcher.pinShortcuts(
+ ShortcutService.this, userId, packageName, shortcutIds);
}
userPackageChanged(packageName, userId);
}
@@ -1731,23 +1761,21 @@
if (DEBUG) {
Slog.d(TAG, "cleanupGonePackages() userId=" + userId);
}
- ArrayList<PackageWithUser> gonePackages = null;
+ final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
synchronized (mLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
- final ArrayMap<PackageWithUser, ShortcutPackageInfo> infos = user.getAllPackageInfos();
- for (int i = infos.size() -1; i >= 0; i--) {
- final ShortcutPackageInfo info = infos.valueAt(i);
- if (info.isShadow()) {
- continue;
+
+ user.forAllPackageItems(spi -> {
+ if (spi.getPackageInfo().isShadow()) {
+ return; // Don't delete shadow information.
}
- if (isPackageInstalled(info.getPackageName(), info.getUserId())) {
- continue;
+ if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
+ return;
}
- gonePackages = ArrayUtils.add(gonePackages,
- PackageWithUser.of(info.getUserId(), info.getPackageName()));
- }
- if (gonePackages != null) {
+ gonePackages.add(PackageWithUser.of(spi));
+ });
+ if (gonePackages.size() > 0) {
for (int i = gonePackages.size() - 1; i >= 0; i--) {
final PackageWithUser pu = gonePackages.get(i);
cleanUpPackageLocked(pu.packageName, userId, pu.userId);
@@ -1761,25 +1789,18 @@
Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
}
synchronized (mLock) {
- final ShortcutPackageInfo existing = getUserShortcutsLocked(userId)
- .getPackageInfo(userId, packageName);
-
- if (existing != null && existing.isShadow()) {
- Slog.w(TAG, "handlePackageAdded: TODO Restore not implemented");
- }
+ getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
}
}
private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
if (DEBUG) {
- Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", packageName, userId));
+ Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
+ packageName, userId));
}
+
synchronized (mLock) {
- final ShortcutPackageInfo spi =
- getUserShortcutsLocked(userId).getPackageInfo(userId, packageName);
- if (spi != null) {
- spi.refreshAndSave(this, userId);
- }
+ getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
}
}
@@ -1792,13 +1813,14 @@
}
}
- // === Backup & restore ===
+ // === PackageManager interaction ===
PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
return injectPackageInfo(packageName, userId, true);
}
int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
+ final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS
, userId);
@@ -1806,12 +1828,15 @@
// Shouldn't happen.
Slog.wtf(TAG, "RemoteException", e);
return -1;
+ } finally {
+ injectRestoreCallingIdentity(token);
}
}
@VisibleForTesting
PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
boolean getSignatures) {
+ final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
| (getSignatures ? PackageManager.GET_SIGNATURES : 0)
@@ -1820,17 +1845,22 @@
// Shouldn't happen.
Slog.wtf(TAG, "RemoteException", e);
return null;
+ } finally {
+ injectRestoreCallingIdentity(token);
}
}
@VisibleForTesting
ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
+ final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
} catch (RemoteException e) {
// Shouldn't happen.
Slog.wtf(TAG, "RemoteException", e);
return null;
+ } finally {
+ injectRestoreCallingIdentity(token);
}
}
@@ -1839,12 +1869,61 @@
return (ai != null) && ((ai.flags & flags) == flags);
}
+ private boolean isPackageInstalled(String packageName, int userId) {
+ return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
+ }
+
+ // === Backup & restore ===
+
boolean shouldBackupApp(String packageName, int userId) {
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
}
- private boolean isPackageInstalled(String packageName, int userId) {
- return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
+ @Override
+ public byte[] getBackupPayload(@UserIdInt int userId) throws RemoteException {
+ enforceSystem();
+ if (DEBUG) {
+ Slog.d(TAG, "Backing up user " + userId);
+ }
+ synchronized (mLock) {
+ final ShortcutUser user = getUserShortcutsLocked(userId);
+ if (user == null) {
+ Slog.w(TAG, "Can't backup: user not found: id=" + userId);
+ return null;
+ }
+
+ user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave(this));
+
+ // Then save.
+ final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
+ try {
+ saveUserInternalLocked(userId, os, /* forBackup */ true);
+ } catch (XmlPullParserException|IOException e) {
+ // Shouldn't happen.
+ Slog.w(TAG, "Backup failed.", e);
+ return null;
+ }
+ return os.toByteArray();
+ }
+ }
+
+ @Override
+ public void applyRestore(byte[] payload, @UserIdInt int userId) throws RemoteException {
+ enforceSystem();
+ if (DEBUG) {
+ Slog.d(TAG, "Restoring user " + userId);
+ }
+ final ShortcutUser user;
+ final ByteArrayInputStream is = new ByteArrayInputStream(payload);
+ try {
+ user = loadUserInternal(userId, is, /* fromBackup */ true);
+ } catch (XmlPullParserException|IOException e) {
+ Slog.w(TAG, "Restoration failed.", e);
+ return;
+ }
+ synchronized (mLock) {
+ mUsers.put(userId, user);
+ }
}
// === Dump ===
@@ -2202,19 +2281,4 @@
return pkg.findShortcutById(shortcutId);
}
}
-
- @VisibleForTesting
- ShortcutPackageInfo getPackageInfoForTest(String packageName, int userId) {
- return getPackageInfoForTest(packageName, userId, userId);
- }
-
- @VisibleForTesting
- ShortcutPackageInfo getPackageInfoForTest(String packageName, int userId, int packageUserId) {
- synchronized (mLock) {
- final ShortcutUser user = mUsers.get(userId);
- if (user == null) return null;
-
- return user.getPackageInfo(packageUserId, packageName);
- }
- }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 19feb2a..487558f 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -31,6 +31,7 @@
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.function.Consumer;
/**
* User information used by {@link ShortcutService}.
@@ -56,6 +57,10 @@
return new PackageWithUser(launcherUserId, packageName);
}
+ public static PackageWithUser of(ShortcutPackageItem spi) {
+ return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
+ }
+
@Override
public int hashCode() {
return packageName.hashCode() ^ userId;
@@ -84,8 +89,6 @@
private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
- private final ArrayMap<PackageWithUser, ShortcutPackageInfo> mPackageInfos = new ArrayMap<>();
-
private ComponentName mLauncherComponent;
public ShortcutUser(int userId) {
@@ -100,35 +103,14 @@
return mLaunchers;
}
- public ShortcutLauncher getLauncher(@UserIdInt int userId, @NonNull String packageName) {
- return mLaunchers.get(PackageWithUser.of(userId, packageName));
- }
-
public void addLauncher(ShortcutLauncher launcher) {
- mLaunchers.put(PackageWithUser.of(launcher.getUserId(), launcher.getPackageName()),
- launcher);
+ mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
+ launcher.getPackageName()), launcher);
}
public ShortcutLauncher removeLauncher(
- @UserIdInt int userId, @NonNull String packageName) {
- return mLaunchers.remove(PackageWithUser.of(userId, packageName));
- }
-
- public ArrayMap<PackageWithUser, ShortcutPackageInfo> getAllPackageInfos() {
- return mPackageInfos;
- }
-
- public ShortcutPackageInfo getPackageInfo(@UserIdInt int userId, @NonNull String packageName) {
- return mPackageInfos.get(PackageWithUser.of(userId, packageName));
- }
-
- public void addPackageInfo(ShortcutPackageInfo spi) {
- mPackageInfos.put(PackageWithUser.of(spi.getUserId(), spi.getPackageName()), spi);
- }
-
- public ShortcutPackageInfo removePackageInfo(
- @UserIdInt int userId, @NonNull String packageName) {
- return mPackageInfos.remove(PackageWithUser.of(userId, packageName));
+ @UserIdInt int packageUserId, @NonNull String packageName) {
+ return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
}
public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
@@ -151,20 +133,37 @@
return ret;
}
- public void ensurePackageInfo(ShortcutService s, String packageName, @UserIdInt int userId) {
- final PackageWithUser key = PackageWithUser.of(userId, packageName);
- final ShortcutPackageInfo existing = mPackageInfos.get(key);
+ public void forAllPackageItems(Consumer<ShortcutPackageItem> callback) {
+ {
+ final int size = mLaunchers.size();
+ for (int i = 0; i < size; i++) {
+ callback.accept(mLaunchers.valueAt(i));
+ }
+ }
+ {
+ final int size = mPackages.size();
+ for (int i = 0; i < size; i++) {
+ callback.accept(mPackages.valueAt(i));
+ }
+ }
+ }
- if (existing != null) {
- return;
- }
- if (ShortcutService.DEBUG) {
- Slog.d(TAG, String.format("Fetching package info: %s user=%d", packageName, userId));
- }
- final ShortcutPackageInfo newSpi = ShortcutPackageInfo.generateForInstalledPackage(
- s, packageName, userId);
- mPackageInfos.put(key, newSpi);
- s.scheduleSaveUser(mUserId);
+ public void unshadowPackage(ShortcutService s, @NonNull String packageName,
+ @UserIdInt int packageUserId) {
+ forPackageItem(packageName, packageUserId, spi -> {
+ Slog.i(TAG, String.format("Restoring for %s, user=%d", packageName, packageUserId));
+ spi.ensureNotShadowAndSave(s);
+ });
+ }
+
+ public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
+ Consumer<ShortcutPackageItem> callback) {
+ forAllPackageItems(spi -> {
+ if ((spi.getPackageUserId() == packageUserId)
+ && spi.getPackageName().equals(packageName)) {
+ callback.accept(spi);
+ }
+ });
}
public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
@@ -174,12 +173,7 @@
ShortcutService.writeTagValue(out, TAG_LAUNCHER,
mLauncherComponent);
- {
- final int size = mPackageInfos.size();
- for (int i = 0; i < size; i++) {
- saveShortcutPackageItem(s, out, mPackageInfos.valueAt(i), forBackup);
- }
- }
+ // Can't use forEachPackageItem due to the checked exceptions.
{
final int size = mLaunchers.size();
for (int i = 0; i < size; i++) {
@@ -198,14 +192,19 @@
private void saveShortcutPackageItem(ShortcutService s, XmlSerializer out,
ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
- if (forBackup && !s.shouldBackupApp(spi.getPackageName(), mUserId)) {
- return; // Don't save.
+ if (forBackup) {
+ if (!s.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
+ return; // Don't save.
+ }
+ if (spi.getPackageUserId() != spi.getOwnerUserId()) {
+ return; // Don't save cross-user information.
+ }
}
spi.saveToXml(out, forBackup);
}
- public static ShortcutUser loadFromXml(XmlPullParser parser, int userId)
- throws IOException, XmlPullParserException {
+ public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
+ boolean fromBackup) throws IOException, XmlPullParserException {
final ShortcutUser ret = new ShortcutUser(userId);
final int outerDepth = parser.getDepth();
@@ -217,31 +216,30 @@
}
final int depth = parser.getDepth();
final String tag = parser.getName();
- switch (tag) {
- case TAG_LAUNCHER: {
- ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
- parser, ATTR_VALUE);
- continue;
- }
- case ShortcutPackage.TAG_ROOT: {
- final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(parser, userId);
- // Don't use addShortcut(), we don't need to save the icon.
- ret.getPackages().put(shortcuts.getPackageName(), shortcuts);
- continue;
- }
+ if (depth == outerDepth + 1) {
+ switch (tag) {
+ case TAG_LAUNCHER: {
+ ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
+ parser, ATTR_VALUE);
+ continue;
+ }
+ case ShortcutPackage.TAG_ROOT: {
+ final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
+ s, parser, userId, fromBackup);
- case ShortcutLauncher.TAG_ROOT: {
- ret.addLauncher(ShortcutLauncher.loadFromXml(parser, userId));
- continue;
- }
+ // Don't use addShortcut(), we don't need to save the icon.
+ ret.getPackages().put(shortcuts.getPackageName(), shortcuts);
+ continue;
+ }
- case ShortcutPackageInfo.TAG_ROOT: {
- ret.addPackageInfo(ShortcutPackageInfo.loadFromXml(parser, userId));
- continue;
+ case ShortcutLauncher.TAG_ROOT: {
+ ret.addLauncher(ShortcutLauncher.loadFromXml(parser, userId, fromBackup));
+ continue;
+ }
}
}
- throw ShortcutService.throwForInvalidTag(depth, tag);
+ ShortcutService.warnForInvalidTag(depth, tag);
}
return ret;
}
@@ -283,9 +281,5 @@
for (int i = 0; i < mPackages.size(); i++) {
mPackages.valueAt(i).dump(s, pw, prefix + " ");
}
-
- for (int i = 0; i < mPackageInfos.size(); i++) {
- mPackageInfos.valueAt(i).dump(s, pw, prefix + " ");
- }
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 6df36e4..7f0da1e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -31,7 +31,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
@@ -924,12 +923,12 @@
}
// Don't call them within the mRestrictionsLock.
synchronized (mPackagesLock) {
- if (globalChanged) {
- writeUserListLP();
- }
if (localChanged) {
writeUserLP(getUserDataNoChecks(userId));
}
+ if (globalChanged) {
+ writeUserListLP();
+ }
}
synchronized (mRestrictionsLock) {
@@ -1491,8 +1490,8 @@
updateUserIds();
initDefaultGuestRestrictions();
- writeUserListLP();
writeUserLP(userData);
+ writeUserListLP();
}
private String getOwnerName() {
@@ -1542,8 +1541,10 @@
serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
Long.toString(userInfo.lastLoggedInTime));
- serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
- userInfo.lastLoggedInFingerprint);
+ if (userInfo.lastLoggedInFingerprint != null) {
+ serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
+ userInfo.lastLoggedInFingerprint);
+ }
if (userInfo.iconPath != null) {
serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
}
@@ -1599,7 +1600,7 @@
serializer.endDocument();
userFile.finishWrite(fos);
} catch (Exception ioe) {
- Slog.e(LOG_TAG, "Error writing user info " + userData.info.id + "\n" + ioe);
+ Slog.e(LOG_TAG, "Error writing user info " + userData.info.id, ioe);
userFile.failWrite(fos);
}
}
@@ -1944,6 +1945,7 @@
userData.info = userInfo;
mUsers.put(userId, userData);
}
+ writeUserLP(userData);
writeUserListLP();
if (parent != null) {
if (isManagedProfile) {
@@ -2217,13 +2219,13 @@
mCachedEffectiveUserRestrictions.remove(userHandle);
mDevicePolicyLocalUserRestrictions.remove(userHandle);
}
- // Remove user file
- AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
- userFile.delete();
// Update the user list
synchronized (mPackagesLock) {
writeUserListLP();
}
+ // Remove user file
+ AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
+ userFile.delete();
updateUserIds();
File userDir = Environment.getUserSystemDirectory(userHandle);
File renamedUserDir = Environment.getUserSystemDirectory(UserHandle.USER_NULL - userHandle);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 574faa0..747e31e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -18,7 +18,6 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
@@ -38,7 +37,6 @@
import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
-import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
import android.app.ActivityManagerInternal;
@@ -4647,7 +4645,7 @@
// TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
// Also, we don't allow windows in multi-window mode to extend out of the screen.
if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && attrs.type != TYPE_SYSTEM_ERROR
- && !win.inMultiWindowMode()) {
+ && !win.isInMultiWindowMode()) {
df.left = df.top = -10000;
df.right = df.bottom = 10000;
if (attrs.type != TYPE_WALLPAPER) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 4a00ebd..dbbaa5e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -319,7 +319,7 @@
*/
@Override
public void disable2(int what, IBinder token, String pkg) {
- disableForUser(what, token, pkg, mCurrentUserId);
+ disable2ForUser(what, token, pkg, mCurrentUserId);
}
/**
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 98d44ac..b501398 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -509,34 +509,35 @@
continue;
}
- Region windowBounds = mTempRegion2;
+ // Consider the touchable portion of the window
Matrix matrix = mTempMatrix;
populateTransformationMatrixLocked(windowState, matrix);
+ Region touchableRegion = mTempRegion3;
+ windowState.getTouchableRegion(touchableRegion);
+ Rect touchableFrame = mTempRect1;
+ touchableRegion.getBounds(touchableFrame);
RectF windowFrame = mTempRectF;
+ windowFrame.set(touchableFrame);
+ windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top);
+ matrix.mapRect(windowFrame);
+ Region windowBounds = mTempRegion2;
+ windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+ (int) windowFrame.right, (int) windowFrame.bottom);
+ // Only update new regions
+ Region portionOfWindowAlreadyAccountedFor = mTempRegion3;
+ portionOfWindowAlreadyAccountedFor.set(mMagnifiedBounds);
+ portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
+ windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
if (mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
- windowFrame.set(windowState.mFrame);
- windowFrame.offset(-windowFrame.left, -windowFrame.top);
- matrix.mapRect(windowFrame);
- windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
- (int) windowFrame.right, (int) windowFrame.bottom);
mMagnifiedBounds.op(windowBounds, Region.Op.UNION);
mMagnifiedBounds.op(mAvailableBounds, Region.Op.INTERSECT);
} else {
- Region touchableRegion = mTempRegion3;
- windowState.getTouchableRegion(touchableRegion);
- Rect touchableFrame = mTempRect1;
- touchableRegion.getBounds(touchableFrame);
- windowFrame.set(touchableFrame);
- windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top);
- matrix.mapRect(windowFrame);
- windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
- (int) windowFrame.right, (int) windowFrame.bottom);
nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
- windowBounds.op(mMagnifiedBounds, Region.Op.DIFFERENCE);
mAvailableBounds.op(windowBounds, Region.Op.DIFFERENCE);
}
+ // Update accounted bounds
Region accountedBounds = mTempRegion2;
accountedBounds.set(mMagnifiedBounds);
accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6741aba..68ea4df 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -41,6 +41,8 @@
import com.android.server.wm.DimLayer.DimLayerUser;
+import java.util.ArrayList;
+
/**
* Keeps information about the docked stack divider.
*/
@@ -87,7 +89,7 @@
private final DimLayer mDimLayer;
private boolean mMinimizedDock;
- private boolean mAnimating;
+ private boolean mAnimatingForMinimizedDockedStack;
private boolean mAnimationStarted;
private long mAnimationStartTime;
private float mAnimationStart;
@@ -96,7 +98,8 @@
private final Interpolator mMinimizedDockInterpolator;
private float mMaximizeMeetFraction;
private final Rect mTouchRegion = new Rect();
- private boolean mAdjustingForIme;
+ private boolean mAnimatingForIme;
+ private boolean mAdjustedForIme;
DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
@@ -174,12 +177,11 @@
return mLastVisibility;
}
- void setAdjustingForIme(boolean adjusting) {
- mAdjustingForIme = adjusting;
- }
-
- boolean isAdjustingForIme() {
- return mAdjustingForIme;
+ void setAdjustedForIme(boolean adjusted, boolean animate) {
+ if (mAdjustedForIme != adjusted) {
+ mAnimatingForIme = animate;
+ mAdjustedForIme = adjusted;
+ }
}
void positionDockedStackedDivider(Rect frame) {
@@ -342,6 +344,7 @@
}
mMinimizedDock = minimizedDock;
+ mAnimatingForIme = false;
if (minimizedDock) {
if (animate) {
startAdjustAnimation(0f, 1f);
@@ -358,7 +361,7 @@
}
private void startAdjustAnimation(float from, float to) {
- mAnimating = true;
+ mAnimatingForMinimizedDockedStack = true;
mAnimationStarted = false;
mAnimationStart = from;
mAnimationTarget = to;
@@ -380,10 +383,45 @@
}
public boolean animate(long now) {
- if (!mAnimating) {
+ if (mAnimatingForMinimizedDockedStack) {
+ return animateForMinimizedDockedStack(now);
+ } else if (mAnimatingForIme) {
+ return animateForIme();
+ } else {
return false;
}
+ }
+ private boolean animateForIme() {
+ boolean updated = false;
+ boolean animating = false;
+
+ final ArrayList<TaskStack> stacks = mDisplayContent.getStacks();
+ for (int i = stacks.size() - 1; i >= 0; --i) {
+ final TaskStack stack = stacks.get(i);
+ if (stack != null && stack.isAdjustedForIme()) {
+ updated |= stack.updateAdjustForIme();
+ animating |= stack.isAnimatingForIme();
+ }
+ }
+
+ if (updated) {
+ mService.mWindowPlacerLocked.performSurfacePlacement();
+ }
+
+ if (!animating) {
+ mAnimatingForIme = false;
+ for (int i = stacks.size() - 1; i >= 0; --i) {
+ final TaskStack stack = stacks.get(i);
+ if (stack != null) {
+ stack.clearImeGoingAway();
+ }
+ }
+ }
+ return animating;
+ }
+
+ private boolean animateForMinimizedDockedStack(long now) {
final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked();
if (!mAnimationStarted) {
mAnimationStarted = true;
@@ -406,7 +444,7 @@
}
}
if (t >= 1.0f) {
- mAnimating = false;
+ mAnimatingForMinimizedDockedStack = false;
return false;
} else {
return true;
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 0581a16..24783bc 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -16,38 +16,33 @@
package com.android.server.wm;
-import android.os.Looper;
import android.os.Process;
import android.view.Display;
import android.view.InputChannel;
-import android.view.InputEventReceiver;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
-
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputWindowHandle;
-public final class InputConsumerImpl implements WindowManagerPolicy.InputConsumer {
+class InputConsumerImpl {
final WindowManagerService mService;
final InputChannel mServerChannel, mClientChannel;
final InputApplicationHandle mApplicationHandle;
final InputWindowHandle mWindowHandle;
- final InputEventReceiver mInputEventReceiver;
- final int mWindowLayer;
- public InputConsumerImpl(WindowManagerService service, Looper looper,
- InputEventReceiver.Factory inputEventReceiverFactory) {
- String name = "input consumer";
+ InputConsumerImpl(WindowManagerService service, String name, InputChannel inputChannel) {
mService = service;
InputChannel[] channels = InputChannel.openInputChannelPair(name);
mServerChannel = channels[0];
- mClientChannel = channels[1];
+ if (inputChannel != null) {
+ channels[1].transferTo(inputChannel);
+ channels[1].dispose();
+ mClientChannel = inputChannel;
+ } else {
+ mClientChannel = channels[1];
+ }
mService.mInputManager.registerInputChannel(mServerChannel, null);
- mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
- mClientChannel, looper);
-
mApplicationHandle = new InputApplicationHandle(null);
mApplicationHandle.name = name;
mApplicationHandle.dispatchingTimeoutNanos =
@@ -57,8 +52,7 @@
mWindowHandle.name = name;
mWindowHandle.inputChannel = mServerChannel;
mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
- mWindowLayer = getLayerLw(mWindowHandle.layoutParamsType);
- mWindowHandle.layer = mWindowLayer;
+ mWindowHandle.layer = getLayerLw(mWindowHandle.layoutParamsType);
mWindowHandle.layoutParamsFlags = 0;
mWindowHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
@@ -81,21 +75,15 @@
mWindowHandle.frameBottom = dh;
}
- @Override
- public void dismiss() {
- synchronized (mService.mWindowMap) {
- if (mService.removeInputConsumer()) {
- mInputEventReceiver.dispose();
- mService.mInputManager.unregisterInputChannel(mServerChannel);
- mClientChannel.dispose();
- mServerChannel.dispose();
- }
- }
- }
-
private int getLayerLw(int windowType) {
return mService.mPolicy.windowTypeToLayerLw(windowType)
* WindowManagerService.TYPE_LAYER_MULTIPLIER
+ WindowManagerService.TYPE_LAYER_OFFSET;
}
+
+ void disposeChannelsLw() {
+ mService.mInputManager.unregisterInputChannel(mServerChannel);
+ mClientChannel.dispose();
+ mServerChannel.dispose();
+ }
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index b702180..eea0e73 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -282,6 +282,8 @@
boolean addInputConsumerHandle = mService.mInputConsumer != null;
+ boolean addWallpaperInputConsumerHandle = mService.mWallpaperInputConsumer != null;
+
// Add all windows on the default display.
final int numDisplays = mService.mDisplayContents.size();
final WallpaperController wallpaperController = mService.mWallpaperControllerLocked;
@@ -302,6 +304,14 @@
addInputConsumerHandle = false;
}
+ if (addWallpaperInputConsumerHandle) {
+ if (child.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER) {
+ // Add the wallpaper input consumer above the first wallpaper window.
+ addInputWindowHandleLw(mService.mWallpaperInputConsumer.mWindowHandle);
+ addWallpaperInputConsumerHandle = false;
+ }
+ }
+
final int flags = child.mAttrs.flags;
final int privateFlags = child.mAttrs.privateFlags;
final int type = child.mAttrs.type;
@@ -329,6 +339,11 @@
}
}
+ if (addWallpaperInputConsumerHandle) {
+ // No wallpaper found, add the wallpaper input consumer at the end.
+ addInputWindowHandleLw(mService.mWallpaperInputConsumer.mWindowHandle);
+ }
+
// Send windows to native code.
mService.mInputManager.setInputWindows(mInputWindowHandles);
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index c0c1ed8..daeb860 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -195,10 +195,8 @@
@Override
public void repositionChild(IWindow window, int left, int top, int right, int bottom,
- int requestedWidth, int requestedHeight,
long deferTransactionUntilFrame, Rect outFrame) {
mService.repositionChild(this, window, left, top, right, bottom,
- requestedWidth, requestedHeight,
deferTransactionUntilFrame, outFrame);
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 8d67771..0bf7102 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -55,6 +55,11 @@
// If the stack should be resized to fullscreen.
private static final boolean FULLSCREEN = true;
+ // When we have a top-bottom split screen, we shift the bottom stack up to accommodate
+ // the IME window. The static flag below controls whether to run animation when the
+ // IME window goes away.
+ private static final boolean ANIMATE_IME_GOING_AWAY = false;
+
/** Unique identifier */
final int mStackId;
@@ -107,6 +112,7 @@
private final Rect mLastContentBounds = new Rect();
private final Rect mTmpAdjustedBounds = new Rect();
private boolean mAdjustedForIme;
+ private boolean mImeGoingAway;
private WindowState mImeWin;
private float mMinimizeAmount;
private final int mDockedStackMinimizeThickness;
@@ -796,19 +802,54 @@
void setAdjustedForIme(WindowState imeWin) {
mAdjustedForIme = true;
mImeWin = imeWin;
- if (updateAdjustedBounds()) {
- getDisplayContent().mDividerControllerLocked.setAdjustingForIme(true);
+ mImeGoingAway = false;
+ }
+
+ boolean isAdjustedForIme() {
+ return mAdjustedForIme || mImeGoingAway;
+ }
+ void clearImeGoingAway() {
+ mImeGoingAway = false;
+ }
+
+ boolean isAnimatingForIme() {
+ return mImeWin != null && mImeWin.isAnimatingLw();
+ }
+
+ /**
+ * Update the stack's bounds (crop or position) according to the IME window's
+ * current position. When IME window is animated, the bottom stack is animated
+ * together to track the IME window's current position, and the top stack is
+ * cropped as necessary.
+ *
+ * @return true if a traversal should be performed after the adjustment.
+ */
+ boolean updateAdjustForIme() {
+ boolean stopped = false;
+ if (mImeGoingAway && (!ANIMATE_IME_GOING_AWAY || !isAnimatingForIme())) {
+ mImeWin = null;
+ mAdjustedForIme = false;
+ stopped = true;
}
+ // Make sure to run a traversal when the animation stops so that the stack
+ // is moved to its final position.
+ return updateAdjustedBounds() || stopped;
}
/**
* Resets the adjustment after it got adjusted for the IME.
+ * @param adjustBoundsNow if true, reset and update the bounds immediately and forget about
+ * animations; otherwise, set flag and animates the window away together
+ * with IME window.
*/
- void resetAdjustedForIme() {
- mAdjustedForIme = false;
- mImeWin = null;
- if (updateAdjustedBounds()) {
- getDisplayContent().mDividerControllerLocked.setAdjustingForIme(true);
+ void resetAdjustedForIme(boolean adjustBoundsNow) {
+ if (adjustBoundsNow) {
+ mImeWin = null;
+ mAdjustedForIme = false;
+ mImeGoingAway = false;
+ updateAdjustedBounds();
+ } else {
+ mImeGoingAway |= mAdjustedForIme;
}
}
@@ -843,6 +884,12 @@
getDisplayContent().getContentRect(displayContentRect);
contentBounds.set(displayContentRect);
int imeTop = Math.max(imeWin.getDisplayFrameLw().top, contentBounds.top);
+
+ // if IME window is animating, get its actual vertical shown position (but no smaller than
+ // the final target vertical position)
+ if (imeWin.isAnimatingLw()) {
+ imeTop = Math.max(imeTop, imeWin.getShownPositionLw().y);
+ }
imeTop += imeWin.getGivenContentInsetsLw().top;
if (contentBounds.bottom > imeTop) {
contentBounds.bottom = imeTop;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5d13b3b..dcb4a63 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -407,6 +407,11 @@
InputConsumerImpl mInputConsumer;
/**
+ * The input consumer added to the window manager before all wallpaper windows.
+ */
+ InputConsumerImpl mWallpaperInputConsumer;
+
+ /**
* Windows that are being resized. Used so we can tell the client about
* the resize after closing the transaction in which we resized the
* underlying surface.
@@ -2531,7 +2536,6 @@
void repositionChild(Session session, IWindow client,
int left, int top, int right, int bottom,
- int requestedWidth, int requestedHeight,
long deferTransactionUntilFrame, Rect outFrame) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild");
long origId = Binder.clearCallingIdentity();
@@ -2547,7 +2551,6 @@
"repositionChild called but window is not"
+ "attached to a parent win=" + win);
}
- win.setRequestedSize(requestedWidth, requestedHeight);
win.mAttrs.x = left;
win.mAttrs.y = top;
@@ -2604,8 +2607,6 @@
== PackageManager.PERMISSION_GRANTED;
long origId = Binder.clearCallingIdentity();
- final boolean preserveGeometry = (attrs != null) && (attrs.privateFlags &
- WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY) != 0;
synchronized(mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
@@ -2613,7 +2614,7 @@
}
WindowStateAnimator winAnimator = win.mWinAnimator;
- if (!preserveGeometry && viewVisibility != View.GONE) {
+ if (viewVisibility != View.GONE) {
win.setRequestedSize(requestedWidth, requestedHeight);
}
@@ -2662,9 +2663,7 @@
if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) {
winAnimator.mAlpha = attrs.alpha;
}
- if (!preserveGeometry) {
- win.setWindowScale(win.mRequestedWidth, win.mRequestedHeight);
- }
+ win.setWindowScale(win.mRequestedWidth, win.mRequestedHeight);
boolean imMayMove = (flagChanges & (FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE)) != 0;
final boolean isDefaultDisplay = win.isDefaultDisplay();
@@ -7380,8 +7379,9 @@
final WindowState imeWin = mInputMethodWindow;
final TaskStack focusedStack =
mCurrentFocus != null ? mCurrentFocus.getStack() : null;
+ final boolean dockVisible = isStackVisibleLocked(DOCKED_STACK_ID);
if (imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
- && isStackVisibleLocked(DOCKED_STACK_ID)
+ && dockVisible
&& focusedStack != null
&& focusedStack.getDockSide() == DOCKED_BOTTOM){
final ArrayList<TaskStack> stacks = displayContent.getStacks();
@@ -7391,12 +7391,14 @@
stack.setAdjustedForIme(imeWin);
}
}
+ displayContent.mDividerControllerLocked.setAdjustedForIme(true, true);
} else {
final ArrayList<TaskStack> stacks = displayContent.getStacks();
for (int i = stacks.size() - 1; i >= 0; --i) {
final TaskStack stack = stacks.get(i);
- stack.resetAdjustedForIme();
+ stack.resetAdjustedForIme(!dockVisible);
}
+ displayContent.mDividerControllerLocked.setAdjustedForIme(false, dockVisible);
}
}
@@ -9627,13 +9629,37 @@
}
}
+ private static final class HideNavInputConsumer extends InputConsumerImpl
+ implements WindowManagerPolicy.InputConsumer {
+ private final InputEventReceiver mInputEventReceiver;
+
+ HideNavInputConsumer(WindowManagerService service, Looper looper,
+ InputEventReceiver.Factory inputEventReceiverFactory) {
+ super(service, "input consumer", null);
+ mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
+ mClientChannel, looper);
+ }
+
+ @Override
+ public void dismiss() {
+ if (mService.removeInputConsumer()) {
+ synchronized (mService.mWindowMap) {
+ mInputEventReceiver.dispose();
+ disposeChannelsLw();
+ }
+ }
+ }
+ }
+
@Override
- public InputConsumerImpl addInputConsumer(Looper looper,
+ public WindowManagerPolicy.InputConsumer addInputConsumer(Looper looper,
InputEventReceiver.Factory inputEventReceiverFactory) {
synchronized (mWindowMap) {
- mInputConsumer = new InputConsumerImpl(this, looper, inputEventReceiverFactory);
+ HideNavInputConsumer inputConsumerImpl = new HideNavInputConsumer(
+ this, looper, inputEventReceiverFactory);
+ mInputConsumer = inputConsumerImpl;
mInputMonitor.updateInputWindowsLw(true);
- return mInputConsumer;
+ return inputConsumerImpl;
}
}
@@ -9648,6 +9674,24 @@
}
}
+ public void createWallpaperInputConsumer(InputChannel inputChannel) {
+ synchronized (mWindowMap) {
+ mWallpaperInputConsumer = new InputConsumerImpl(this, "wallpaper input", inputChannel);
+ mWallpaperInputConsumer.mWindowHandle.hasWallpaper = true;
+ mInputMonitor.updateInputWindowsLw(true);
+ }
+ }
+
+ public void removeWallpaperInputConsumer() {
+ synchronized (mWindowMap) {
+ if (mWallpaperInputConsumer != null) {
+ mWallpaperInputConsumer.disposeChannelsLw();
+ mWallpaperInputConsumer = null;
+ mInputMonitor.updateInputWindowsLw(true);
+ }
+ }
+ }
+
@Override
public boolean hasNavigationBar() {
return mPolicy.hasNavigationBar();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0866c03..57ead8b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -642,7 +642,7 @@
mHaveFrame = true;
final Task task = getTask();
- final boolean fullscreenTask = !inMultiWindowMode();
+ final boolean fullscreenTask = !isInMultiWindowMode();
final boolean windowsAreFloating = task != null && task.isFloating();
// If the task has temp inset bounds set, we have to make sure all its windows uses
@@ -2226,7 +2226,7 @@
}
@Override
- public boolean inMultiWindowMode() {
+ public boolean isInMultiWindowMode() {
final Task task = getTask();
return task != null && !task.isFullscreen();
}
@@ -2527,7 +2527,7 @@
final int pw = mContainingFrame.width();
final int ph = mContainingFrame.height();
final Task task = getTask();
- final boolean nonFullscreenTask = inMultiWindowMode();
+ final boolean nonFullscreenTask = isInMultiWindowMode();
final boolean fitToDisplay = task != null && !task.isFloating() && !layoutInParentFrame();
float x, y;
int w,h;
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 3b0081d..eda2f39 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -693,16 +693,14 @@
// currently animating... let's do something.
final int left = w.mFrame.left;
final int top = w.mFrame.top;
- final boolean adjustedForMinimizedDockedStack = w.getTask() != null &&
- w.getTask().mStack.isAdjustedForMinimizedDockedStack();
+ final boolean adjustedForMinimizedDockOrIme = task != null
+ && (task.mStack.isAdjustedForMinimizedDockedStack()
+ || task.mStack.isAdjustedForIme());
if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
- && !w.isDragResizing() && !adjustedForMinimizedDockedStack
+ && !w.isDragResizing() && !adjustedForMinimizedDockOrIme
&& (task == null || !w.getTask().mStack.getFreezeMovementAnimations())
&& !w.mWinAnimator.mLastHidden) {
winAnimator.setMoveAnimation(left, top);
- } else if (w.mAttrs.type == TYPE_DOCK_DIVIDER &&
- displayContent.getDockedDividerController().isAdjustingForIme()) {
- winAnimator.setMoveAnimation(left, top);
}
//TODO (multidisplay): Accessibility supported only for the default display.
@@ -819,8 +817,6 @@
mService.updateResizingWindows(w);
}
- displayContent.getDockedDividerController().setAdjustingForIme(false);
-
mService.mDisplayManagerInternal.setDisplayProperties(displayId,
mDisplayHasContent,
mPreferredRefreshRate,
@@ -864,6 +860,10 @@
mService.mInputConsumer.layout(dw, dh);
}
+ if (mService.mWallpaperInputConsumer != null) {
+ mService.mWallpaperInputConsumer.layout(dw, dh);
+ }
+
final int N = windows.size();
int i;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 86dd118..96ec02a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -654,8 +654,8 @@
String shortSupportMessage = null;
String longSupportMessage = null;
- // Background color of confirm credentials screen. Default: gray.
- static final int DEF_ORGANIZATION_COLOR = Color.GRAY;
+ // Background color of confirm credentials screen. Default: teal.
+ static final int DEF_ORGANIZATION_COLOR = Color.parseColor("#00796B");
int organizationColor = DEF_ORGANIZATION_COLOR;
// Default title of confirm credentials screen
@@ -4138,8 +4138,8 @@
}
@Override
- public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, String alias,
- boolean requestAccess) {
+ public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, byte[] chain,
+ String alias, boolean requestAccess) {
enforceCanManageInstalledKeys(who);
final int callingUid = mInjector.binderGetCallingUid();
@@ -4149,7 +4149,7 @@
KeyChain.bindAsUser(mContext, UserHandle.getUserHandleForUid(callingUid));
try {
IKeyChainService keyChain = keyChainConnection.getService();
- if (!keyChain.installKeyPair(privKey, cert, alias)) {
+ if (!keyChain.installKeyPair(privKey, cert, chain, alias)) {
return false;
}
if (requestAccess) {
@@ -5696,26 +5696,25 @@
}
@Override
- public boolean setDeviceOwnerLockScreenInfo(ComponentName who, String info) {
+ public void setDeviceOwnerLockScreenInfo(ComponentName who, CharSequence info) {
Preconditions.checkNotNull(who, "ComponentName is null");
if (!mHasFeature) {
- return false;
+ return;
}
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
long token = mInjector.binderClearCallingIdentity();
try {
- mLockPatternUtils.setDeviceOwnerInfo(info);
+ mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null);
} finally {
mInjector.binderRestoreCallingIdentity(token);
}
- return true;
}
}
@Override
- public String getDeviceOwnerLockScreenInfo() {
+ public CharSequence getDeviceOwnerLockScreenInfo() {
return mLockPatternUtils.getDeviceOwnerInfo();
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 14efc27..b026bc5 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -697,6 +697,14 @@
// as appropriate.
mSystemServiceManager.startService(UiModeManagerService.class);
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "UpdatePackagesIfNeeded");
+ try {
+ mPackageManagerService.updatePackagesIfNeeded();
+ } catch (Throwable e) {
+ reportWtf("update packages", e);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "PerformFstrimIfNeeded");
try {
mPackageManagerService.performFstrimIfNeeded();
@@ -705,14 +713,6 @@
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "ExtractPackagesIfNeeded");
- try {
- mPackageManagerService.extractPackagesIfNeeded();
- } catch (Throwable e) {
- reportWtf("extract packages", e);
- }
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
-
try {
ActivityManagerNative.getDefault().showBootMessage(
context.getResources().getText(
@@ -1025,7 +1025,8 @@
mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
}
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) {
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
+ || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
}
@@ -1131,7 +1132,9 @@
mSystemServiceManager.startService(TvInputManagerService.class);
}
- mSystemServiceManager.startService(MediaResourceMonitorService.class);
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
+ mSystemServiceManager.startService(MediaResourceMonitorService.class);
+ }
if (!disableNonCoreServices) {
traceBeginAndSlog("StartMediaRouterService");
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
index eb16a1d..c44ffa4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
@@ -16,9 +16,16 @@
*/
package com.android.server.pm;
+import android.content.ComponentName;
+import android.content.Intent;
import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.PersistableBundle;
import android.test.AndroidTestCase;
+import com.android.internal.util.Preconditions;
import com.android.server.testutis.TestUtils;
/**
@@ -33,12 +40,232 @@
*/
public class ShortcutInfoTest extends AndroidTestCase {
- public void testNoId() {
+ public void testMissingMandatoryFields() {
TestUtils.assertExpectException(
IllegalArgumentException.class,
"ID must be provided",
() -> new ShortcutInfo.Builder(mContext).build());
+ TestUtils.assertExpectException(
+ IllegalArgumentException.class,
+ "title must be provided",
+ () -> new ShortcutInfo.Builder(mContext).setId("id").build()
+ .enforceMandatoryFields());
+ TestUtils.assertExpectException(
+ NullPointerException.class,
+ "Intent must be provided",
+ () -> new ShortcutInfo.Builder(mContext).setId("id").setTitle("x").build()
+ .enforceMandatoryFields());
}
- // TODO Add more tests.
+ private ShortcutInfo parceled(ShortcutInfo si) {
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(si, 0);
+ p.setDataPosition(0);
+ ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
+ p.recycle();
+ return si2;
+ }
+
+ private Intent makeIntent(String action, Object... bundleKeysAndValues) {
+ final Intent intent = new Intent(action);
+ intent.replaceExtras(ShortcutManagerTest.makeBundle(bundleKeysAndValues));
+ return intent;
+ }
+
+ public void testParcel() {
+ ShortcutInfo si = parceled(new ShortcutInfo.Builder(getContext())
+ .setId("id")
+ .setTitle("title")
+ .setIntent(makeIntent("action"))
+ .build());
+ assertEquals(getContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals("title", si.getTitle());
+ assertEquals("action", si.getIntent().getAction());
+
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+
+ si = new ShortcutInfo.Builder(getContext())
+ .setId("id")
+ .setActivityComponent(new ComponentName("a", "b"))
+ .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
+ si.setBitmapPath("abc");
+ si.setIconResourceId(456);
+
+ si = parceled(si);
+
+ assertEquals(getContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals("content://a.b.c/", si.getIcon().getUriString());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals("abc", si.getBitmapPath());
+ assertEquals(456, si.getIconResourceId());
+ }
+
+ public void testClone() {
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
+ .setId("id")
+ .setActivityComponent(new ComponentName("a", "b"))
+ .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+ sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+ sorig.setBitmapPath("abc");
+ sorig.setIconResourceId(456);
+
+ ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
+
+ assertEquals(getContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals("content://a.b.c/", si.getIcon().getUriString());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals("abc", si.getBitmapPath());
+ assertEquals(456, si.getIconResourceId());
+
+ si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
+
+ assertEquals(getContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals(null, si.getBitmapPath());
+ assertEquals(0, si.getIconResourceId());
+
+ si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+ assertEquals(getContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+ assertEquals(null, si.getIcon());
+ assertEquals("title", si.getTitle());
+ assertEquals("text", si.getText());
+ assertEquals(null, si.getIntent());
+ assertEquals(123, si.getWeight());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals(null, si.getBitmapPath());
+ assertEquals(0, si.getIconResourceId());
+
+ si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
+
+ assertEquals(getContext().getPackageName(), si.getPackageName());
+ assertEquals("id", si.getId());
+ assertEquals(null, si.getActivityComponent());
+ assertEquals(null, si.getIcon());
+ assertEquals(null, si.getTitle());
+ assertEquals(null, si.getText());
+ assertEquals(null, si.getIntent());
+ assertEquals(0, si.getWeight());
+ assertEquals(null, si.getExtras());
+
+ assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
+ assertEquals(null, si.getBitmapPath());
+ assertEquals(0, si.getIconResourceId());
+ }
+
+
+ public void testCopyNonNullFieldsFrom() {
+ PersistableBundle pb = new PersistableBundle();
+ pb.putInt("k", 1);
+ ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
+ .setId("id")
+ .setActivityComponent(new ComponentName("a", "b"))
+ .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+ .setTitle("title")
+ .setText("text")
+ .setIntent(makeIntent("action", "key", "val"))
+ .setWeight(123)
+ .setExtras(pb)
+ .build();
+ sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+ sorig.setBitmapPath("abc");
+ sorig.setIconResourceId(456);
+
+ ShortcutInfo si;
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+ .setActivityComponent(new ComponentName("x", "y")).build());
+ assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+ .setIcon(Icon.createWithContentUri("content://x.y.z/")).build());
+ assertEquals("content://x.y.z/", si.getIcon().getUriString());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+ .setTitle("xyz").build());
+ assertEquals("xyz", si.getTitle());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+ .setText("xxx").build());
+ assertEquals("xxx", si.getText());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+ .setIntent(makeIntent("action2")).build());
+ assertEquals("action2", si.getIntent().getAction());
+ assertEquals(null, si.getIntent().getStringExtra("key"));
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+ .setIntent(makeIntent("action3", "key", "x")).build());
+ assertEquals("action3", si.getIntent().getAction());
+ assertEquals("x", si.getIntent().getStringExtra("key"));
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+ .setWeight(999).build());
+ assertEquals(999, si.getWeight());
+
+
+ PersistableBundle pb2 = new PersistableBundle();
+ pb2.putInt("x", 99);
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+ .setExtras(pb2).build());
+ assertEquals(99, si.getExtras().getInt("x"));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
index 61249ae..5d29242 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -646,7 +646,7 @@
runTestOnUiThread(() -> {});
}
- private static Bundle makeBundle(Object... keysAndValues) {
+ public static Bundle makeBundle(Object... keysAndValues) {
Preconditions.checkState((keysAndValues.length % 2) == 0);
if (keysAndValues.length == 0) {
@@ -797,7 +797,6 @@
@NonNull
private List<ShortcutInfo> assertShortcutIds(@NonNull List<ShortcutInfo> actualShortcuts,
String... expectedIds) {
- assertEquals(expectedIds.length, actualShortcuts.size());
final HashSet<String> expected = new HashSet<>(list(expectedIds));
final HashSet<String> actual = new HashSet<>();
for (ShortcutInfo s : actualShortcuts) {
@@ -973,18 +972,6 @@
assertTrue(b == null || b.size() == 0);
}
- private void assertShortcutPackageInfo(String packageName, int userId, int expectedVersion) {
- ShortcutPackageInfo spi = mService.getPackageInfoForTest(packageName, userId);
- assertNotNull(spi);
- assertEquals(expectedVersion, spi.getVersionCode());
-
- assertTrue(spi.canRestoreTo(genPackage(packageName, /*uid*/ 0, 9999999, packageName)));
- }
-
- private void assertNoShortcutPackageInfo(String packageName, int userId) {
- assertNull(mService.getPackageInfoForTest(packageName, userId));
- }
-
private ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) {
return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
}
@@ -1229,11 +1216,6 @@
"shortcut1", "shortcut2");
assertEquals(2, mManager.getRemainingCallCount());
- assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
- assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_0);
- assertNoShortcutPackageInfo(CALLING_PACKAGE_1, USER_10);
- assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_10);
-
// TODO: Check fields
assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1260,11 +1242,6 @@
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
-
- assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
- assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_0);
- assertNoShortcutPackageInfo(CALLING_PACKAGE_1, USER_10);
- assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_10, 2);
});
}
@@ -1302,7 +1279,6 @@
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
assertTrue(mManager.addDynamicShortcut(makeShortcut("s1")));
- assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_10, 2);
});
}
@@ -1608,7 +1584,6 @@
Bitmap bmp;
setCaller(LAUNCHER_1);
-
// Check hasIconResource()/hasIconFile().
assertShortcutIds(assertAllHaveIconResId(mLauncherApps.getShortcutInfo(
CALLING_PACKAGE_1, list("res32x32"),
@@ -1898,12 +1873,7 @@
// TODO Check bitmap removal too.
runWithCaller(CALLING_PACKAGE_2, USER_11, () -> {
- assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_11);
-
mManager.updateShortcuts(list());
-
- // Even an empty update call will populate the package info.
- assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_11, 2);
});
}
@@ -1940,7 +1910,7 @@
assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
setCaller(CALLING_PACKAGE_3);
- final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", 5000);
+ final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", START_TIME + 5000);
assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
setCaller(LAUNCHER_1);
@@ -2100,16 +2070,9 @@
// Pin some.
runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertNoShortcutPackageInfo(LAUNCHER_1, USER_0);
-
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
list("s2", "s3"), getCallingUser());
- assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4);
- assertNoShortcutPackageInfo(LAUNCHER_2, USER_0);
- assertNoShortcutPackageInfo(LAUNCHER_1, USER_10);
- assertNoShortcutPackageInfo(LAUNCHER_2, USER_10);
-
mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
list("s3", "s4", "s5"), getCallingUser());
@@ -2170,19 +2133,11 @@
dumpsysOnLogcat();
- assertNoShortcutPackageInfo(LAUNCHER_1, USER_0);
- assertNoShortcutPackageInfo(LAUNCHER_2, USER_0);
- assertNoShortcutPackageInfo(LAUNCHER_1, USER_10);
- assertNoShortcutPackageInfo(LAUNCHER_2, USER_10);
-
// Pin some.
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
list("s3", "s4"), getCallingUser());
- assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4);
- assertNoShortcutPackageInfo(LAUNCHER_2, USER_0);
-
mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
list("s1", "s2", "s4"), getCallingUser());
});
@@ -2256,16 +2211,10 @@
| ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
"s2");
- assertNoShortcutPackageInfo(LAUNCHER_2, USER_0);
- assertNoShortcutPackageInfo(LAUNCHER_2, USER_10);
-
// Now pin some.
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
list("s1", "s2"), getCallingUser());
- assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5);
- assertNoShortcutPackageInfo(LAUNCHER_2, USER_10);
-
mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
list("s1", "s2"), getCallingUser());
@@ -2283,12 +2232,6 @@
"s2");
});
- assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
- assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2);
- assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0);
- assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4);
- assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5);
-
// Re-initialize and load from the files.
mService.saveDirtyInfo();
initService();
@@ -2297,12 +2240,6 @@
mService.handleUnlockUser(USER_0);
// Make sure package info is restored too.
- assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
- assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2);
- assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0);
- assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4);
- assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5);
-
runWithCaller(LAUNCHER_1, USER_0, () -> {
assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
@@ -3234,10 +3171,6 @@
mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setLauncherComponent(
mService, new ComponentName("pkg1", "class"));
- assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
- assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2);
- assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0);
-
// Restore.
mService.saveDirtyInfo();
initService();
@@ -3248,10 +3181,6 @@
// this will pre-load the per-user info.
mService.handleUnlockUser(UserHandle.USER_SYSTEM);
- assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
- assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2);
- assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0);
-
// Now it's loaded.
assertEquals(1, mService.getShortcutsForTest().size());
@@ -3394,11 +3323,6 @@
assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-
mService.saveDirtyInfo();
// Nonexistent package.
@@ -3430,11 +3354,6 @@
assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-
mService.saveDirtyInfo();
// Remove a package.
@@ -3465,11 +3384,6 @@
assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-
mService.saveDirtyInfo();
// Remove a launcher.
@@ -3524,11 +3438,6 @@
assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-
mService.saveDirtyInfo();
// Remove the other launcher from user 10 too.
@@ -3585,6 +3494,97 @@
mService.saveDirtyInfo();
}
+
+ public void testSaveAndLoadUser_forBackup() {
+ // Create some shortcuts.
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+ });
+
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+ // Pin some.
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+ list("s1"), HANDLE_USER_0);
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+ list("s2"), UserHandle.of(USER_P0));
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+ list("s3"), HANDLE_USER_0);
+ });
+
+ runWithCaller(LAUNCHER_1, USER_P0, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+ list("s2"), HANDLE_USER_0);
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+ list("s3"), UserHandle.of(USER_P0));
+
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+ list("s1"), HANDLE_USER_0);
+ });
+
+ runWithCaller(LAUNCHER_1, USER_10, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+ list("s3"), HANDLE_USER_10);
+ });
+
+ // Check the state.
+
+ assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+ assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+ assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+ assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+ assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+ assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+ assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+ assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+ // Make sure all the information is persisted.
+ mService.saveDirtyInfo();
+ initService();
+ mService.handleUnlockUser(USER_0);
+ mService.handleUnlockUser(USER_P0);
+ mService.handleUnlockUser(USER_10);
+ }
+
public void testHandleGonePackage_crossProfile() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3667,20 +3667,6 @@
assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-
- // These two shouldn't exist
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0, USER_P0));
-
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10));
-
// Make sure all the information is persisted.
mService.saveDirtyInfo();
initService();
@@ -3704,21 +3690,6 @@
assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-
- // These two shouldn't exist
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0, USER_P0));
-
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10));
-
-
// Start uninstalling.
uninstallPackage(USER_10, LAUNCHER_1);
mService.cleanupGonePackages(USER_10);
@@ -3739,16 +3710,6 @@
assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10));
-
// Uninstall.
uninstallPackage(USER_10, CALLING_PACKAGE_1);
mService.cleanupGonePackages(USER_10);
@@ -3769,17 +3730,6 @@
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10));
-
uninstallPackage(USER_P0, LAUNCHER_1);
mService.cleanupGonePackages(USER_0);
@@ -3799,17 +3749,6 @@
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10));
-
mService.cleanupGonePackages(USER_P0);
assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
@@ -3828,17 +3767,6 @@
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10));
-
uninstallPackage(USER_P0, CALLING_PACKAGE_1);
mService.saveDirtyInfo();
@@ -3863,17 +3791,6 @@
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10));
-
// Uninstall
uninstallPackage(USER_0, LAUNCHER_1);
@@ -3899,17 +3816,6 @@
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10));
-
uninstallPackage(USER_0, CALLING_PACKAGE_2);
mService.saveDirtyInfo();
@@ -3933,17 +3839,6 @@
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_P0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_0, USER_P0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_P0, USER_P0));
- assertNull(mService.getPackageInfoForTest(LAUNCHER_1, USER_10, USER_10));
}
// TODO Detailed test for hasShortcutPermissionInner().
@@ -3998,27 +3893,6 @@
checkCanRestoreTo(false, spi2, 11, "x", "sig2x", "sig1", "y");
}
- public void testShortcutPackageInfoRefresh() {
- addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1");
-
- final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackage(
- mService, CALLING_PACKAGE_1, USER_0);
-
- checkCanRestoreTo(true, spi1, 10, "sig1");
-
- addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 11, "sig1", "sig2");
-
- spi1.refreshAndSave(mService, USER_0);
-
- mService.handleCleanupUser(USER_0);
- initService();
-
- checkCanRestoreTo(false, spi1, 10, "sig1", "sig2");
- checkCanRestoreTo(false, spi1, 11, "sig", "sig2");
- checkCanRestoreTo(false, spi1, 11, "sig1", "sig");
- checkCanRestoreTo(true, spi1, 11, "sig1", "sig2");
- }
-
public void testHandlePackageDelete() {
setCaller(CALLING_PACKAGE_1, USER_0);
assertTrue(mManager.addDynamicShortcut(makeShortcut("s1")));
@@ -4038,73 +3912,56 @@
setCaller(CALLING_PACKAGE_3, USER_10);
assertTrue(mManager.addDynamicShortcut(makeShortcut("s1")));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10));
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10));
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
mInjectedPackages.remove(CALLING_PACKAGE_1);
mInjectedPackages.remove(CALLING_PACKAGE_3);
mService.handleUnlockUser(USER_0);
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10));
+
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
mService.handleUnlockUser(USER_10);
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
- assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10));
+
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+ assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+ assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
}
public void testHandlePackageUpdate() {
- setCaller(CALLING_PACKAGE_1, USER_0);
- assertTrue(mManager.addDynamicShortcut(makeShortcut("s1")));
-
- assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
- assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode());
-
- addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 123);
-
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageUpdateIntent("abc", USER_0));
- assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode());
-
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageUpdateIntent("abc", USER_10));
- assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode());
-
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
- assertEquals(123, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)
- .getVersionCode());
+ // TODO: Make sure unshadow is called.
}
}
diff --git a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
index 52e8f37..d2a4484 100644
--- a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
@@ -24,19 +24,14 @@
}
public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
- Runnable r) {
- assertExpectException(expectedExceptionType, null, r);
- }
-
- public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
String expectedExceptionMessageRegex, Runnable r) {
try {
r.run();
- Assert.fail("Expected exception type " + expectedExceptionType.getClass().getName()
+ Assert.fail("Expected exception type " + expectedExceptionType.getName()
+ " was not thrown");
} catch (Throwable e) {
Assert.assertTrue(
- "Expected exception type was " + expectedExceptionType.getClass().getName()
+ "Expected exception type was " + expectedExceptionType.getName()
+ " but caught " + e.getClass().getName(),
expectedExceptionType.isAssignableFrom(e.getClass()));
if (expectedExceptionMessageRegex != null) {
diff --git a/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java b/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java
index cb77118..a169b18 100644
--- a/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java
+++ b/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java
@@ -1,35 +1,35 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-package com.android.locationtracker;
-
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-
-/**
- * Settings preference screen for location tracker
- */
-public class SettingsActivity extends PreferenceActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Load the preferences from an XML resource
- addPreferencesFromResource(R.xml.preferences);
- }
-
-}
+/*
+ * Copyright (C) 2008 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.
+ */
+
+package com.android.locationtracker;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+/**
+ * Settings preference screen for location tracker
+ */
+public class SettingsActivity extends PreferenceActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.preferences);
+ }
+
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java
index 55d4d1e..adc39b3 100644
--- a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java
@@ -1,75 +1,75 @@
-/*
- * Copyright (C) 2008 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.
- */
-package com.android.locationtracker.data;
-
-import android.app.ListActivity;
-import android.content.Context;
-import android.database.Cursor;
-import android.view.View;
-import android.widget.ResourceCursorAdapter;
-import android.widget.TextView;
-
-import com.android.locationtracker.R;
-
-/**
- * Used to bind Tracker data to a list view UI
- */
-public class TrackerListHelper extends TrackerDataHelper {
-
- private ListActivity mActivity;
-
- // sort entries by most recent first
- private static final String SORT_ORDER = TrackerEntry.ID_COL + " DESC";
-
- public TrackerListHelper(ListActivity activity) {
- super(activity, TrackerDataHelper.CSV_FORMATTER);
- mActivity = activity;
- }
-
- /**
- * Helper method for binding the list activities UI to the tracker data
- * Tracker data will be sorted in most-recent first order
- * Will enable automatic UI changes as tracker data changes
- *
- * @param layout - layout to populate data
- */
- public void bindListUI(int layout) {
- Cursor cursor = mActivity.managedQuery(TrackerProvider.CONTENT_URI,
- TrackerEntry.ATTRIBUTES, null, null, SORT_ORDER);
- // Used to map tracker entries from the database to views
- TrackerAdapter adapter = new TrackerAdapter(mActivity, layout, cursor);
- mActivity.setListAdapter(adapter);
- cursor.setNotificationUri(mActivity.getContentResolver(),
- TrackerProvider.CONTENT_URI);
-
- }
-
- private class TrackerAdapter extends ResourceCursorAdapter {
-
- public TrackerAdapter(Context context, int layout, Cursor c) {
- super(context, layout, c);
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- final TextView v = (TextView) view
- .findViewById(R.id.entrylist_item);
- String rowText = mFormatter.getOutput(TrackerEntry
- .createEntry(cursor));
- v.setText(rowText);
- }
- }
-}
+/*
+ * Copyright (C) 2008 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.
+ */
+package com.android.locationtracker.data;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import com.android.locationtracker.R;
+
+/**
+ * Used to bind Tracker data to a list view UI
+ */
+public class TrackerListHelper extends TrackerDataHelper {
+
+ private ListActivity mActivity;
+
+ // sort entries by most recent first
+ private static final String SORT_ORDER = TrackerEntry.ID_COL + " DESC";
+
+ public TrackerListHelper(ListActivity activity) {
+ super(activity, TrackerDataHelper.CSV_FORMATTER);
+ mActivity = activity;
+ }
+
+ /**
+ * Helper method for binding the list activities UI to the tracker data
+ * Tracker data will be sorted in most-recent first order
+ * Will enable automatic UI changes as tracker data changes
+ *
+ * @param layout - layout to populate data
+ */
+ public void bindListUI(int layout) {
+ Cursor cursor = mActivity.managedQuery(TrackerProvider.CONTENT_URI,
+ TrackerEntry.ATTRIBUTES, null, null, SORT_ORDER);
+ // Used to map tracker entries from the database to views
+ TrackerAdapter adapter = new TrackerAdapter(mActivity, layout, cursor);
+ mActivity.setListAdapter(adapter);
+ cursor.setNotificationUri(mActivity.getContentResolver(),
+ TrackerProvider.CONTENT_URI);
+
+ }
+
+ private class TrackerAdapter extends ResourceCursorAdapter {
+
+ public TrackerAdapter(Context context, int layout, Cursor c) {
+ super(context, layout, c);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ final TextView v = (TextView) view
+ .findViewById(R.id.entrylist_item);
+ String rowText = mFormatter.getOutput(TrackerEntry
+ .createEntry(cursor));
+ v.setText(rowText);
+ }
+ }
+}
diff --git a/tests/VectorDrawableTest/res/drawable/btn_radio_on_to_off_bundle.xml b/tests/VectorDrawableTest/res/drawable/btn_radio_on_to_off_bundle.xml
new file mode 100644
index 0000000..4f05090
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/btn_radio_on_to_off_bundle.xml
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="32dp"
+ android:viewportWidth="32"
+ android:height="32dp"
+ android:viewportHeight="32">
+ <group
+ android:name="btn_radio_to_off_mtrl_0"
+ android:translateX="16"
+ android:translateY="16">
+ <group
+ android:name="ring_outer">
+ <path
+ android:name="ring_outer_path"
+ android:strokeColor="#FF000000"
+ android:strokeWidth="2"
+ android:pathData="M 0.0,-9.0 c 4.9705627482,0.0 9.0,4.0294372518 9.0,9.0 c 0.0,4.9705627482 -4.0294372518,9.0 -9.0,9.0 c -4.9705627482,0.0 -9.0,-4.0294372518 -9.0,-9.0 c 0.0,-4.9705627482 4.0294372518,-9.0 9.0,-9.0 Z"/>
+ </group>
+ <group
+ android:name="dot_group">
+ <path
+ android:name="dot_path"
+ android:pathData="M 0.0,-5.0 c -2.7619934082,0.0 -5.0,2.2380065918 -5.0,5.0 c 0.0,2.7619934082 2.2380065918,5.0 5.0,5.0 c 2.7619934082,0.0 5.0,-2.2380065918 5.0,-5.0 c 0.0,-2.7619934082 -2.2380065918,-5.0 -5.0,-5.0 Z"
+ android:fillColor="#FF000000"/>
+ </group>
+ </group>
+ </vector>
+ </aapt:attr>
+ <target android:name="ring_outer">
+ <aapt:attr name="android:animation">
+ <set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="183"
+ android:propertyName="scaleX"
+ android:valueFrom="1.0"
+ android:valueTo="0.9"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:duration="16"
+ android:propertyName="scaleX"
+ android:valueFrom="0.9"
+ android:valueTo="0.5"
+ android:valueType="floatType"
+ android:interpolator="@interpolator/btn_radio_to_off_mtrl_animation_interpolator_0" />
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="scaleX"
+ android:valueFrom="0.5"
+ android:valueTo="1.0"
+ android:valueType="floatType"
+ android:interpolator="@interpolator/btn_radio_to_off_mtrl_animation_interpolator_0" />
+ </set>
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="183"
+ android:propertyName="scaleY"
+ android:valueFrom="1.0"
+ android:valueTo="0.9"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:duration="16"
+ android:propertyName="scaleY"
+ android:valueFrom="0.9"
+ android:valueTo="0.5"
+ android:valueType="floatType"
+ android:interpolator="@interpolator/btn_radio_to_off_mtrl_animation_interpolator_0" />
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="scaleY"
+ android:valueFrom="0.5"
+ android:valueTo="1.0"
+ android:valueType="floatType"
+ android:interpolator="@interpolator/btn_radio_to_off_mtrl_animation_interpolator_0" />
+ </set>
+ </set>
+ </aapt:attr>
+ </target>
+
+ <target android:name="ring_outer_path">
+ <aapt:attr name="android:animation">
+ <set
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <set
+ android:ordering="sequentially">
+ <objectAnimator
+ android:duration="183"
+ android:propertyName="strokeWidth"
+ android:valueFrom="2.0"
+ android:valueTo="2.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in"/>
+ <objectAnimator
+ android:duration="16"
+ android:propertyName="strokeWidth"
+ android:valueFrom="2.0"
+ android:valueTo="18.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in"/>
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="strokeWidth"
+ android:valueFrom="18.0"
+ android:valueTo="2.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in"/>
+ </set>
+
+ </set>
+ </aapt:attr>
+ </target>
+ <target
+ android:name="dot_group">
+ <aapt:attr name="android:animation">
+ <set
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <set
+ android:ordering="sequentially">
+ <objectAnimator
+ android:duration="183"
+ android:propertyName="scaleX"
+ android:valueFrom="1.0"
+ android:valueTo="1.4"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in"/>
+ <objectAnimator
+ android:duration="16"
+ android:propertyName="scaleX"
+ android:valueFrom="1.4"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in"/>
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="scaleX"
+ android:valueFrom="0.0"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in"/>
+ </set>
+ <set
+ android:ordering="sequentially">
+ <objectAnimator
+ android:duration="183"
+ android:propertyName="scaleY"
+ android:valueFrom="1.0"
+ android:valueTo="1.4"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in"/>
+ <objectAnimator
+ android:duration="16"
+ android:propertyName="scaleY"
+ android:valueFrom="1.4"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in"/>
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="scaleY"
+ android:valueFrom="0.0"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in"/>
+ </set>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml
index 96fd70e..a6da114 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml
@@ -17,7 +17,7 @@
android:height="4dp"
android:viewportHeight="4"
android:viewportWidth="360"
- android:width="360dp" >
+ android:width="36dp" >
<group
android:name="linear_indeterminate"
diff --git a/tests/VectorDrawableTest/res/interpolator/btn_radio_to_off_mtrl_animation_interpolator_0.xml b/tests/VectorDrawableTest/res/interpolator/btn_radio_to_off_mtrl_animation_interpolator_0.xml
new file mode 100644
index 0000000..d3728c4
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/btn_radio_to_off_mtrl_animation_interpolator_0.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 c 0.4,0.0 0.4,1.0 1.0,1.0" />
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
index 087e68a..4026e5e 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
@@ -29,6 +29,7 @@
private static final String LOGCAT = "AnimatedVectorDrawableTest";
protected int[] icon = {
+ R.drawable.btn_radio_on_to_off_bundle,
R.drawable.ic_rotate_2_portrait_v2_animation,
R.drawable.ic_signal_airplane_v2_animation,
R.drawable.ic_hourglass_animation,
@@ -43,33 +44,37 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
+ final int[] layerTypes = {View.LAYER_TYPE_SOFTWARE, View.LAYER_TYPE_HARDWARE};
super.onCreate(savedInstanceState);
ScrollView scrollView = new ScrollView(this);
GridLayout container = new GridLayout(this);
scrollView.addView(container);
- container.setColumnCount(1);
+ container.setColumnCount(2);
for (int i = 0; i < icon.length; i++) {
- Button button = new Button(this);
- button.setWidth(400);
- button.setHeight(400);
- button.setBackgroundResource(icon[i]);
- AnimatedVectorDrawable d = (AnimatedVectorDrawable) button.getBackground();
- d.registerAnimationCallback(new Animatable2.AnimationCallback() {
- @Override
- public void onAnimationStart(Drawable drawable) {
- Log.v(LOGCAT, "Animator start");
- }
+ for (int j = 0; j < layerTypes.length; j++) {
+ Button button = new Button(this);
+ button.setWidth(400);
+ button.setHeight(400);
+ button.setLayerType(layerTypes[j], null);
+ button.setBackgroundResource(icon[i]);
+ AnimatedVectorDrawable d = (AnimatedVectorDrawable) button.getBackground();
+ d.registerAnimationCallback(new Animatable2.AnimationCallback() {
+ @Override
+ public void onAnimationStart(Drawable drawable) {
+ Log.v(LOGCAT, "Animator start");
+ }
- @Override
- public void onAnimationEnd(Drawable drawable) {
+ @Override
+ public void onAnimationEnd(Drawable drawable) {
Log.v(LOGCAT, "Animator end");
- }
- });
+ }
+ });
- container.addView(button);
- button.setOnClickListener(this);
+ container.addView(button);
+ button.setOnClickListener(this);
+ }
}
setContentView(scrollView);
diff --git a/tools/aapt2/integration-tests/StaticLibOne/Android.mk b/tools/aapt2/integration-tests/StaticLibOne/Android.mk
index d59dc60..0b7129a 100644
--- a/tools/aapt2/integration-tests/StaticLibOne/Android.mk
+++ b/tools/aapt2/integration-tests/StaticLibOne/Android.mk
@@ -22,4 +22,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+# We need this to compile the Java sources of AaptTestStaticLibTwo using javac.
+LOCAL_JAR_EXCLUDE_FILES := none
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 53adb41..5a6a00f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -97,7 +97,6 @@
@Override
public void repositionChild(IWindow window, int left, int top, int right, int bottom,
- int requestedWidth, int requestedHeight,
long deferTransactionUntilFrame, Rect outFrame) {
// pass for now.
return;
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 9f8af6e..8d5efba 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -320,7 +320,8 @@
if (!TextUtils.isEmpty(unicode)) {
return "\"" + unicode + "\"";
} else {
- return mWifiSsid.getHexString();
+ String hex = mWifiSsid.getHexString();
+ return (hex != null) ? hex : WifiSsid.NONE;
}
}
return WifiSsid.NONE;
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 508bdff..73ddbbc 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -27,6 +27,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.WorkSource;
import android.util.Log;
import android.util.SparseArray;
@@ -160,6 +161,11 @@
*/
public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
+
+ /** {@hide} */
+ public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
+ /** {@hide} */
+ public static final String SCAN_PARAMS_WORK_SOURCE_KEY = "WorkSource";
/**
* scan configuration parameters to be sent to {@link #startBackgroundScan}
*/
@@ -661,12 +667,29 @@
* scans should also not share this object.
*/
public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
+ startBackgroundScan(settings, listener, null);
+ }
+
+ /** start wifi scan in background
+ * @param settings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param workSource WorkSource to blame for power usage
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ */
+ public void startBackgroundScan(ScanSettings settings, ScanListener listener,
+ WorkSource workSource) {
Preconditions.checkNotNull(listener, "listener cannot be null");
int key = addListener(listener);
if (key == INVALID_KEY) return;
validateChannel();
- sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, settings);
+ Bundle scanParams = new Bundle();
+ scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
+ scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
+ sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, scanParams);
}
+
/**
* stop an ongoing wifi scan
* @param listener specifies which scan to cancel; must be same object as passed in {@link
@@ -698,11 +721,27 @@
* scans should also not share this object.
*/
public void startScan(ScanSettings settings, ScanListener listener) {
+ startScan(settings, listener, null);
+ }
+
+ /**
+ * starts a single scan and reports results asynchronously
+ * @param settings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param workSource WorkSource to blame for power usage
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ */
+ public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
Preconditions.checkNotNull(listener, "listener cannot be null");
int key = addListener(listener);
if (key == INVALID_KEY) return;
validateChannel();
- sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, settings);
+ Bundle scanParams = new Bundle();
+ scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
+ scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
+ sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
}
/**
@@ -721,7 +760,6 @@
private void startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key) {
// Bundle up both the settings and send it across.
Bundle pnoParams = new Bundle();
- if (pnoParams == null) return;
// Set the PNO scan flag.
scanSettings.isPnoScan = true;
pnoParams.putParcelable(PNO_PARAMS_SCAN_SETTINGS_KEY, scanSettings);
diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java
index f8ba95d..c53cd3c 100644
--- a/wifi/java/android/net/wifi/WifiSsid.java
+++ b/wifi/java/android/net/wifi/WifiSsid.java
@@ -205,7 +205,7 @@
for (int i = 0; i < octets.size(); i++) {
out += String.format(Locale.US, "%02x", ssidbytes[i]);
}
- return out;
+ return (octets.size() > 0) ? out : null;
}
/** Implement the Parcelable interface {@hide} */