1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.am; 18 19 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; 20 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; 21 22 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 23 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 24 25 import android.net.LocalSocket; 26 import android.net.LocalSocketAddress; 27 import android.os.MessageQueue; 28 import android.util.Slog; 29 30 import com.android.internal.annotations.GuardedBy; 31 32 import libcore.io.IoUtils; 33 34 import java.io.FileDescriptor; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.io.OutputStream; 38 import java.nio.ByteBuffer; 39 40 /** 41 * Lmkd connection to communicate with lowmemorykiller daemon. 42 */ 43 public class LmkdConnection { 44 private static final String TAG = TAG_WITH_CLASS_NAME ? "LmkdConnection" : TAG_AM; 45 46 // lmkd reply max size in bytes 47 private static final int LMKD_REPLY_MAX_SIZE = 12; 48 49 // connection listener interface 50 interface LmkdConnectionListener { onConnect(OutputStream ostream)51 boolean onConnect(OutputStream ostream); onDisconnect()52 void onDisconnect(); 53 /** 54 * Check if received reply was expected (reply to an earlier request) 55 * 56 * @param replyBuf The buffer provided in exchange() to receive the reply. 57 * It can be used by exchange() caller to store reply-specific 58 * tags for later use in isReplyExpected() to verify if 59 * received packet is the expected reply. 60 * @param dataReceived The buffer holding received data 61 * @param receivedLen Size of the data received 62 */ isReplyExpected(ByteBuffer replyBuf, ByteBuffer dataReceived, int receivedLen)63 boolean isReplyExpected(ByteBuffer replyBuf, ByteBuffer dataReceived, 64 int receivedLen); 65 66 /** 67 * Handle the received message if it's unsolicited. 68 * 69 * @param dataReceived The buffer holding received data 70 * @param receivedLen Size of the data received 71 * @return True if the message has been handled correctly, false otherwise. 72 */ handleUnsolicitedMessage(ByteBuffer dataReceived, int receivedLen)73 boolean handleUnsolicitedMessage(ByteBuffer dataReceived, int receivedLen); 74 } 75 76 private final MessageQueue mMsgQueue; 77 78 // lmkd connection listener 79 private final LmkdConnectionListener mListener; 80 81 // mutex to synchronize access to the socket 82 private final Object mLmkdSocketLock = new Object(); 83 84 // socket to communicate with lmkd 85 @GuardedBy("mLmkdSocketLock") 86 private LocalSocket mLmkdSocket = null; 87 88 // socket I/O streams 89 @GuardedBy("mLmkdSocketLock") 90 private OutputStream mLmkdOutputStream = null; 91 @GuardedBy("mLmkdSocketLock") 92 private InputStream mLmkdInputStream = null; 93 94 // buffer to store incoming data 95 private final ByteBuffer mInputBuf = 96 ByteBuffer.allocate(LMKD_REPLY_MAX_SIZE); 97 98 // object to protect mReplyBuf and to wait/notify when reply is received 99 private final Object mReplyBufLock = new Object(); 100 101 // reply buffer 102 @GuardedBy("mReplyBufLock") 103 private ByteBuffer mReplyBuf = null; 104 105 //////////////////// END FIELDS //////////////////// 106 LmkdConnection(MessageQueue msgQueue, LmkdConnectionListener listener)107 LmkdConnection(MessageQueue msgQueue, LmkdConnectionListener listener) { 108 mMsgQueue = msgQueue; 109 mListener = listener; 110 } 111 connect()112 boolean connect() { 113 synchronized (mLmkdSocketLock) { 114 if (mLmkdSocket != null) { 115 return true; 116 } 117 // temporary sockets and I/O streams 118 final LocalSocket socket = openSocket(); 119 120 if (socket == null) { 121 Slog.w(TAG, "Failed to connect to lowmemorykiller, retry later"); 122 return false; 123 } 124 125 final OutputStream ostream; 126 final InputStream istream; 127 try { 128 ostream = socket.getOutputStream(); 129 istream = socket.getInputStream(); 130 } catch (IOException ex) { 131 IoUtils.closeQuietly(socket); 132 return false; 133 } 134 // execute onConnect callback 135 if (mListener != null && !mListener.onConnect(ostream)) { 136 Slog.w(TAG, "Failed to communicate with lowmemorykiller, retry later"); 137 IoUtils.closeQuietly(socket); 138 return false; 139 } 140 // connection established 141 mLmkdSocket = socket; 142 mLmkdOutputStream = ostream; 143 mLmkdInputStream = istream; 144 mMsgQueue.addOnFileDescriptorEventListener(mLmkdSocket.getFileDescriptor(), 145 EVENT_INPUT | EVENT_ERROR, 146 new MessageQueue.OnFileDescriptorEventListener() { 147 public int onFileDescriptorEvents(FileDescriptor fd, int events) { 148 return fileDescriptorEventHandler(fd, events); 149 } 150 } 151 ); 152 mLmkdSocketLock.notifyAll(); 153 } 154 return true; 155 } 156 fileDescriptorEventHandler(FileDescriptor fd, int events)157 private int fileDescriptorEventHandler(FileDescriptor fd, int events) { 158 if (mListener == null) { 159 return 0; 160 } 161 if ((events & EVENT_INPUT) != 0) { 162 processIncomingData(); 163 } 164 if ((events & EVENT_ERROR) != 0) { 165 synchronized (mLmkdSocketLock) { 166 // stop listening on this socket 167 mMsgQueue.removeOnFileDescriptorEventListener( 168 mLmkdSocket.getFileDescriptor()); 169 IoUtils.closeQuietly(mLmkdSocket); 170 mLmkdSocket = null; 171 } 172 // wake up reply waiters if any 173 synchronized (mReplyBufLock) { 174 if (mReplyBuf != null) { 175 mReplyBuf = null; 176 mReplyBufLock.notifyAll(); 177 } 178 } 179 // notify listener 180 mListener.onDisconnect(); 181 return 0; 182 } 183 return (EVENT_INPUT | EVENT_ERROR); 184 } 185 processIncomingData()186 private void processIncomingData() { 187 int len = read(mInputBuf); 188 if (len > 0) { 189 synchronized (mReplyBufLock) { 190 if (mReplyBuf != null) { 191 if (mListener.isReplyExpected(mReplyBuf, mInputBuf, len)) { 192 // copy into reply buffer 193 mReplyBuf.put(mInputBuf.array(), 0, len); 194 mReplyBuf.rewind(); 195 // wakeup the waiting thread 196 mReplyBufLock.notifyAll(); 197 } else if (!mListener.handleUnsolicitedMessage(mInputBuf, len)) { 198 // received unexpected packet 199 // treat this as an error 200 mReplyBuf = null; 201 mReplyBufLock.notifyAll(); 202 Slog.e(TAG, "Received an unexpected packet from lmkd"); 203 } 204 } else if (!mListener.handleUnsolicitedMessage(mInputBuf, len)) { 205 // received asynchronous communication from lmkd 206 // but we don't recognize it. 207 Slog.w(TAG, "Received an unexpected packet from lmkd"); 208 } 209 } 210 } 211 } 212 isConnected()213 boolean isConnected() { 214 synchronized (mLmkdSocketLock) { 215 return (mLmkdSocket != null); 216 } 217 } 218 waitForConnection(long timeoutMs)219 boolean waitForConnection(long timeoutMs) { 220 synchronized (mLmkdSocketLock) { 221 if (mLmkdSocket != null) { 222 return true; 223 } 224 try { 225 mLmkdSocketLock.wait(timeoutMs); 226 return (mLmkdSocket != null); 227 } catch (InterruptedException e) { 228 return false; 229 } 230 } 231 } 232 openSocket()233 private LocalSocket openSocket() { 234 final LocalSocket socket; 235 236 try { 237 socket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET); 238 socket.connect( 239 new LocalSocketAddress("lmkd", 240 LocalSocketAddress.Namespace.RESERVED)); 241 } catch (IOException ex) { 242 Slog.e(TAG, "Connection failed: " + ex.toString()); 243 return null; 244 } 245 return socket; 246 } 247 write(ByteBuffer buf)248 private boolean write(ByteBuffer buf) { 249 synchronized (mLmkdSocketLock) { 250 try { 251 mLmkdOutputStream.write(buf.array(), 0, buf.position()); 252 } catch (IOException ex) { 253 return false; 254 } 255 return true; 256 } 257 } 258 read(ByteBuffer buf)259 private int read(ByteBuffer buf) { 260 synchronized (mLmkdSocketLock) { 261 try { 262 return mLmkdInputStream.read(buf.array(), 0, buf.array().length); 263 } catch (IOException ex) { 264 } 265 return -1; 266 } 267 } 268 269 /** 270 * Exchange a request/reply packets with lmkd 271 * 272 * @param req The buffer holding the request data to be sent 273 * @param repl The buffer to receive the reply 274 */ exchange(ByteBuffer req, ByteBuffer repl)275 public boolean exchange(ByteBuffer req, ByteBuffer repl) { 276 if (repl == null) { 277 return write(req); 278 } 279 280 boolean result = false; 281 // set reply buffer to user-defined one to fill it 282 synchronized (mReplyBufLock) { 283 mReplyBuf = repl; 284 285 if (write(req)) { 286 try { 287 // wait for the reply 288 mReplyBufLock.wait(); 289 result = (mReplyBuf != null); 290 } catch (InterruptedException ie) { 291 result = false; 292 } 293 } 294 295 // reset reply buffer 296 mReplyBuf = null; 297 } 298 return result; 299 } 300 } 301