diff options
17 files changed, 466 insertions, 188 deletions
diff --git a/Android.mk b/Android.mk index 1469c2cdf93e..fc9c319a996a 100644 --- a/Android.mk +++ b/Android.mk @@ -197,7 +197,6 @@ LOCAL_SRC_FILES += \ core/java/android/net/ICaptivePortal.aidl \ core/java/android/net/IConnectivityManager.aidl \ core/java/android/net/IConnectivityMetricsLogger.aidl \ - core/java/android/net/IConnectivityMetricsLoggerSubscriber.aidl \ core/java/android/net/IEthernetManager.aidl \ core/java/android/net/IEthernetServiceListener.aidl \ core/java/android/net/INetworkManagementEventObserver.aidl \ diff --git a/core/java/android/net/ConnectivityMetricsEvent.aidl b/core/java/android/net/ConnectivityMetricsEvent.aidl index da175614b588..a027d7c38140 100644 --- a/core/java/android/net/ConnectivityMetricsEvent.aidl +++ b/core/java/android/net/ConnectivityMetricsEvent.aidl @@ -17,3 +17,4 @@ package android.net; parcelable ConnectivityMetricsEvent; +parcelable ConnectivityMetricsEvent.Reference; diff --git a/core/java/android/net/ConnectivityMetricsEvent.java b/core/java/android/net/ConnectivityMetricsEvent.java index 098f1e6947a3..b5d67d38455d 100644 --- a/core/java/android/net/ConnectivityMetricsEvent.java +++ b/core/java/android/net/ConnectivityMetricsEvent.java @@ -78,4 +78,42 @@ public final class ConnectivityMetricsEvent implements Parcelable { return String.format("ConnectivityMetricsEvent(%d, %d, %d)", timestamp, componentTag, eventTag); } + + /** {@hide} */ + public static class Reference implements Parcelable { + + public long value; + + public Reference(long ref) { + this.value = ref; + } + + /** Implement the Parcelable interface */ + public static final Parcelable.Creator<Reference> CREATOR + = new Parcelable.Creator<Reference> (){ + public Reference createFromParcel(Parcel source) { + return new Reference(source.readLong()); + } + + public Reference[] newArray(int size) { + return new Reference[size]; + } + }; + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(value); + } + + public void readFromParcel(Parcel in) { + value = in.readLong(); + } + } } diff --git a/core/java/android/net/ConnectivityMetricsLogger.java b/core/java/android/net/ConnectivityMetricsLogger.java index 3ef805017f14..eafb8acb8aeb 100644 --- a/core/java/android/net/ConnectivityMetricsLogger.java +++ b/core/java/android/net/ConnectivityMetricsLogger.java @@ -15,6 +15,7 @@ */ package android.net; +import android.os.Bundle; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; @@ -28,14 +29,24 @@ public class ConnectivityMetricsLogger { public static final String CONNECTIVITY_METRICS_LOGGER_SERVICE = "connectivity_metrics_logger"; // Component Tags - public static final int COMPONENT_TAG_CONNECTIVITY = 1; - public static final int COMPONENT_TAG_BLUETOOTH = 2; - public static final int COMPONENT_TAG_WIFI = 3; - public static final int COMPONENT_TAG_TELECOM = 4; - public static final int COMPONENT_TAG_TELEPHONY = 5; + public static final int COMPONENT_TAG_CONNECTIVITY = 0; + public static final int COMPONENT_TAG_BLUETOOTH = 1; + public static final int COMPONENT_TAG_WIFI = 2; + public static final int COMPONENT_TAG_TELECOM = 3; + public static final int COMPONENT_TAG_TELEPHONY = 4; + + public static final int NUMBER_OF_COMPONENTS = 5; + + // Event Tag + public static final int TAG_SKIPPED_EVENTS = -1; + + public static final String DATA_KEY_EVENTS_COUNT = "count"; private IConnectivityMetricsLogger mService; + private long mServiceUnblockedTimestampMillis = 0; + private int mNumSkippedEvents = 0; + public ConnectivityMetricsLogger() { mService = IConnectivityMetricsLogger.Stub.asInterface(ServiceManager.getService( CONNECTIVITY_METRICS_LOGGER_SERVICE)); @@ -46,12 +57,51 @@ public class ConnectivityMetricsLogger { if (DBG) { Log.d(TAG, "logEvent(" + componentTag + "," + eventTag + ") Service not ready"); } - } else { - try { - mService.logEvent(new ConnectivityMetricsEvent(timestamp, componentTag, eventTag, data)); - } catch (RemoteException e) { - Log.e(TAG, "Error logging event " + e.getMessage()); + return; + } + + if (mServiceUnblockedTimestampMillis > 0) { + if (System.currentTimeMillis() < mServiceUnblockedTimestampMillis) { + // Service is throttling events. + // Don't send new events because they will be dropped. + mNumSkippedEvents++; + return; + } + } + + ConnectivityMetricsEvent skippedEventsEvent = null; + if (mNumSkippedEvents > 0) { + // Log number of skipped events + Bundle b = new Bundle(); + b.putInt(DATA_KEY_EVENTS_COUNT, mNumSkippedEvents); + skippedEventsEvent = new ConnectivityMetricsEvent(mServiceUnblockedTimestampMillis, + componentTag, TAG_SKIPPED_EVENTS, b); + + mServiceUnblockedTimestampMillis = 0; + } + + ConnectivityMetricsEvent event = new ConnectivityMetricsEvent(timestamp, componentTag, + eventTag, data); + + try { + long result; + if (skippedEventsEvent == null) { + result = mService.logEvent(event); + } else { + result = mService.logEvents(new ConnectivityMetricsEvent[] + {skippedEventsEvent, event}); + } + + if (result == 0) { + mNumSkippedEvents = 0; + } else { + mNumSkippedEvents++; + if (result > 0) { // events are throttled + mServiceUnblockedTimestampMillis = result; + } } + } catch (RemoteException e) { + Log.e(TAG, "Error logging event " + e.getMessage()); } } } diff --git a/core/java/android/net/IConnectivityMetricsLogger.aidl b/core/java/android/net/IConnectivityMetricsLogger.aidl index 27786712a5c5..a83a01935253 100644 --- a/core/java/android/net/IConnectivityMetricsLogger.aidl +++ b/core/java/android/net/IConnectivityMetricsLogger.aidl @@ -16,15 +16,28 @@ package android.net; +import android.app.PendingIntent; import android.net.ConnectivityMetricsEvent; -import android.net.IConnectivityMetricsLoggerSubscriber; /** {@hide} */ interface IConnectivityMetricsLogger { - void logEvent(in ConnectivityMetricsEvent event); - void logEvents(in ConnectivityMetricsEvent[] events); + /** + * @return 0 on success + * <0 if error happened + * >0 timestamp after which new events will be accepted + */ + long logEvent(in ConnectivityMetricsEvent event); + long logEvents(in ConnectivityMetricsEvent[] events); - boolean subscribe(in IConnectivityMetricsLoggerSubscriber subscriber); - void unsubscribe(in IConnectivityMetricsLoggerSubscriber subscriber); + /** + * @param reference of the last event previously returned. The function will return + * events following it. + * If 0 then all events will be returned. + * After the function call it will contain reference of the last event. + */ + ConnectivityMetricsEvent[] getEvents(inout ConnectivityMetricsEvent.Reference reference); + + boolean register(in PendingIntent newEventsIntent); + void unregister(in PendingIntent newEventsIntent); } diff --git a/core/java/android/net/IConnectivityMetricsLoggerSubscriber.aidl b/core/java/android/net/IConnectivityMetricsLoggerSubscriber.aidl deleted file mode 100644 index a2c62cdaee97..000000000000 --- a/core/java/android/net/IConnectivityMetricsLoggerSubscriber.aidl +++ /dev/null @@ -1,25 +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 android.net; - -import android.net.ConnectivityMetricsEvent; - -/** {@hide} */ -oneway interface IConnectivityMetricsLoggerSubscriber { - - void onEvents(in ConnectivityMetricsEvent[] events); -} diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 6585fd8b3143..7055f783c584 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1863,9 +1863,9 @@ public class Editor { if (hasSelection) { hideInsertionPointCursorController(); if (mTextActionMode == null) { - if (mRestartActionModeOnNextRefresh || mTextView.isInExtractedMode()) { + if (mRestartActionModeOnNextRefresh) { // To avoid distraction, newly start action mode only when selection action - // mode is being restarted or in full screen extracted mode. + // mode is being restarted. startSelectionActionMode(); } } else if (selectionController == null || !selectionController.isActive()) { @@ -4875,11 +4875,12 @@ public class Editor { @Override protected int getOffsetAtCoordinate(@NonNull Layout layout, int line, float x) { - final int primaryOffset = layout.getOffsetForHorizontal(line, x, true); + final float localX = mTextView.convertToLocalHorizontalCoordinate(x); + final int primaryOffset = layout.getOffsetForHorizontal(line, localX, true); if (!layout.isLevelBoundary(primaryOffset)) { return primaryOffset; } - final int secondaryOffset = layout.getOffsetForHorizontal(line, x, false); + final int secondaryOffset = layout.getOffsetForHorizontal(line, localX, false); final int currentOffset = getCurrentCursorOffset(); final int primaryDiff = Math.abs(primaryOffset - currentOffset); final int secondaryDiff = Math.abs(secondaryOffset - currentOffset); diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 341b2a440b48..c2bb4ebd09b7 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -106,6 +106,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -1063,8 +1064,19 @@ public class DirectoryFragment extends Fragment } public void selectAllFiles() { + // Exclude disabled files + List<String> enabled = new ArrayList<String>(); + for (String id : mAdapter.getModelIds()) { + Cursor cursor = getModel().getItem(id); + String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); + if (isDocumentEnabled(docMimeType, docFlags)) { + enabled.add(id); + } + } + // Only select things currently visible in the adapter. - boolean changed = mSelectionManager.setItemsSelected(mAdapter.getModelIds(), true); + boolean changed = mSelectionManager.setItemsSelected(enabled, true); if (changed) { updateDisplayState(); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java index faa8e3892f61..016cc9e889df 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java @@ -148,11 +148,15 @@ public abstract class FragmentTuner { MenuItem share = menu.findItem(R.id.menu_share); MenuItem delete = menu.findItem(R.id.menu_delete); MenuItem rename = menu.findItem(R.id.menu_rename); + MenuItem selectAll = menu.findItem(R.id.menu_select_all); open.setVisible(true); share.setVisible(false); delete.setVisible(false); rename.setVisible(false); + selectAll.setVisible(mState.allowMultiple); + + Menus.disableHiddenItems(menu); } @Override diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 95d3cc3bbcf0..a4d6be53a033 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -371,8 +371,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private int mNetTransitionWakeLockTimeout; private final PowerManager.WakeLock mPendingIntentWakeLock; - private InetAddress mDefaultDns; - // used in DBG mode to track inet condition reports private static final int INET_CONDITION_LOG_MAX_SIZE = 15; private ArrayList mInetLog; @@ -645,19 +643,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - // read our default dns server ip - String dns = Settings.Global.getString(context.getContentResolver(), - Settings.Global.DEFAULT_DNS_SERVER); - if (dns == null || dns.length() == 0) { - dns = context.getResources().getString( - com.android.internal.R.string.config_default_dns_server); - } - try { - mDefaultDns = NetworkUtils.numericToInetAddress(dns); - } catch (IllegalArgumentException e) { - loge("Error setting defaultDns using " + dns); - } - mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000); @@ -4149,14 +4134,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // } updateTcpBufferSizes(networkAgent); - // TODO: deprecate and remove mDefaultDns when we can do so safely. See http://b/18327075 - // In L, we used it only when the network had Internet access but provided no DNS servers. - // For now, just disable it, and if disabling it doesn't break things, remove it. - // final boolean useDefaultDns = networkAgent.networkCapabilities.hasCapability( - // NET_CAPABILITY_INTERNET); - final boolean useDefaultDns = false; final boolean flushDns = updateRoutes(newLp, oldLp, netId); - updateDnses(newLp, oldLp, netId, flushDns, useDefaultDns); + updateDnses(newLp, oldLp, netId, flushDns); updateClat(newLp, oldLp, networkAgent); if (isDefaultNetwork(networkAgent)) { @@ -4260,16 +4239,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId, - boolean flush, boolean useDefaultDns) { + boolean flush) { if (oldLp == null || (newLp.isIdenticalDnses(oldLp) == false)) { Collection<InetAddress> dnses = newLp.getDnsServers(); - if (dnses.size() == 0 && mDefaultDns != null && useDefaultDns) { - dnses = new ArrayList(); - dnses.add(mDefaultDns); - if (DBG) { - loge("no dns provided for netId " + netId + ", so using defaults"); - } - } if (DBG) log("Setting Dns servers for network " + netId + " to " + dnses); try { mNetd.setDnsServersForNetwork(netId, NetworkUtils.makeStrings(dnses), diff --git a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java index 7cac2270950d..f91db7813d42 100644 --- a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java +++ b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java @@ -18,18 +18,21 @@ package com.android.server.connectivity; import com.android.server.SystemService; +import android.app.PendingIntent; import android.content.Context; +import android.content.pm.PackageManager; import android.net.ConnectivityMetricsEvent; import android.net.ConnectivityMetricsLogger; import android.net.IConnectivityMetricsLogger; -import android.net.IConnectivityMetricsLoggerSubscriber; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.ArrayMap; +import android.os.Binder; +import android.os.Parcel; +import android.text.format.DateUtils; import android.util.Log; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.List; /** {@hide} */ public class MetricsLoggerService extends SystemService { @@ -43,134 +46,307 @@ public class MetricsLoggerService extends SystemService { @Override public void onStart() { + resetThrottlingCounters(System.currentTimeMillis()); } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { - Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY"); + if (DBG) Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY"); publishBinderService(ConnectivityMetricsLogger.CONNECTIVITY_METRICS_LOGGER_SERVICE, mBinder); } } - private final int MAX_NUMBER_OF_EVENTS = 100; - private final int MAX_TIME_OFFSET = 15*60*1000; // 15 minutes - private final List<ConnectivityMetricsEvent> mEvents = new ArrayList<>(); - private long mLastSentEventTimeMillis = System.currentTimeMillis(); + // TODO: read from system property + private final int MAX_NUMBER_OF_EVENTS = 1000; - private final void enforceConnectivityInternalPermission() { + // TODO: read from system property + private final int EVENTS_NOTIFICATION_THRESHOLD = 300; + + // TODO: read from system property + private final int THROTTLING_TIME_INTERVAL_MILLIS = 60 * 60 * 1000; // 1 hour + + // TODO: read from system property + private final int THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT = 1000; + + private int mEventCounter = 0; + + /** + * Reference of the last event in the list of cached events. + * + * When client of this service retrieves events by calling getEvents, it is passing + * ConnectivityMetricsEvent.Reference object. After getEvents returns, that object will + * contain this reference. The client can save it and use next time it calls getEvents. + * This way only new events will be returned. + */ + private long mLastEventReference = 0; + + private final int mThrottlingCounters[] = + new int[ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS]; + + private long mThrottlingIntervalBoundaryMillis; + + private final ArrayDeque<ConnectivityMetricsEvent> mEvents = new ArrayDeque<>(); + + private void enforceConnectivityInternalPermission() { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.CONNECTIVITY_INTERNAL, "MetricsLoggerService"); } + private void enforceDumpPermission() { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DUMP, + "MetricsLoggerService"); + } + + private void resetThrottlingCounters(long currentTimeMillis) { + for (int i = 0; i < mThrottlingCounters.length; i++) { + mThrottlingCounters[i] = 0; + } + mThrottlingIntervalBoundaryMillis = + currentTimeMillis + THROTTLING_TIME_INTERVAL_MILLIS; + } + + private void addEvent(ConnectivityMetricsEvent e) { + if (VDBG) { + Log.v(TAG, "writeEvent(" + e.toString() + ")"); + } + + while (mEvents.size() >= MAX_NUMBER_OF_EVENTS) { + mEvents.removeFirst(); + } + + mEvents.addLast(e); + } + /** * Implementation of the IConnectivityMetricsLogger interface. */ private final IConnectivityMetricsLogger.Stub mBinder = new IConnectivityMetricsLogger.Stub() { - private final ArrayMap<IConnectivityMetricsLoggerSubscriber, - IBinder.DeathRecipient> mSubscribers = new ArrayMap<>(); - - - private ConnectivityMetricsEvent[] prepareEventsToSendIfReady() { - ConnectivityMetricsEvent[] eventsToSend = null; - final long currentTimeMillis = System.currentTimeMillis(); - final long timeOffset = currentTimeMillis - mLastSentEventTimeMillis; - if (timeOffset >= MAX_TIME_OFFSET - || timeOffset < 0 // system time has changed - || mEvents.size() >= MAX_NUMBER_OF_EVENTS) { - // batch events - mLastSentEventTimeMillis = currentTimeMillis; - eventsToSend = new ConnectivityMetricsEvent[mEvents.size()]; - mEvents.toArray(eventsToSend); - mEvents.clear(); + private final ArrayList<PendingIntent> mPendingIntents = new ArrayList<>(); + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump ConnectivityMetricsLoggerService " + + "from from pid=" + Binder.getCallingPid() + ", uid=" + + Binder.getCallingUid()); + return; } - return eventsToSend; - } - private void maybeSendEventsToSubscribers(ConnectivityMetricsEvent[] eventsToSend) { - if (eventsToSend == null || eventsToSend.length == 0) return; - synchronized (mSubscribers) { - for (IConnectivityMetricsLoggerSubscriber s : mSubscribers.keySet()) { - try { - s.onEvents(eventsToSend); - } catch (RemoteException ex) { - Log.e(TAG, "RemoteException " + ex); + boolean dumpSerializedSize = false; + boolean dumpEvents = false; + for (String arg : args) { + switch (arg) { + case "--events": + dumpEvents = true; + break; + + case "--size": + dumpSerializedSize = true; + break; + + case "--all": + dumpEvents = true; + dumpSerializedSize = true; + break; + } + } + + synchronized (mEvents) { + pw.println("Number of events: " + mEvents.size()); + pw.println("Time span: " + + DateUtils.formatElapsedTime( + (System.currentTimeMillis() - mEvents.peekFirst().timestamp) + / 1000)); + + if (dumpSerializedSize) { + long dataSize = 0; + Parcel p = Parcel.obtain(); + for (ConnectivityMetricsEvent e : mEvents) { + dataSize += 16; // timestamp and 2 stamps + + p.writeParcelable(e.data, 0); + } + dataSize += p.dataSize(); + p.recycle(); + pw.println("Serialized data size: " + dataSize); + } + + if (dumpEvents) { + pw.println(); + pw.println("Events:"); + for (ConnectivityMetricsEvent e : mEvents) { + pw.println(e.toString()); } } } + + if (!mPendingIntents.isEmpty()) { + pw.println(); + pw.println("Pending intents:"); + for (PendingIntent pi : mPendingIntents) { + pw.println(pi.toString()); + } + } } - public void logEvent(ConnectivityMetricsEvent event) { + public long logEvent(ConnectivityMetricsEvent event) { ConnectivityMetricsEvent[] events = new ConnectivityMetricsEvent[]{event}; - logEvents(events); + return logEvents(events); } - public void logEvents(ConnectivityMetricsEvent[] events) { + /** + * @param events + * + * Note: All events must belong to the same component. + * + * @return 0 on success + * <0 if error happened + * >0 timestamp after which new events will be accepted + */ + public long logEvents(ConnectivityMetricsEvent[] events) { enforceConnectivityInternalPermission(); - ConnectivityMetricsEvent[] eventsToSend; - if (VDBG) { - for (ConnectivityMetricsEvent e : events) { - Log.v(TAG, "writeEvent(" + e.toString() + ")"); + if (events == null || events.length == 0) { + Log.wtf(TAG, "No events passed to logEvents()"); + return -1; + } + + int componentTag = events[0].componentTag; + if (componentTag < 0 || + componentTag >= ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS) { + Log.wtf(TAG, "Unexpected tag: " + componentTag); + return -1; + } + + synchronized (mThrottlingCounters) { + long currentTimeMillis = System.currentTimeMillis(); + if (currentTimeMillis > mThrottlingIntervalBoundaryMillis) { + resetThrottlingCounters(currentTimeMillis); + } + + mThrottlingCounters[componentTag] += events.length; + + if (mThrottlingCounters[componentTag] > + THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT) { + Log.w(TAG, "Too many events from #" + componentTag + + ". Block until " + mThrottlingIntervalBoundaryMillis); + + return mThrottlingIntervalBoundaryMillis; } } + boolean sendPendingIntents = false; + synchronized (mEvents) { for (ConnectivityMetricsEvent e : events) { - mEvents.add(e); + if (e.componentTag != componentTag) { + Log.wtf(TAG, "Unexpected tag: " + e.componentTag); + return -1; + } + + addEvent(e); } - eventsToSend = prepareEventsToSendIfReady(); + mLastEventReference += events.length; + + mEventCounter += events.length; + if (mEventCounter >= EVENTS_NOTIFICATION_THRESHOLD) { + mEventCounter = 0; + sendPendingIntents = true; + } + } + + if (sendPendingIntents) { + synchronized (mPendingIntents) { + for (PendingIntent pi : mPendingIntents) { + if (VDBG) Log.v(TAG, "Send pending intent"); + try { + pi.send(getContext(), 0, null, null, null); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent canceled: " + pi); + mPendingIntents.remove(pi); + } + } + } } - maybeSendEventsToSubscribers(eventsToSend); + return 0; } - public boolean subscribe(IConnectivityMetricsLoggerSubscriber subscriber) { - enforceConnectivityInternalPermission(); - if (VDBG) Log.v(TAG, "subscribe"); + /** + * Retrieve events + * + * @param reference of the last event previously returned. The function will return + * events following it. + * If 0 then all events will be returned. + * After the function call it will contain reference of the + * last returned event. + * @return events + */ + public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) { + enforceDumpPermission(); + long ref = reference.value; + if (VDBG) Log.v(TAG, "getEvents(" + ref + ")"); - synchronized (mSubscribers) { - if (mSubscribers.containsKey(subscriber)) { - Log.e(TAG, "subscriber is already subscribed"); - return false; + ConnectivityMetricsEvent[] result; + synchronized (mEvents) { + if (ref > mLastEventReference) { + Log.e(TAG, "Invalid reference"); + reference.value = mLastEventReference; + return null; } - final IConnectivityMetricsLoggerSubscriber s = subscriber; - IBinder.DeathRecipient dr = new IBinder.DeathRecipient() { - @Override - public void binderDied() { - if (VDBG) Log.v(TAG, "subscriber died"); - synchronized (mSubscribers) { - mSubscribers.remove(s); - } + if (ref < mLastEventReference - mEvents.size()) { + ref = mLastEventReference - mEvents.size(); + } + + int numEventsToSkip = + mEvents.size() // Total number of events + - (int)(mLastEventReference - ref); // Number of events to return + + result = new ConnectivityMetricsEvent[mEvents.size() - numEventsToSkip]; + int i = 0; + for (ConnectivityMetricsEvent e : mEvents) { + if (numEventsToSkip > 0) { + numEventsToSkip--; + } else { + result[i++] = e; } - }; - - try { - subscriber.asBinder().linkToDeath(dr, 0); - mSubscribers.put(subscriber, dr); - } catch (RemoteException e) { - Log.e(TAG, "subscribe failed: " + e); - return false; } } + reference.value = mLastEventReference; + + return result; + } + + public boolean register(PendingIntent newEventsIntent) { + enforceDumpPermission(); + if (VDBG) Log.v(TAG, "register(" + newEventsIntent + ")"); + + synchronized (mPendingIntents) { + if (mPendingIntents.remove(newEventsIntent)) { + Log.w(TAG, "Replacing registered pending intent"); + } + mPendingIntents.add(newEventsIntent); + } + return true; } - public void unsubscribe(IConnectivityMetricsLoggerSubscriber subscriber) { - enforceConnectivityInternalPermission(); - if (VDBG) Log.v(TAG, "unsubscribe"); - synchronized (mSubscribers) { - IBinder.DeathRecipient dr = mSubscribers.remove(subscriber); - if (dr == null) { - Log.e(TAG, "subscriber is not subscribed"); - return; + public void unregister(PendingIntent newEventsIntent) { + enforceDumpPermission(); + if (VDBG) Log.v(TAG, "unregister(" + newEventsIntent + ")"); + + synchronized (mPendingIntents) { + if (!mPendingIntents.remove(newEventsIntent)) { + Log.e(TAG, "Pending intent is not registered"); } - subscriber.asBinder().unlinkToDeath(dr, 0); } } }; diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index 371ed1199824..54aeb3db7319 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -100,10 +100,6 @@ public class IpManager extends StateMachine { public void onProvisioningSuccess(LinkProperties newLp) {} public void onProvisioningFailure(LinkProperties newLp) {} - // This is called whenever 464xlat is being enabled or disabled (i.e. - // started or stopped). - public void on464XlatChange(boolean enabled) {} - // Invoked on LinkProperties changes. public void onLinkPropertiesChange(LinkProperties newLp) {} @@ -120,6 +116,10 @@ public class IpManager extends StateMachine { // If multicast filtering cannot be accomplished with APF, this function will be called to // actuate multicast filtering using another means. public void setFallbackMulticastFilter(boolean enabled) {} + + // Enabled/disable Neighbor Discover offload functionality. This is + // called, for example, whenever 464xlat is being started or stopped. + public void setNeighborDiscoveryOffload(boolean enable) {} } public static class WaitForProvisioningCallback extends Callback { @@ -304,7 +304,7 @@ public class IpManager extends StateMachine { public void interfaceAdded(String iface) { super.interfaceAdded(iface); if (mClatInterfaceName.equals(iface)) { - mCallback.on464XlatChange(true); + mCallback.setNeighborDiscoveryOffload(false); } } @@ -312,7 +312,10 @@ public class IpManager extends StateMachine { public void interfaceRemoved(String iface) { super.interfaceRemoved(iface); if (mClatInterfaceName.equals(iface)) { - mCallback.on464XlatChange(false); + // TODO: consider sending a message to the IpManager main + // StateMachine thread, in case "NDO enabled" state becomes + // tied to more things that 464xlat operation. + mCallback.setNeighborDiscoveryOffload(true); } } }; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index c8e3d03169e8..9e50ee898769 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -24,6 +24,7 @@ import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.Result.Status; import com.android.ide.common.rendering.api.SessionParams; +import com.android.layoutlib.bridge.android.RenderParamsFlags; import com.android.layoutlib.bridge.impl.RenderDrawable; import com.android.layoutlib.bridge.impl.RenderSessionImpl; import com.android.layoutlib.bridge.util.DynamicIdMap; @@ -408,7 +409,9 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { /** * Starts a layout session by inflating and rendering it. The method returns a * {@link RenderSession} on which further actions can be taken. - * + * <p/> + * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE}, + * this method will only inflate the layout but will NOT render it. * @param params the {@link SessionParams} object with all the information necessary to create * the scene. * @return a new {@link RenderSession} object that contains the result of the layout. @@ -424,7 +427,10 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { lastResult = scene.init(params.getTimeout()); if (lastResult.isSuccess()) { lastResult = scene.inflate(); - if (lastResult.isSuccess()) { + + boolean doNotRenderOnCreate = Boolean.TRUE.equals( + params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE)); + if (lastResult.isSuccess() && !doNotRenderOnCreate) { lastResult = scene.render(true /*freshRender*/); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java index bd17a2fe6ca2..051de9055042 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java @@ -53,6 +53,12 @@ public final class RenderParamsFlags { */ public static final Key<Boolean> FLAG_KEY_XML_FILE_PARSER_SUPPORT = new Key<Boolean>("xmlFileParser", Boolean.class); + /** + * To tell LayoutLib to not render when creating a new session. This allows controlling when the first + * layout rendering will happen. + */ + public static final Key<Boolean> FLAG_DO_NOT_RENDER_ON_CREATE = + new Key<Boolean>("doNotRenderOnCreate", Boolean.class); // Disallow instances. private RenderParamsFlags() {} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 016825ae66e2..ce7104ee6f9b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -267,6 +267,34 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } /** + * Renders the given view hierarchy to the passed canvas and returns the result of the render + * operation. + * @param canvas an optional canvas to render the views to. If null, only the measure and + * layout steps will be executed. + */ + private static Result render(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot, + @Nullable Canvas canvas, int width, int height) { + // measure again with the size we need + // This must always be done before the call to layout + measureView(viewRoot, null /*measuredView*/, + width, MeasureSpec.EXACTLY, + height, MeasureSpec.EXACTLY); + + // now do the layout. + viewRoot.layout(0, 0, width, height); + handleScrolling(context, viewRoot); + + if (canvas == null) { + return SUCCESS.createResult(); + } + + AttachInfo_Accessor.dispatchOnPreDraw(viewRoot); + viewRoot.draw(canvas); + + return SUCCESS.createResult(); + } + + /** * Renders the scene. * <p> * {@link #acquire(long)} must have been called before this. @@ -367,24 +395,12 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } } - // measure again with the size we need - // This must always be done before the call to layout - measureView(mViewRoot, null /*measuredView*/, - mMeasuredScreenWidth, MeasureSpec.EXACTLY, - mMeasuredScreenHeight, MeasureSpec.EXACTLY); - - // now do the layout. - mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); - - handleScrolling(mViewRoot); - + Result renderResult = SUCCESS.createResult(); if (params.isLayoutOnly()) { // delete the canvas and image to reset them on the next full rendering mImage = null; mCanvas = null; } else { - AttachInfo_Accessor.dispatchOnPreDraw(mViewRoot); - // draw the views // create the BufferedImage into which the layout will be rendered. boolean newImage = false; @@ -446,6 +462,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { if (mElapsedFrameTimeNanos >= 0) { long initialTime = System_Delegate.nanoTime(); if (!mFirstFrameExecuted) { + // We need to run an initial draw call to initialize the animations + render(getContext(), mViewRoot, mCanvas, 0, 0); + // The first frame will initialize the animations Choreographer_Delegate.doFrame(initialTime); mFirstFrameExecuted = true; @@ -453,14 +472,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // Second frame will move the animations Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos); } - mViewRoot.draw(mCanvas); + renderResult = render(getContext(), mViewRoot, mCanvas, mMeasuredScreenWidth, + mMeasuredScreenHeight); } mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), false); // success! - return SUCCESS.createResult(); + return renderResult; } catch (Throwable e) { // get the real cause of the exception. Throwable t = e; @@ -488,7 +508,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * @return the measured width/height if measuredView is non-null, null otherwise. */ @SuppressWarnings("deprecation") // For the use of Pair - private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView, + private static Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView, int width, int widthMode, int height, int heightMode) { int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode); int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode); @@ -1061,8 +1081,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * the component supports nested scrolling attempt that first, then use the unconsumed scroll * part to scroll the content in the component. */ - private void handleScrolling(View view) { - BridgeContext context = getContext(); + private static void handleScrolling(BridgeContext context, View view) { int scrollPosX = context.getScrollXPos(view); int scrollPosY = context.getScrollYPos(view); if (scrollPosX != 0 || scrollPosY != 0) { @@ -1080,7 +1099,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } } if (scrollPosX != 0 || scrollPosY != 0) { - view.scrollBy(scrollPosX, scrollPosY); + view.scrollTo(scrollPosX, scrollPosY); } } @@ -1090,7 +1109,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { ViewGroup group = (ViewGroup) view; for (int i = 0; i < group.getChildCount(); i++) { View child = group.getChildAt(i); - handleScrolling(child); + handleScrolling(context, child); } } diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml index a5ebc2e20847..a07498cd07b1 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml @@ -2,8 +2,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:scrollX="10px" - android:scrollY="30px"> + android:scrollX="30px" + android:scrollY="90px"> <LinearLayout android:layout_width="60dp" android:layout_height="60dp" @@ -29,8 +29,8 @@ android:layout_width="200dp" android:layout_height="400dp" android:orientation="vertical" - android:scrollX="-30px" - android:scrollY="150px"> + android:scrollX="-90px" + android:scrollY="450px"> <LinearLayout android:layout_width="fill_parent" android:layout_height="60dp" diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java index 09dd5f078b9a..034c8b2db7da 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java @@ -30,6 +30,7 @@ import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.io.FolderWrapper; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.RenderParamsFlags; import com.android.layoutlib.bridge.impl.RenderAction; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator; @@ -564,7 +565,7 @@ public class Main { sFrameworkRepo.getConfiguredResources(config), themeName, isProjectTheme); - return new SessionParams( + SessionParams sessionParams = new SessionParams( layoutParser, renderingMode, null /*used for caching*/, @@ -574,6 +575,8 @@ public class Main { 0, targetSdk, getLayoutLog()); + sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true); + return sessionParams; } private static LayoutLog getLayoutLog() { |