diff options
23 files changed, 557 insertions, 100 deletions
diff --git a/api/current.txt b/api/current.txt index 1a2774aa03c8..17182dbde4ce 100644 --- a/api/current.txt +++ b/api/current.txt @@ -36005,10 +36005,11 @@ package android.telecom { field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 field public static final int PROPERTY_CONFERENCE = 1; // 0x1 field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4 + field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20 field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2 field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10 field public static final int PROPERTY_WIFI = 8; // 0x8 - field public static final int PROPERTY_WORK_CALL = 32; // 0x20 + field public static final deprecated int PROPERTY_WORK_CALL = 32; // 0x20 } public final class CallAudioState implements android.os.Parcelable { diff --git a/api/system-current.txt b/api/system-current.txt index 0fb1826fda81..9bccb9ebc837 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -38841,10 +38841,11 @@ package android.telecom { field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 field public static final int PROPERTY_CONFERENCE = 1; // 0x1 field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4 + field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20 field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2 field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10 field public static final int PROPERTY_WIFI = 8; // 0x8 - field public static final int PROPERTY_WORK_CALL = 32; // 0x20 + field public static final deprecated int PROPERTY_WORK_CALL = 32; // 0x20 } public static abstract deprecated class Call.Listener extends android.telecom.Call.Callback { diff --git a/api/test-current.txt b/api/test-current.txt index b14d60117488..13a96e013670 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -36082,10 +36082,11 @@ package android.telecom { field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 field public static final int PROPERTY_CONFERENCE = 1; // 0x1 field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4 + field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20 field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2 field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10 field public static final int PROPERTY_WIFI = 8; // 0x8 - field public static final int PROPERTY_WORK_CALL = 32; // 0x20 + field public static final deprecated int PROPERTY_WORK_CALL = 32; // 0x20 } public final class CallAudioState implements android.os.Parcelable { diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 29e5b5db5b69..c1aac8584acb 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1485,6 +1485,16 @@ public final class Configuration implements Parcelable, Comparable<Configuration } /** + * @hide + * + * Clears the locale without changing layout direction. + */ + public void clearLocales() { + mLocaleList = LocaleList.getEmptyLocaleList(); + locale = null; + } + + /** * Return the layout direction. Will be either {@link View#LAYOUT_DIRECTION_LTR} or * {@link View#LAYOUT_DIRECTION_RTL}. * diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 66d487fd3105..cbe98f7cd6e5 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2241,7 +2241,7 @@ public final class Settings { public static void clearConfiguration(Configuration inoutConfig) { inoutConfig.fontScale = 0; if (!inoutConfig.userSetLocale && !inoutConfig.getLocales().isEmpty()) { - inoutConfig.setLocales(LocaleList.getEmptyLocaleList()); + inoutConfig.clearLocales(); } } @@ -3562,6 +3562,7 @@ public final class Settings { DTMF_TONE_TYPE_WHEN_DIALING, HEARING_AID, TTY_MODE, + MASTER_MONO, SOUND_EFFECTS_ENABLED, HAPTIC_FEEDBACK_ENABLED, POWER_SOUNDS_ENABLED, // moved to global diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index a3eed70c47b6..fc120eb0e65f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -1939,6 +1939,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } + + // PasswordTransformationMethod always have LTR text direction heuristics returned by + // getTextDirectionHeuristic, needs reset + mTextDir = getTextDirectionHeuristic(); } /** diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 937fc75046cb..93dc625f8846 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -8873,8 +8873,6 @@ public class BatteryStatsImpl extends BatteryStats { return; } - // Record whether we've seen a non-zero time (for debugging b/22716723). - boolean seenNonZeroTime = false; for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) { String name = ent.getKey(); KernelWakelockStats.Entry kws = ent.getValue(); @@ -8884,27 +8882,24 @@ public class BatteryStatsImpl extends BatteryStats { kwlt = new SamplingTimer(mClocks, mOnBatteryScreenOffTimeBase); mKernelWakelockStats.put(name, kwlt); } + kwlt.update(kws.mTotalTime, kws.mCount); kwlt.setUpdateVersion(kws.mVersion); - - if (kws.mVersion != wakelockStats.kernelWakelockVersion) { - seenNonZeroTime |= kws.mTotalTime > 0; - } } int numWakelocksSetStale = 0; - if (wakelockStats.size() != mKernelWakelockStats.size()) { - // Set timers to stale if they didn't appear in /proc/wakelocks this time. - for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { - SamplingTimer st = ent.getValue(); - if (st.getUpdateVersion() != wakelockStats.kernelWakelockVersion) { - st.endSample(); - numWakelocksSetStale++; - } + // Set timers to stale if they didn't appear in /d/wakeup_sources (or /proc/wakelocks) + // this time. + for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { + SamplingTimer st = ent.getValue(); + if (st.getUpdateVersion() != wakelockStats.kernelWakelockVersion) { + st.endSample(); + numWakelocksSetStale++; } } - if (!seenNonZeroTime) { + // Record whether we've seen a non-zero time (for debugging b/22716723). + if (wakelockStats.isEmpty()) { Slog.wtf(TAG, "All kernel wakelocks had time of zero"); } diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java index 6654ea5683e3..8036f257823b 100644 --- a/core/java/com/android/internal/os/KernelWakelockReader.java +++ b/core/java/com/android/internal/os/KernelWakelockReader.java @@ -18,6 +18,8 @@ package com.android.internal.os; import android.os.Process; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; + import java.io.FileInputStream; import java.util.Iterator; @@ -106,14 +108,14 @@ public class KernelWakelockReader { /** * Reads the wakelocks and updates the staleStats with the new information. */ - private KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources, - final KernelWakelockStats staleStats) { + @VisibleForTesting + public KernelWakelockStats parseProcWakelocks(byte[] wlBuffer, int len, boolean wakeup_sources, + final KernelWakelockStats staleStats) { String name; int count; long totalTime; int startIndex; int endIndex; - int numUpdatedWlNames = 0; // Advance past the first line. int i; @@ -126,11 +128,10 @@ public class KernelWakelockReader { for (endIndex=startIndex; endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0'; endIndex++); - endIndex++; // endIndex is an exclusive upper bound. // Don't go over the end of the buffer, Process.parseProcLine might // write to wlBuffer[endIndex] - if (endIndex >= (len - 1) ) { - return staleStats; + if (endIndex > (len - 1) ) { + break; } String[] nameStringArray = mProcWakelocksName; @@ -161,7 +162,6 @@ public class KernelWakelockReader { if (!staleStats.containsKey(name)) { staleStats.put(name, new KernelWakelockStats.Entry(count, totalTime, sKernelWakelockUpdateVersion)); - numUpdatedWlNames++; } else { KernelWakelockStats.Entry kwlStats = staleStats.get(name); if (kwlStats.mVersion == sKernelWakelockUpdateVersion) { @@ -171,7 +171,6 @@ public class KernelWakelockReader { kwlStats.mCount = count; kwlStats.mTotalTime = totalTime; kwlStats.mVersion = sKernelWakelockUpdateVersion; - numUpdatedWlNames++; } } } else if (!parsed) { @@ -182,16 +181,14 @@ public class KernelWakelockReader { Slog.wtf(TAG, "Failed to parse proc line!"); } } - startIndex = endIndex; + startIndex = endIndex + 1; } - if (staleStats.size() != numUpdatedWlNames) { - // Don't report old data. - Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator(); - while (itr.hasNext()) { - if (itr.next().mVersion != sKernelWakelockUpdateVersion) { - itr.remove(); - } + // Don't report old data. + Iterator<KernelWakelockStats.Entry> itr = staleStats.values().iterator(); + while (itr.hasNext()) { + if (itr.next().mVersion != sKernelWakelockUpdateVersion) { + itr.remove(); } } diff --git a/core/tests/coretests/src/com/android/internal/os/KernelWakelockReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelWakelockReaderTest.java new file mode 100644 index 000000000000..4e4bb350739d --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/KernelWakelockReaderTest.java @@ -0,0 +1,147 @@ +/* + * 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.internal.os; + +import android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import java.nio.charset.Charset; + +public class KernelWakelockReaderTest extends TestCase { + /** + * Helper class that builds the mock Kernel module file /d/wakeup_sources. + */ + private static class ProcFileBuilder { + private final static String sHeader = "name\t\tactive_count\tevent_count\twakeup_count\t" + + "expire_count\tactive_since\ttotal_time\tmax_time\tlast_change\t" + + "prevent_suspend_time\n"; + + private StringBuilder mStringBuilder; + + private void ensureHeader() { + if (mStringBuilder == null) { + mStringBuilder = new StringBuilder(); + mStringBuilder.append(sHeader); + } + } + + public ProcFileBuilder addLine(String name, int count, long timeMillis) { + ensureHeader(); + mStringBuilder.append(name).append("\t").append(count).append("\t0\t0\t0\t0\t") + .append(timeMillis).append("\t0\t0\t0\n"); + return this; + } + + public byte[] getBytes() throws Exception { + ensureHeader(); + byte[] data = mStringBuilder.toString().getBytes(Charset.forName("UTF-8")); + + // The Kernel puts a \0 at the end of the data. Since each of our lines ends with \n, + // we override the last \n with a \0. + data[data.length - 1] = 0; + return data; + } + } + + private KernelWakelockReader mReader; + + @Override + public void setUp() throws Exception { + super.setUp(); + mReader = new KernelWakelockReader(); + } + + @SmallTest + public void testParseEmptyFile() throws Exception { + KernelWakelockStats staleStats = mReader.parseProcWakelocks(new byte[0], 0, true, + new KernelWakelockStats()); + assertTrue(staleStats.isEmpty()); + } + + @SmallTest + public void testOnlyHeader() throws Exception { + byte[] buffer = new ProcFileBuilder().getBytes(); + KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true, + new KernelWakelockStats()); + assertTrue(staleStats.isEmpty()); + } + + @SmallTest + public void testOneWakelock() throws Exception { + byte[] buffer = new ProcFileBuilder() + .addLine("Wakelock", 34, 123) // Milliseconds + .getBytes(); + KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true, + new KernelWakelockStats()); + assertEquals(1, staleStats.size()); + assertTrue(staleStats.containsKey("Wakelock")); + + KernelWakelockStats.Entry entry = staleStats.get("Wakelock"); + assertEquals(34, entry.mCount); + assertEquals(123 * 1000, entry.mTotalTime); // Microseconds + } + + @SmallTest + public void testTwoWakelocks() throws Exception { + byte[] buffer = new ProcFileBuilder() + .addLine("Wakelock", 1, 10) + .addLine("Fakelock", 2, 20) + .getBytes(); + KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true, + new KernelWakelockStats()); + assertEquals(2, staleStats.size()); + assertTrue(staleStats.containsKey("Wakelock")); + assertTrue(staleStats.containsKey("Fakelock")); + } + + @SmallTest + public void testDuplicateWakelocksAccumulate() throws Exception { + byte[] buffer = new ProcFileBuilder() + .addLine("Wakelock", 1, 10) // Milliseconds + .addLine("Wakelock", 1, 10) // Milliseconds + .getBytes(); + KernelWakelockStats staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true, + new KernelWakelockStats()); + assertEquals(1, staleStats.size()); + assertTrue(staleStats.containsKey("Wakelock")); + + KernelWakelockStats.Entry entry = staleStats.get("Wakelock"); + assertEquals(2, entry.mCount); + assertEquals(20 * 1000, entry.mTotalTime); // Microseconds + } + + @SmallTest + public void testWakelocksBecomeStale() throws Exception { + byte[] buffer = new ProcFileBuilder() + .addLine("Fakelock", 3, 30) + .getBytes(); + KernelWakelockStats staleStats = new KernelWakelockStats(); + + staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true, staleStats); + assertEquals(1, staleStats.size()); + assertTrue(staleStats.containsKey("Fakelock")); + + buffer = new ProcFileBuilder() + .addLine("Wakelock", 1, 10) + .getBytes(); + + staleStats = mReader.parseProcWakelocks(buffer, buffer.length, true, staleStats); + assertEquals(1, staleStats.size()); + assertTrue(staleStats.containsKey("Wakelock")); + assertFalse(staleStats.containsKey("Fakelock")); + } +} diff --git a/docs/html/preview/features/afw.jd b/docs/html/preview/features/afw.jd index dc53bd977f2f..9b94c079cb92 100644 --- a/docs/html/preview/features/afw.jd +++ b/docs/html/preview/features/afw.jd @@ -515,7 +515,7 @@ Android N.</p> <p> The dialer should check for the new flag - <code>android.telecom.Call.PROPERTY_WORK_CALL</code> to determine if a call + <code>android.telecom.Call.PROPERTY_ENTERPRISE_CALL</code> to determine if a call is a work call. If a call is a work call, the dialer should indicate this, such as by adding a work badge. </p> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 029a1259cd1b..f8fb1e579446 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -329,7 +329,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { final HashSet<Network> mKnownNetworks = new HashSet<Network>(); final ArrayList<Network> mNetworks = new ArrayList<Network>(8); - public void readNetworks(BufferedReader in, List<WifiConfiguration> whitelist) { + public void readNetworks(BufferedReader in, List<WifiConfiguration> whitelist, + boolean acceptEapNetworks) { try { String line; while (in.ready()) { @@ -348,7 +349,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } // Don't propagate EAP network definitions - if (net.isEap) { + if (net.isEap && !acceptEapNetworks) { if (DEBUG_BACKUP) { Log.v(TAG, "Skipping EAP network " + net.ssid + " / " + net.key_mgmt); } @@ -1176,7 +1177,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { WifiNetworkSettings fromFile = new WifiNetworkSettings(); br = new BufferedReader(new FileReader(file)); - fromFile.readNetworks(br, configs); + fromFile.readNetworks(br, configs, false); // Write the parsed networks into a packed byte array if (fromFile.mKnownNetworks.size() > 0) { @@ -1204,7 +1205,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (supplicantFile.exists()) { // Retain the existing APs; we'll append the restored ones to them BufferedReader in = new BufferedReader(new FileReader(FILE_WIFI_SUPPLICANT)); - supplicantImage.readNetworks(in, null); + supplicantImage.readNetworks(in, null, true); in.close(); supplicantFile.delete(); @@ -1215,7 +1216,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { char[] restoredAsBytes = new char[size]; for (int i = 0; i < size; i++) restoredAsBytes[i] = (char) bytes[i]; BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsBytes)); - supplicantImage.readNetworks(in, null); + supplicantImage.readNetworks(in, null, false); if (DEBUG_BACKUP) { Log.v(TAG, "Final AP list:"); @@ -1371,4 +1372,4 @@ public class SettingsBackupAgent extends BackupAgentHelper { } return WifiManager.WIFI_STATE_UNKNOWN; } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml index 36e937d4d3ef..8ba4c9c05ba6 100644 --- a/packages/SystemUI/res/layout/navigation_bar.xml +++ b/packages/SystemUI/res/layout/navigation_bar.xml @@ -27,12 +27,6 @@ <com.android.systemui.statusbar.phone.NavigationBarInflaterView android:id="@+id/navigation_inflater" android:layout_width="match_parent" - android:layout_height="match_parent"> - - <include android:id="@+id/rot0" layout="@layout/navigation_layout" /> - - <include android:id="@+id/rot90" layout="@layout/navigation_layout_rot90" /> - - </com.android.systemui.statusbar.phone.NavigationBarInflaterView> + android:layout_height="match_parent" /> </com.android.systemui.statusbar.phone.NavigationBarView> 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 eaf375eefdb0..61a92b468b48 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -142,11 +142,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } } mTileDividerIndex = mTiles.size(); - if (mOtherTiles.size() != 0) { - mTiles.add(null); - } + mTiles.add(null); mTiles.addAll(mOtherTiles); - mEditIndex = mTiles.indexOf(null); + updateDividerLocations(); notifyDataSetChanged(); } @@ -203,6 +201,8 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta @Override public void onBindViewHolder(final Holder holder, int position) { if (holder.getItemViewType() == TYPE_DIVIDER) { + holder.itemView.setVisibility(mTileDividerIndex < mTiles.size() - 1 ? View.VISIBLE + : View.INVISIBLE); return; } if (holder.getItemViewType() == TYPE_EDIT) { @@ -340,9 +340,8 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta to = mTileDividerIndex; } } else { - if (mTileDividerIndex == mTiles.size()) { - notifyItemInserted(mTiles.size()); - mTiles.add(null); + if (mTileDividerIndex == mTiles.size() - 1) { + notifyItemChanged(mTileDividerIndex); } if (to <= mTileDividerIndex) { to = mTileDividerIndex; @@ -351,7 +350,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } else { if (to > mEditIndex) { // Don't allow tiles to be dragged around when they aren't added. - return false; + to = from; } // Allow the case where to == mEditIndex to fall through and swap which // side the tile is currently on. @@ -362,6 +361,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta CharSequence fromLabel = mTiles.get(from).state.label; move(from, to, mTiles); updateDividerLocations(); + if (to == from) { + return true; + } CharSequence announcement; if (to >= mEditIndex) { MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE_SPEC, @@ -405,12 +407,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } } if (mTiles.size() - 1 == mTileDividerIndex) { - mTiles.remove(mTiles.size() - 1); - notifyItemRemoved(mTiles.size()); + notifyItemChanged(mTileDividerIndex); } } - private String strip(TileInfo tileInfo) { + private static String strip(TileInfo tileInfo) { String spec = tileInfo.spec; if (spec.startsWith(CustomTile.PREFIX)) { ComponentName component = CustomTile.getComponentFromSpec(spec); @@ -420,8 +421,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } 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); + list.add(to, list.remove(from)); notifyItemMoved(from, to); notifyItemChanged(to); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java index 4ec36f608111..2bee816b0539 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java @@ -24,7 +24,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Space; import com.android.systemui.R; @@ -90,6 +89,7 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi if (mDensity != newConfig.densityDpi) { mDensity = newConfig.densityDpi; createInflaters(); + inflateChildren(); clearViews(); inflateLayout(mCurrentLayout); } @@ -98,12 +98,25 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi @Override protected void onFinishInflate() { super.onFinishInflate(); - mRot0 = (FrameLayout) findViewById(R.id.rot0); - mRot90 = (FrameLayout) findViewById(R.id.rot90); + inflateChildren(); clearViews(); inflateLayout(getDefaultLayout()); } + private void inflateChildren() { + removeAllViews(); + mRot0 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout, this, false); + mRot0.setId(R.id.rot0); + addView(mRot0); + mRot90 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_rot90, this, + false); + mRot90.setId(R.id.rot90); + addView(mRot90); + if (getParent() instanceof NavigationBarView) { + ((NavigationBarView) getParent()).updateRotatedViews(); + } + } + protected String getDefaultLayout() { return mContext.getString(R.string.config_navBarLayout); } @@ -123,9 +136,6 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi @Override public void onTuningChanged(String key, String newValue) { if (NAV_BAR_VIEWS.equals(key)) { - if (newValue == null) { - newValue = getDefaultLayout(); - } if (!Objects.equals(mCurrentLayout, newValue)) { clearViews(); inflateLayout(newValue); @@ -162,6 +172,9 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi protected void inflateLayout(String newLayout) { mCurrentLayout = newLayout; + if (newLayout == null) { + newLayout = getDefaultLayout(); + } String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3); String[] start = sets[0].split(BUTTON_SEPARATOR); String[] center = sets[1].split(BUTTON_SEPARATOR); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 4b7d56b27169..5fab79692135 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -492,17 +492,7 @@ public class NavigationBarView extends LinearLayout { @Override public void onFinishInflate() { - mRotatedViews[Surface.ROTATION_0] = - mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); - - mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90); - - mRotatedViews[Surface.ROTATION_270] = mRotatedViews[Surface.ROTATION_90]; - - mCurrentView = mRotatedViews[Surface.ROTATION_0]; - for (int i = 0; i < mButtonDisatchers.size(); i++) { - mButtonDisatchers.valueAt(i).setCurrentView(mCurrentView); - } + updateRotatedViews(); ((NavigationBarInflaterView) findViewById(R.id.navigation_inflater)).setButtonDispatchers( mButtonDisatchers); @@ -544,15 +534,16 @@ public class NavigationBarView extends LinearLayout { } } - private void updateRecentsIcon() { - getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon); - } + void updateRotatedViews() { + mRotatedViews[Surface.ROTATION_0] = + mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); + mRotatedViews[Surface.ROTATION_270] = + mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90); - public boolean isVertical() { - return mVertical; + updateCurrentView(); } - public void reorient() { + private void updateCurrentView() { final int rot = mDisplay.getRotation(); for (int i=0; i<4; i++) { mRotatedViews[i].setVisibility(View.GONE); @@ -563,6 +554,18 @@ public class NavigationBarView extends LinearLayout { mButtonDisatchers.valueAt(i).setCurrentView(mCurrentView); } updateLayoutTransitionsEnabled(); + } + + private void updateRecentsIcon() { + getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon); + } + + public boolean isVertical() { + return mVertical; + } + + public void reorient() { + updateCurrentView(); getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index ad57ae2cecb3..dc4d7b1d8a05 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -1396,11 +1396,18 @@ class ActivityStarter { final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId()); intentActivity = task != null ? task.getTopActivity() : null; } else if (putIntoExistingTask) { - // See if there is a task to bring to the front. If this is a SINGLE_INSTANCE - // activity, there can be one and only one instance of it in the history, and it is - // always in its own unique task, so we do a special search. - intentActivity = mLaunchSingleInstance ? mSupervisor.findActivityLocked(mIntent, mStartActivity.info) - : mSupervisor.findTaskLocked(mStartActivity); + if (mLaunchSingleInstance) { + // There can be one and only one instance of single instance activity in the + // history, and it is always in its own unique task, so we do a special search. + intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info); + } else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) { + // For the launch adjacent case we only want to put the activity in an existing + // task if the activity already exists in the history. + intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info); + } else { + // Otherwise find the best task to put the activity in. + intentActivity = mSupervisor.findTaskLocked(mStartActivity); + } } return intentActivity; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index fe6ecbd0a770..04be34a81579 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -92,6 +92,7 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; @@ -174,6 +175,7 @@ public class NotificationManagerService extends SystemService { = SystemProperties.getBoolean("debug.child_notifs", true); static final int MAX_PACKAGE_NOTIFICATIONS = 50; + static final float MAX_PACKAGE_ENQUEUE_RATE = 50f; // message codes static final int MESSAGE_TIMEOUT = 2; @@ -216,6 +218,7 @@ public class NotificationManagerService extends SystemService { /** notification_enqueue status value for an ignored notification. */ private static final int EVENTLOG_ENQUEUE_STATUS_IGNORED = 2; + private static final long MIN_PACKAGE_OVERRATE_LOG_INTERVAL = 5000; // milliseconds private String mRankerServicePackageName; private IActivityManager mAm; @@ -295,6 +298,7 @@ public class NotificationManagerService extends SystemService { private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); private RankingHandler mRankingHandler; + private long mLastOverRateLogTime; private static class Archive { final int mBufferSize; @@ -2500,6 +2504,18 @@ public class NotificationManagerService extends SystemService { // package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks. if (!isSystemNotification && !isNotificationFromListener) { synchronized (mNotificationList) { + final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg); + if (appEnqueueRate > MAX_PACKAGE_ENQUEUE_RATE) { + mUsageStats.registerOverRateQuota(pkg); + final long now = SystemClock.elapsedRealtime(); + if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) { + Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate + + ". Shedding events. package=" + pkg); + mLastOverRateLogTime = now; + } + return; + } + int count = 0; final int N = mNotificationList.size(); for (int i=0; i<N; i++) { @@ -2510,6 +2526,7 @@ public class NotificationManagerService extends SystemService { } count++; if (count >= MAX_PACKAGE_NOTIFICATIONS) { + mUsageStats.registerOverCountQuota(pkg); Slog.e(TAG, "Package has already posted " + count + " notifications. Not showing more. package=" + pkg); return; diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index b853417ac0b3..00d7a7b9e0cb 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -29,6 +29,7 @@ import android.os.HandlerThread; import android.os.Message; import android.os.SystemClock; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; import com.android.internal.logging.MetricsLogger; @@ -74,6 +75,7 @@ public class NotificationUsageStats { // Guarded by synchronized(this). private final Map<String, AggregatedStats> mStats = new HashMap<>(); private final ArrayDeque<AggregatedStats[]> mStatsArrays = new ArrayDeque<>(); + private ArraySet<String> mStatExpiredkeys = new ArraySet<>(); private final SQLiteLog mSQLiteLog; private final Context mContext; private final Handler mHandler; @@ -102,12 +104,26 @@ public class NotificationUsageStats { /** * Called when a notification has been posted. */ + public synchronized float getAppEnqueueRate(String packageName) { + AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName); + if (stats != null) { + return stats.getEnqueueRate(SystemClock.elapsedRealtime()); + } else { + return 0f; + } + } + + /** + * Called when a notification has been posted. + */ public synchronized void registerPostedByApp(NotificationRecord notification) { - notification.stats.posttimeElapsedMs = SystemClock.elapsedRealtime(); + final long now = SystemClock.elapsedRealtime(); + notification.stats.posttimeElapsedMs = now; AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification); for (AggregatedStats stats : aggregatedStatsArray) { stats.numPostedByApp++; + stats.updateInterarrivalEstimate(now); stats.countApiUse(notification); } releaseAggregatedStatsLocked(aggregatedStatsArray); @@ -124,6 +140,7 @@ public class NotificationUsageStats { AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification); for (AggregatedStats stats : aggregatedStatsArray) { stats.numUpdatedByApp++; + stats.updateInterarrivalEstimate(SystemClock.elapsedRealtime()); stats.countApiUse(notification); } releaseAggregatedStatsLocked(aggregatedStatsArray); @@ -206,18 +223,37 @@ public class NotificationUsageStats { releaseAggregatedStatsLocked(aggregatedStatsArray); } + public synchronized void registerOverRateQuota(String packageName) { + AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName); + for (AggregatedStats stats : aggregatedStatsArray) { + stats.numRateViolations++; + } + } + + public synchronized void registerOverCountQuota(String packageName) { + AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName); + for (AggregatedStats stats : aggregatedStatsArray) { + stats.numQuotaViolations++; + } + } + // Locked by this. private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) { + return getAggregatedStatsLocked(record.sbn.getPackageName()); + } + + // Locked by this. + private AggregatedStats[] getAggregatedStatsLocked(String packageName) { if (!ENABLE_AGGREGATED_IN_MEMORY_STATS) { return EMPTY_AGGREGATED_STATS; } - // TODO: expand to package-level counts in the future. AggregatedStats[] array = mStatsArrays.poll(); if (array == null) { - array = new AggregatedStats[1]; + array = new AggregatedStats[2]; } array[0] = getOrCreateAggregatedStatsLocked(DEVICE_GLOBAL_STATS); + array[1] = getOrCreateAggregatedStatsLocked(packageName); return array; } @@ -236,6 +272,7 @@ public class NotificationUsageStats { result = new AggregatedStats(mContext, key); mStats.put(key, result); } + result.mLastAccessTime = SystemClock.elapsedRealtime(); return result; } @@ -272,6 +309,7 @@ public class NotificationUsageStats { as.dump(pw, indent); } pw.println(indent + "mStatsArrays.size(): " + mStatsArrays.size()); + pw.println(indent + "mStats.size(): " + mStats.size()); } if (ENABLE_SQLITE_LOG) { mSQLiteLog.dump(pw, indent, filter); @@ -279,12 +317,20 @@ public class NotificationUsageStats { } public synchronized void emit() { - // TODO: expand to package-level counts in the future. AggregatedStats stats = getOrCreateAggregatedStatsLocked(DEVICE_GLOBAL_STATS); stats.emit(); - mLastEmitTime = SystemClock.elapsedRealtime(); mHandler.removeMessages(MSG_EMIT); mHandler.sendEmptyMessageDelayed(MSG_EMIT, EMIT_PERIOD); + for(String key: mStats.keySet()) { + if (mStats.get(key).mLastAccessTime < mLastEmitTime) { + mStatExpiredkeys.add(key); + } + } + for(String key: mStatExpiredkeys) { + mStats.remove(key); + } + mStatExpiredkeys.clear(); + mLastEmitTime = SystemClock.elapsedRealtime(); } /** @@ -326,6 +372,10 @@ public class NotificationUsageStats { public ImportanceHistogram noisyImportance; public ImportanceHistogram quietImportance; public ImportanceHistogram finalImportance; + public RateEstimator enqueueRate; + public int numRateViolations; + public int numQuotaViolations; + public long mLastAccessTime; public AggregatedStats(Context context, String key) { this.key = key; @@ -334,6 +384,7 @@ public class NotificationUsageStats { noisyImportance = new ImportanceHistogram(context, "note_imp_noisy_"); quietImportance = new ImportanceHistogram(context, "note_imp_quiet_"); finalImportance = new ImportanceHistogram(context, "note_importance_"); + enqueueRate = new RateEstimator(mCreated); } public AggregatedStats getPrevious() { @@ -444,6 +495,8 @@ public class NotificationUsageStats { maybeCount("note_text", (numWithText - previous.numWithText)); maybeCount("note_sub_text", (numWithSubText - previous.numWithSubText)); maybeCount("note_info_text", (numWithInfoText - previous.numWithInfoText)); + maybeCount("note_over_rate", (numRateViolations - previous.numRateViolations)); + maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations)); noisyImportance.maybeCount(previous.noisyImportance); quietImportance.maybeCount(previous.quietImportance); finalImportance.maybeCount(previous.finalImportance); @@ -473,6 +526,8 @@ public class NotificationUsageStats { previous.numWithText = numWithText; previous.numWithSubText = numWithSubText; previous.numWithInfoText = numWithInfoText; + previous.numRateViolations = numRateViolations; + previous.numQuotaViolations = numQuotaViolations; noisyImportance.update(previous.noisyImportance); quietImportance.update(previous.quietImportance); finalImportance.update(previous.finalImportance); @@ -493,6 +548,19 @@ public class NotificationUsageStats { return toStringWithIndent(""); } + /** @return the enqueue rate if there were a new enqueue event right now. */ + public float getEnqueueRate() { + return getEnqueueRate(SystemClock.elapsedRealtime()); + } + + public float getEnqueueRate(long now) { + return enqueueRate.getRate(now); + } + + public void updateInterarrivalEstimate(long now) { + enqueueRate.update(now); + } + private String toStringWithIndent(String indent) { StringBuilder output = new StringBuilder(); output.append(indent).append("AggregatedStats{\n"); @@ -549,6 +617,8 @@ public class NotificationUsageStats { output.append("numWithSubText=").append(numWithSubText).append("\n"); output.append(indentPlusTwo); output.append("numWithInfoText=").append(numWithInfoText).append("\n"); + output.append("numRateViolations=").append(numRateViolations).append("\n"); + output.append("numQuotaViolations=").append(numQuotaViolations).append("\n"); output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n"); output.append(indentPlusTwo).append(quietImportance.toString()).append("\n"); output.append(indentPlusTwo).append(finalImportance.toString()).append("\n"); @@ -586,6 +656,9 @@ public class NotificationUsageStats { maybePut(dump, "numWithText", numWithText); maybePut(dump, "numWithSubText", numWithSubText); maybePut(dump, "numWithInfoText", numWithInfoText); + maybePut(dump, "numRateViolations", numRateViolations); + maybePut(dump, "numQuotaLViolations", numQuotaViolations); + maybePut(dump, "notificationEnqueueRate", getEnqueueRate()); noisyImportance.maybePut(dump, previous.noisyImportance); quietImportance.maybePut(dump, previous.quietImportance); finalImportance.maybePut(dump, previous.finalImportance); @@ -598,6 +671,12 @@ public class NotificationUsageStats { dump.put(name, value); } } + + private void maybePut(JSONObject dump, String name, float value) throws JSONException { + if (value > 0.0) { + dump.put(name, value); + } + } } private static class ImportanceHistogram { diff --git a/services/core/java/com/android/server/notification/RateEstimator.java b/services/core/java/com/android/server/notification/RateEstimator.java new file mode 100644 index 000000000000..4dc30a4c98bd --- /dev/null +++ b/services/core/java/com/android/server/notification/RateEstimator.java @@ -0,0 +1,54 @@ +/* + * 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.notification; + + +/** + * Exponentially weighted moving average estimator for event rate. + * + * {@hide} + */ +public class RateEstimator { + private static final double RATE_ALPHA = 0.8; + private static final double MINIMUM_DT = 0.0005; + private long mLastEventTime; + private float mInterarrivalTime; + + public RateEstimator(long now) { + mLastEventTime = now; + } + + /** Update the estimate to account for an event that jsut happened. */ + public float update(long now) { + mInterarrivalTime = (float) getInterarrivalEstimate(now); + mLastEventTime = now; + return (float) (1.0 / mInterarrivalTime); + } + + /** @return the estimated rate if there were a new event right now. */ + public float getRate(long now) { + return (float) (1.0 / getInterarrivalEstimate(now)); + } + + /** @return the average inter-arrival time if there were a new event right now. */ + private double getInterarrivalEstimate(long now) { + // a*iat_old + (1-a)*(t_now-t_last) + double dt = ((double) (now - mLastEventTime)) / 1000.0; + dt = Math.max(dt, MINIMUM_DT); + return (RATE_ALPHA * mInterarrivalTime + (1.0 - RATE_ALPHA) * dt); + } +} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index b30817f53cdb..b3c5743e239c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5411,7 +5411,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { errorIntent.setComponent(errorComponent); errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_FOREGROUND); - mContext.sendBroadcastAsUser(errorIntent, UserHandle.ALL); + mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT); } /** {@inheritDoc} */ diff --git a/services/tests/servicestests/src/com/android/server/notification/RateEstimatorTest.java b/services/tests/servicestests/src/com/android/server/notification/RateEstimatorTest.java new file mode 100644 index 000000000000..cc0920f506a8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/notification/RateEstimatorTest.java @@ -0,0 +1,118 @@ +/* + * 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.notification; + + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +public class RateEstimatorTest extends AndroidTestCase { + private long mTestStartTime; + private RateEstimator mEstimator; + + @Override + public void setUp() { + mTestStartTime = 1225731600000L; + mEstimator = new RateEstimator(mTestStartTime); + } + + @SmallTest + public void testRunningTimeBackwardDoesntExplodeUpdate() throws Exception { + final float rate = mEstimator.update(mTestStartTime - 1000L); + assertFalse(Float.isInfinite(rate)); + assertFalse(Float.isNaN(rate)); + } + + @SmallTest + public void testRunningTimeBackwardDoesntExplodeGet() throws Exception { + final float rate = mEstimator.getRate(mTestStartTime - 1000L); + assertFalse(Float.isInfinite(rate)); + assertFalse(Float.isNaN(rate)); + } + + @SmallTest + public void testInstantaneousEventsDontExplodeUpdate() throws Exception { + final float rate = mEstimator.update(mTestStartTime); + assertFalse(Float.isInfinite(rate)); + assertFalse(Float.isNaN(rate)); + } + + @SmallTest + public void testInstantaneousEventsDontExplodeGet() throws Exception { + final float rate = mEstimator.getRate(mTestStartTime); + assertFalse(Float.isInfinite(rate)); + assertFalse(Float.isNaN(rate)); + } + + @SmallTest + public void testCompactBurstIsEstimatedUnderTwoPercent() throws Exception { + long eventStart = mTestStartTime + 1000; // start event a long time after initialization + long nextEventTime = postEvents(eventStart, 1, 5); // five events at 1000Hz + final float rate = mEstimator.getRate(nextEventTime); + assertLessThan("Rate", rate, 20f); + } + + @SmallTest + public void testSustained1000HzBurstIsEstimatedOverNinetyPercent() throws Exception { + long eventStart = mTestStartTime + 1000; // start event a long time after initialization + long nextEventTime = postEvents(eventStart, 1, 100); // one hundred events at 1000Hz + final float rate = mEstimator.getRate(nextEventTime); + assertGreaterThan("Rate", rate, 900f); + } + + @SmallTest + public void testSustained100HzBurstIsEstimatedOverNinetyPercent() throws Exception { + long eventStart = mTestStartTime + 1000; // start event a long time after initialization + long nextEventTime = postEvents(eventStart, 10, 100); // one hundred events at 100Hz + final float rate = mEstimator.getRate(nextEventTime); + + assertGreaterThan("Rate", rate, 90f); + } + + @SmallTest + public void testRecoverQuicklyAfterSustainedBurst() throws Exception { + long eventStart = mTestStartTime + 1000; // start event a long time after initialization + long nextEventTime = postEvents(eventStart, 10, 1000); // one hundred events at 100Hz + final float rate = mEstimator.getRate(nextEventTime + 5000L); // two seconds later + assertLessThan("Rate", rate, 2f); + } + + @SmallTest + public void testEstimateShouldNotOvershoot() throws Exception { + long eventStart = mTestStartTime + 1000; // start event a long time after initialization + long nextEventTime = postEvents(eventStart, 1, 1000); // one thousand events at 1000Hz + final float rate = mEstimator.getRate(nextEventTime); + assertLessThan("Rate", rate, 1000f); + } + + private void assertLessThan(String label, float a, float b) { + assertTrue(String.format("%s was %f, but should be less than %f", label, a, b), a < b); + } + + private void assertGreaterThan(String label, float a, float b) { + assertTrue(String.format("%s was %f, but should be more than %f", label, a, b), a > b); + } + + /** @returns the next event time. */ + private long postEvents(long start, long dt, int num) { + long time = start; + for (int i = 0; i < num; i++) { + mEstimator.update(time); + time += dt; + } + return time; + } +} diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 08cbcf764a4e..df9242dc0aa1 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -319,6 +319,7 @@ public class UsbDeviceManager { // current USB state private boolean mConnected; private boolean mHostConnected; + private boolean mSourcePower; private boolean mConfigured; private boolean mUsbDataUnlocked; private String mCurrentFunctions; @@ -399,7 +400,8 @@ public class UsbDeviceManager { public void updateHostState(UsbPort port, UsbPortStatus status) { boolean hostConnected = status.getCurrentDataRole() == UsbPort.DATA_ROLE_HOST; - obtainMessage(MSG_UPDATE_HOST_STATE, hostConnected ? 1 :0, 0).sendToTarget(); + boolean sourcePower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE; + obtainMessage(MSG_UPDATE_HOST_STATE, hostConnected ? 1 :0, sourcePower ? 1 :0).sendToTarget(); } private boolean waitForState(String state) { @@ -717,6 +719,7 @@ public class UsbDeviceManager { break; case MSG_UPDATE_HOST_STATE: mHostConnected = (msg.arg1 == 1); + mSourcePower = (msg.arg2 == 1); updateUsbNotification(); if (mBootCompleted) { updateUsbStateBroadcastIfNeeded(); @@ -782,7 +785,11 @@ public class UsbDeviceManager { Resources r = mContext.getResources(); if (mConnected) { if (!mUsbDataUnlocked) { - id = com.android.internal.R.string.usb_charging_notification_title; + if (mSourcePower) { + id = com.android.internal.R.string.usb_supplying_notification_title; + } else { + id = com.android.internal.R.string.usb_charging_notification_title; + } } else if (UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)) { id = com.android.internal.R.string.usb_mtp_notification_title; @@ -795,10 +802,12 @@ public class UsbDeviceManager { } else if (UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ACCESSORY)) { id = com.android.internal.R.string.usb_accessory_notification_title; + } else if (mSourcePower) { + id = com.android.internal.R.string.usb_supplying_notification_title; } else { id = com.android.internal.R.string.usb_charging_notification_title; } - } else if (mHostConnected) { + } else if (mSourcePower) { id = com.android.internal.R.string.usb_supplying_notification_title; } if (id != mUsbNotificationId) { diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 39a12070a69b..43f8739f74fd 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -287,11 +287,16 @@ public final class Call { public static final int PROPERTY_HIGH_DEF_AUDIO = 0x00000010; /** - * Whether the call is associated with the work profile. + * @deprecated Use {@link #PROPERTY_ENTERPRISE_CALL} instead. */ public static final int PROPERTY_WORK_CALL = 0x00000020; /** + * Whether the call is associated with the work profile. + */ + public static final int PROPERTY_ENTERPRISE_CALL = 0x00000020; + + /** * When set, indicates that this {@code Call} does not actually exist locally for the * {@link ConnectionService}. * <p> |