1/*
2 * Copyright (C) 2014 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#define LOG_TAG "BatteryStatsService"
18//#define LOG_NDEBUG 0
19
20#include <android_runtime/AndroidRuntime.h>
21#include <jni.h>
22
23#include <ScopedLocalRef.h>
24#include <ScopedPrimitiveArray.h>
25
26#include <cutils/log.h>
27#include <utils/misc.h>
28#include <utils/Log.h>
29#include <hardware/hardware.h>
30#include <suspend/autosuspend.h>
31
32#include <stdio.h>
33#include <errno.h>
34#include <fcntl.h>
35#include <semaphore.h>
36#include <stddef.h>
37#include <string.h>
38#include <sys/stat.h>
39#include <sys/types.h>
40#include <unistd.h>
41
42namespace android
43{
44
45#define LAST_RESUME_REASON "/sys/kernel/wakeup_reasons/last_resume_reason"
46#define MAX_REASON_SIZE 512
47
48static bool wakeup_init = false;
49static sem_t wakeup_sem;
50
51static void wakeup_callback(void)
52{
53    ALOGV("In wakeup_callback");
54    int ret = sem_post(&wakeup_sem);
55    if (ret < 0) {
56        char buf[80];
57        strerror_r(errno, buf, sizeof(buf));
58        ALOGE("Error posting wakeup sem: %s\n", buf);
59    }
60}
61
62static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jintArray outIrqs,
63        jobjectArray outReasons)
64{
65    bool first_time = false;
66
67    if (outIrqs == NULL || outReasons == NULL) {
68        jniThrowException(env, "java/lang/NullPointerException", "null argument");
69        return -1;
70    }
71
72    // Register our wakeup callback if not yet done.
73    if (!wakeup_init) {
74        wakeup_init = true;
75        ALOGV("Creating semaphore...");
76        int ret = sem_init(&wakeup_sem, 0, 0);
77        if (ret < 0) {
78            char buf[80];
79            strerror_r(errno, buf, sizeof(buf));
80            ALOGE("Error creating semaphore: %s\n", buf);
81            jniThrowException(env, "java/lang/IllegalStateException", buf);
82            return -1;
83        }
84        ALOGV("Registering callback...");
85        set_wakeup_callback(&wakeup_callback);
86        // First time through, we will just drain the current wakeup reasons.
87        first_time = true;
88    } else {
89        // On following calls, we need to wait for wakeup.
90        ALOGV("Waiting for wakeup...");
91        int ret = sem_wait(&wakeup_sem);
92        if (ret < 0) {
93            char buf[80];
94            strerror_r(errno, buf, sizeof(buf));
95            ALOGE("Error waiting on semaphore: %s\n", buf);
96            // Return 0 here to let it continue looping but not return results.
97            return 0;
98        }
99    }
100
101    FILE *fp = fopen(LAST_RESUME_REASON, "r");
102    if (fp == NULL) {
103        ALOGE("Failed to open %s", LAST_RESUME_REASON);
104        return -1;
105    }
106
107    int numOut = env->GetArrayLength(outIrqs);
108    ScopedIntArrayRW irqs(env, outIrqs);
109
110    ALOGV("Reading up to %d wakeup reasons", numOut);
111
112    char mergedreason[MAX_REASON_SIZE];
113    char* mergedreasonpos = mergedreason;
114    int remainreasonlen = MAX_REASON_SIZE;
115    int firstirq = 0;
116    char reasonline[128];
117    int i = 0;
118    while (fgets(reasonline, sizeof(reasonline), fp) != NULL && i < numOut) {
119        char* pos = reasonline;
120        char* endPos;
121        // First field is the index.
122        int irq = (int)strtol(pos, &endPos, 10);
123        if (pos == endPos) {
124            // Ooops.
125            ALOGE("Bad reason line: %s", reasonline);
126            continue;
127        }
128        pos = endPos;
129        // Skip whitespace; rest of the buffer is the reason string.
130        while (*pos == ' ') {
131            pos++;
132        }
133        // Chop newline at end.
134        char* endpos = pos;
135        while (*endpos != 0) {
136            if (*endpos == '\n') {
137                *endpos = 0;
138                break;
139            }
140            endpos++;
141        }
142        // For now we are not separating out the first irq.
143        // This is because in practice there are always multiple
144        // lines of wakeup reasons, so it is better to just treat
145        // them all together as a single string.
146        if (false && i == 0) {
147            firstirq = irq;
148        } else {
149            int len = snprintf(mergedreasonpos, remainreasonlen,
150                    i == 0 ? "%d" : ":%d", irq);
151            if (len >= 0 && len < remainreasonlen) {
152                mergedreasonpos += len;
153                remainreasonlen -= len;
154            }
155        }
156        int len = snprintf(mergedreasonpos, remainreasonlen, ":%s", pos);
157        if (len >= 0 && len < remainreasonlen) {
158            mergedreasonpos += len;
159            remainreasonlen -= len;
160        }
161        // For now it is better to combine all of these in to one entry in the
162        // battery history.  In the future, it might be nice to figure out a way
163        // to efficiently store multiple lines as a single entry in the history.
164        //irqs[i] = irq;
165        //ScopedLocalRef<jstring> reasonString(env, env->NewStringUTF(pos));
166        //env->SetObjectArrayElement(outReasons, i, reasonString.get());
167        //ALOGV("Wakeup reason #%d: irw %d reason %s", i, irq, pos);
168        i++;
169    }
170
171    ALOGV("Got %d reasons", i);
172    if (first_time) {
173        i = 0;
174    }
175    if (i > 0) {
176        irqs[0] = firstirq;
177        *mergedreasonpos = 0;
178        ScopedLocalRef<jstring> reasonString(env, env->NewStringUTF(mergedreason));
179        env->SetObjectArrayElement(outReasons, 0, reasonString.get());
180        i = 1;
181    }
182
183    if (fclose(fp) != 0) {
184        ALOGE("Failed to close %s", LAST_RESUME_REASON);
185        return -1;
186    }
187
188    return first_time ? 0 : i;
189}
190
191static JNINativeMethod method_table[] = {
192    { "nativeWaitWakeup", "([I[Ljava/lang/String;)I", (void*)nativeWaitWakeup },
193};
194
195int register_android_server_BatteryStatsService(JNIEnv *env)
196{
197    return jniRegisterNativeMethods(env, "com/android/server/am/BatteryStatsService",
198            method_table, NELEM(method_table));
199}
200
201};
202