• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ByteArrayInputStream;
35 import java.io.DataInputStream;
36 import java.io.FileDescriptor;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.nio.ByteBuffer;
41 
42 /**
43  * Lmkd connection to communicate with lowmemorykiller daemon.
44  */
45 public class LmkdConnection {
46     private static final String TAG = TAG_WITH_CLASS_NAME ? "LmkdConnection" : TAG_AM;
47 
48     /**
49      * Max LMKD reply packet length in bytes
50      * Used to hold the data for the statsd atoms logging
51      * Must be in sync with statslog.h
52      */
53     private static final int LMKD_REPLY_MAX_SIZE = 222;
54 
55     // connection listener interface
56     interface LmkdConnectionListener {
onConnect(OutputStream ostream)57         public boolean onConnect(OutputStream ostream);
onDisconnect()58         public void onDisconnect();
59         /**
60          * Check if received reply was expected (reply to an earlier request)
61          *
62          * @param replyBuf The buffer provided in exchange() to receive the reply.
63          *                 It can be used by exchange() caller to store reply-specific
64          *                 tags for later use in isReplyExpected() to verify if
65          *                 received packet is the expected reply.
66          * @param dataReceived The buffer holding received data
67          * @param receivedLen Size of the data received
68          */
isReplyExpected(ByteBuffer replyBuf, ByteBuffer dataReceived, int receivedLen)69         public boolean isReplyExpected(ByteBuffer replyBuf, ByteBuffer dataReceived,
70             int receivedLen);
71 
72         /**
73          * Handle the received message if it's unsolicited.
74          *
75          * @param dataReceived The buffer holding received data
76          * @param receivedLen Size of the data received
77          * @return True if the message has been handled correctly, false otherwise.
78          */
handleUnsolicitedMessage(DataInputStream inputData, int receivedLen)79         boolean handleUnsolicitedMessage(DataInputStream inputData, int receivedLen);
80     }
81 
82     private final MessageQueue mMsgQueue;
83 
84     // lmkd connection listener
85     private final LmkdConnectionListener mListener;
86 
87     // mutex to synchronize access to the socket
88     private final Object mLmkdSocketLock = new Object();
89 
90     // socket to communicate with lmkd
91     @GuardedBy("mLmkdSocketLock")
92     private LocalSocket mLmkdSocket = null;
93 
94     // socket I/O streams
95     @GuardedBy("mLmkdSocketLock")
96     private OutputStream mLmkdOutputStream = null;
97     @GuardedBy("mLmkdSocketLock")
98     private InputStream mLmkdInputStream = null;
99 
100     // buffer to store incoming data
101     private final ByteBuffer mInputBuf =
102             ByteBuffer.allocate(LMKD_REPLY_MAX_SIZE);
103 
104     // Input stream to parse the incoming data
105     private final DataInputStream mInputData = new DataInputStream(
106             new ByteArrayInputStream(mInputBuf.array()));
107 
108     // object to protect mReplyBuf and to wait/notify when reply is received
109     private final Object mReplyBufLock = new Object();
110 
111     // reply buffer
112     @GuardedBy("mReplyBufLock")
113     private ByteBuffer mReplyBuf = null;
114 
115     ////////////////////  END FIELDS  ////////////////////
116 
LmkdConnection(MessageQueue msgQueue, LmkdConnectionListener listener)117     LmkdConnection(MessageQueue msgQueue, LmkdConnectionListener listener) {
118         mMsgQueue = msgQueue;
119         mListener = listener;
120     }
121 
connect()122     public boolean connect() {
123         synchronized (mLmkdSocketLock) {
124             if (mLmkdSocket != null) {
125                 return true;
126             }
127             // temporary sockets and I/O streams
128             final LocalSocket socket = openSocket();
129 
130             if (socket == null) {
131                 Slog.w(TAG, "Failed to connect to lowmemorykiller, retry later");
132                 return false;
133             }
134 
135             final OutputStream ostream;
136             final InputStream istream;
137             try {
138                 ostream = socket.getOutputStream();
139                 istream = socket.getInputStream();
140             } catch (IOException ex) {
141                 IoUtils.closeQuietly(socket);
142                 return false;
143             }
144             // execute onConnect callback
145             if (mListener != null && !mListener.onConnect(ostream)) {
146                 Slog.w(TAG, "Failed to communicate with lowmemorykiller, retry later");
147                 IoUtils.closeQuietly(socket);
148                 return false;
149             }
150             // connection established
151             mLmkdSocket = socket;
152             mLmkdOutputStream = ostream;
153             mLmkdInputStream = istream;
154             mMsgQueue.addOnFileDescriptorEventListener(mLmkdSocket.getFileDescriptor(),
155                 EVENT_INPUT | EVENT_ERROR,
156                 new MessageQueue.OnFileDescriptorEventListener() {
157                     public int onFileDescriptorEvents(FileDescriptor fd, int events) {
158                         return fileDescriptorEventHandler(fd, events);
159                     }
160                 }
161             );
162             mLmkdSocketLock.notifyAll();
163         }
164         return true;
165     }
166 
fileDescriptorEventHandler(FileDescriptor fd, int events)167     private int fileDescriptorEventHandler(FileDescriptor fd, int events) {
168         if (mListener == null) {
169             return 0;
170         }
171         if ((events & EVENT_INPUT) != 0) {
172             processIncomingData();
173         }
174         if ((events & EVENT_ERROR) != 0) {
175             synchronized (mLmkdSocketLock) {
176                 // stop listening on this socket
177                 mMsgQueue.removeOnFileDescriptorEventListener(
178                         mLmkdSocket.getFileDescriptor());
179                 IoUtils.closeQuietly(mLmkdSocket);
180                 mLmkdSocket = null;
181             }
182             // wake up reply waiters if any
183             synchronized (mReplyBufLock) {
184                 if (mReplyBuf != null) {
185                     mReplyBuf = null;
186                     mReplyBufLock.notifyAll();
187                 }
188             }
189             // notify listener
190             mListener.onDisconnect();
191             return 0;
192         }
193         return (EVENT_INPUT | EVENT_ERROR);
194     }
195 
processIncomingData()196     private void processIncomingData() {
197         int len = read(mInputBuf);
198         if (len > 0) {
199             try {
200                 // reset InputStream to point into mInputBuf.array() begin
201                 mInputData.reset();
202                 synchronized (mReplyBufLock) {
203                     if (mReplyBuf != null) {
204                         if (mListener.isReplyExpected(mReplyBuf, mInputBuf, len)) {
205                             // copy into reply buffer
206                             mReplyBuf.put(mInputBuf.array(), 0, len);
207                             mReplyBuf.rewind();
208                             // wakeup the waiting thread
209                             mReplyBufLock.notifyAll();
210                         } else if (!mListener.handleUnsolicitedMessage(mInputData, len)) {
211                             // received unexpected packet
212                             // treat this as an error
213                             mReplyBuf = null;
214                             mReplyBufLock.notifyAll();
215                             Slog.e(TAG, "Received an unexpected packet from lmkd");
216                         }
217                     } else if (!mListener.handleUnsolicitedMessage(mInputData, len)) {
218                         // received asynchronous communication from lmkd
219                         // but we don't recognize it.
220                         Slog.w(TAG, "Received an unexpected packet from lmkd");
221                     }
222                 }
223             } catch (IOException e) {
224                 Slog.e(TAG, "Failed to parse lmkd data buffer. Size = " + len);
225             }
226         }
227     }
228 
isConnected()229     public boolean isConnected() {
230         synchronized (mLmkdSocketLock) {
231             return (mLmkdSocket != null);
232         }
233     }
234 
waitForConnection(long timeoutMs)235     public boolean waitForConnection(long timeoutMs) {
236         synchronized (mLmkdSocketLock) {
237             if (mLmkdSocket != null) {
238                 return true;
239             }
240             try {
241                 mLmkdSocketLock.wait(timeoutMs);
242                 return (mLmkdSocket != null);
243             } catch (InterruptedException e) {
244                 return false;
245             }
246         }
247     }
248 
openSocket()249     private LocalSocket openSocket() {
250         final LocalSocket socket;
251 
252         try {
253             socket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
254             socket.connect(
255                 new LocalSocketAddress("lmkd",
256                         LocalSocketAddress.Namespace.RESERVED));
257         } catch (IOException ex) {
258             Slog.e(TAG, "Connection failed: " + ex.toString());
259             return null;
260         }
261         return socket;
262     }
263 
write(ByteBuffer buf)264     private boolean write(ByteBuffer buf) {
265         synchronized (mLmkdSocketLock) {
266             try {
267                 mLmkdOutputStream.write(buf.array(), 0, buf.position());
268             } catch (IOException ex) {
269                 return false;
270             }
271             return true;
272         }
273     }
274 
read(ByteBuffer buf)275     private int read(ByteBuffer buf) {
276         synchronized (mLmkdSocketLock) {
277             try {
278                 return mLmkdInputStream.read(buf.array(), 0, buf.array().length);
279             } catch (IOException ex) {
280             }
281             return -1;
282         }
283     }
284 
285     /**
286      * Exchange a request/reply packets with lmkd
287      *
288      * @param req The buffer holding the request data to be sent
289      * @param repl The buffer to receive the reply
290      */
exchange(ByteBuffer req, ByteBuffer repl)291     public boolean exchange(ByteBuffer req, ByteBuffer repl) {
292         if (repl == null) {
293             return write(req);
294         }
295 
296         boolean result = false;
297         // set reply buffer to user-defined one to fill it
298         synchronized (mReplyBufLock) {
299             mReplyBuf = repl;
300 
301             if (write(req)) {
302                 try {
303                     // wait for the reply
304                     mReplyBufLock.wait();
305                     result = (mReplyBuf != null);
306                 } catch (InterruptedException ie) {
307                     result = false;
308                 }
309             }
310 
311             // reset reply buffer
312             mReplyBuf = null;
313         }
314         return result;
315     }
316 }
317