| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.base; |
| |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.MessageQueue; |
| import android.util.Log; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| |
| class SystemMessageHandler extends Handler { |
| |
| private static final String TAG = "SystemMessageHandler"; |
| |
| private static final int SCHEDULED_WORK = 1; |
| private static final int DELAYED_SCHEDULED_WORK = 2; |
| |
| // Native class pointer set by the constructor of the SharedClient native class. |
| private long mMessagePumpDelegateNative = 0; |
| private long mDelayedScheduledTimeTicks = 0; |
| |
| // The following members are used to detect and trace the presence of sync |
| // barriers in Android's MessageQueue. Note that this detection is |
| // experimental, temporary and intended only for diagnostic purposes. |
| private MessageQueue mMessageQueue; |
| private Field mMessageQueueMessageField; |
| private Field mMessageTargetField; |
| private boolean mQueueHasSyncBarrier; |
| private long mSyncBarrierTraceId; |
| |
| private SystemMessageHandler(long messagePumpDelegateNative) { |
| mMessagePumpDelegateNative = messagePumpDelegateNative; |
| tryEnableSyncBarrierDetection(); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| updateWhetherQueueHasBlockingSyncBarrier(); |
| if (msg.what == DELAYED_SCHEDULED_WORK) { |
| mDelayedScheduledTimeTicks = 0; |
| } |
| nativeDoRunLoopOnce(mMessagePumpDelegateNative, mDelayedScheduledTimeTicks); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void scheduleWork() { |
| updateWhetherQueueHasBlockingSyncBarrier(); |
| if (mQueueHasSyncBarrier) TraceEvent.instant("SystemMessageHandler:immediateWorkBlocked"); |
| sendEmptyMessage(SCHEDULED_WORK); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void scheduleDelayedWork(long delayedTimeTicks, long millis) { |
| if (mDelayedScheduledTimeTicks != 0) { |
| removeMessages(DELAYED_SCHEDULED_WORK); |
| } |
| mDelayedScheduledTimeTicks = delayedTimeTicks; |
| updateWhetherQueueHasBlockingSyncBarrier(); |
| if (mQueueHasSyncBarrier) TraceEvent.instant("SystemMessageHandler:delayedWorkBlocked"); |
| sendEmptyMessageDelayed(DELAYED_SCHEDULED_WORK, millis); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void removeAllPendingMessages() { |
| updateWhetherQueueHasBlockingSyncBarrier(); |
| removeMessages(SCHEDULED_WORK); |
| removeMessages(DELAYED_SCHEDULED_WORK); |
| } |
| |
| private void updateWhetherQueueHasBlockingSyncBarrier() { |
| if (mMessageQueue == null) return; |
| // As barrier detection is only used for tracing, early out when tracing |
| // is disabled to avoid any potential performance penalties. |
| if (!TraceEvent.enabled()) { |
| mQueueHasSyncBarrier = false; |
| return; |
| } |
| Message queueHead = (Message) getField(mMessageQueue, mMessageQueueMessageField); |
| setqueueHasSyncBarrier(isSyncBarrierMessage(queueHead)); |
| } |
| |
| private boolean isSyncBarrierMessage(Message message) { |
| if (message == null) return false; |
| // Sync barrier messages have null targets. |
| return getField(message, mMessageTargetField) == null; |
| } |
| |
| private void tryEnableSyncBarrierDetection() { |
| assert mMessageQueue == null; |
| |
| boolean success = false; |
| try { |
| Method getQueueMethod = Looper.class.getMethod("getQueue", new Class[]{}); |
| mMessageQueue = (MessageQueue) getQueueMethod.invoke(getLooper()); |
| |
| mMessageQueueMessageField = mMessageQueue.getClass().getDeclaredField("mMessages"); |
| mMessageQueueMessageField.setAccessible(true); |
| |
| mMessageTargetField = Message.class.getDeclaredField("target"); |
| mMessageTargetField.setAccessible(true); |
| |
| mSyncBarrierTraceId = hashCode(); |
| |
| success = true; |
| } catch (NoSuchMethodException e) { |
| Log.e(TAG, "Failed to load method: " + e); |
| } catch (NoSuchFieldException e) { |
| Log.e(TAG, "Failed to load field: " + e); |
| } catch (InvocationTargetException e) { |
| Log.e(TAG, "Failed invocation: " + e); |
| } catch (IllegalAccessException e) { |
| Log.e(TAG, "Illegal access to reflected invocation: " + e); |
| } catch (IllegalArgumentException e) { |
| Log.e(TAG, "Illegal argument to reflected invocation: " + e); |
| } catch (RuntimeException e) { |
| Log.e(TAG, e.toString()); |
| } finally { |
| if (!success) disableSyncBarrierDetection(); |
| } |
| } |
| |
| private void disableSyncBarrierDetection() { |
| Log.e(TAG, "Unexpected error with sync barrier detection, disabling."); |
| mMessageQueue = null; |
| mMessageQueueMessageField = null; |
| mMessageTargetField = null; |
| setqueueHasSyncBarrier(false); |
| } |
| |
| private void setqueueHasSyncBarrier(boolean queueHasSyncBarrier) { |
| if (queueHasSyncBarrier == mQueueHasSyncBarrier) return; |
| mQueueHasSyncBarrier = queueHasSyncBarrier; |
| if (mQueueHasSyncBarrier) { |
| TraceEvent.startAsync("SyncBarrier", mSyncBarrierTraceId); |
| } else { |
| TraceEvent.finishAsync("SyncBarrier", mSyncBarrierTraceId); |
| } |
| } |
| |
| private Object getField(Object object, Field field) { |
| try { |
| return field.get(object); |
| } catch (IllegalAccessException e) { |
| Log.e(TAG, "Failed field access: " + e); |
| disableSyncBarrierDetection(); |
| } |
| return null; |
| } |
| |
| @CalledByNative |
| private static SystemMessageHandler create(long messagePumpDelegateNative) { |
| return new SystemMessageHandler(messagePumpDelegateNative); |
| } |
| |
| private native void nativeDoRunLoopOnce( |
| long messagePumpDelegateNative, long delayedScheduledTimeTicks); |
| } |