/* Copyright (C) 2011 The University of Michigan This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Please send inquiries to powertutor@umich.edu */ package edu.umich.PowerTutor.components; import edu.umich.PowerTutor.PowerNotifications; import edu.umich.PowerTutor.phone.PhoneConstants; import edu.umich.PowerTutor.service.IterationData; import edu.umich.PowerTutor.service.PowerData; import edu.umich.PowerTutor.util.NativeLoader; import edu.umich.PowerTutor.util.NotificationService; import edu.umich.PowerTutor.util.Recycler; import edu.umich.PowerTutor.util.SystemInfo; import edu.umich.PowerTutor.util.ForegroundDetector; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.provider.Settings; import android.os.Process; import android.util.Log; import android.util.DisplayMetrics; import android.view.WindowManager; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.RandomAccessFile; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.List; import java.util.Random; import java.io.*; import java.nio.*; import java.nio.channels.*; public class OLED extends PowerComponent { public static class OledData extends PowerData { private static Recycler<OledData> recycler = new Recycler<OledData>(); public static OledData obtain() { OledData result = recycler.obtain(); if(result != null) return result; return new OledData(); } @Override public void recycle() { recycler.recycle(this); } public int brightness; public double pixPower; public boolean screenOn; private OledData() { } public void init() { this.screenOn = false; } public void init(int brightness, double pixPower) { screenOn = true; this.brightness = brightness; this.pixPower = pixPower; } public void writeLogDataInfo(OutputStreamWriter out) throws IOException { out.write("OLED-brightness " + brightness + "\n"); out.write("OLED-pix-power " + pixPower + "\n"); out.write("OLED-screen-on " + screenOn + "\n"); } } private static final String TAG = "OLED"; private static final String[] BACKLIGHT_BRIGHTNESS_FILES = { "/sys/class/leds/lcd-backlight/brightness", "/sys/devices/virtual/leds/lcd-backlight/brightness", "/sys/devices/platform/trout-backlight.0/leds/lcd-backlight/brightness", }; private Context context; private ForegroundDetector foregroundDetector; private BroadcastReceiver broadcastReceiver; private boolean screenOn; private File frameBufferFile; private int screenWidth; private int screenHeight; private static final int NUMBER_OF_SAMPLES = 500; private int[] samples; private String brightnessFile; /* Coefficients pre-computed for pix power calculations. */ private double rcoef; private double gcoef; private double bcoef; private double modul_coef; public OLED(Context context, PhoneConstants constants) { this.context = context; screenOn = true; foregroundDetector = new ForegroundDetector((ActivityManager) context.getSystemService(context.ACTIVITY_SERVICE)); broadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { synchronized(this) { if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { screenOn = false; } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { screenOn = true; } } }; }; IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_SCREEN_ON); context.registerReceiver(broadcastReceiver, intentFilter); frameBufferFile = new File("/dev/fb0"); if(!frameBufferFile.exists()) { frameBufferFile = new File("/dev/graphics/fb0"); } if(frameBufferFile.exists()) try { /* Check if we already have permission to read the frame buffer. */ boolean readOk = false; try { RandomAccessFile fin = new RandomAccessFile(frameBufferFile, "r"); int b = fin.read(); fin.close(); readOk = true; } catch(IOException e) { } /* Don't have permission, try to change permission as root. */ if(!readOk) { java.lang.Process p = Runtime.getRuntime().exec("su"); DataOutputStream os = new DataOutputStream(p.getOutputStream()); os.writeBytes("chown " + android.os.Process.myUid() + " " + frameBufferFile.getAbsolutePath() + "\n"); os.writeBytes("chown app_" + (android.os.Process.myUid() - SystemInfo.AID_APP) + " " + frameBufferFile.getAbsolutePath() + "\n"); os.writeBytes("chmod 660 " + frameBufferFile.getAbsolutePath() + "\n"); os.writeBytes("exit\n"); os.flush(); p.waitFor(); if(p.exitValue() != 0) { Log.i(TAG, "failed to change permissions on frame buffer"); } } } catch (InterruptedException e) { Log.i(TAG, "changing permissions on frame buffer interrupted"); } catch (IOException e) { Log.i(TAG, "unexpected exception while changing permission on " + "frame buffer"); e.printStackTrace(); } DisplayMetrics metrics = new DisplayMetrics(); WindowManager windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); windowManager.getDefaultDisplay().getMetrics(metrics); screenWidth = metrics.widthPixels; screenHeight = metrics.heightPixels; Random r = new Random(); samples = new int[NUMBER_OF_SAMPLES]; for(int i = 0; i < NUMBER_OF_SAMPLES; i++) { int a = screenWidth * screenHeight * i / NUMBER_OF_SAMPLES; int b = screenWidth * screenHeight * (i + 1) / NUMBER_OF_SAMPLES; samples[i] = a + r.nextInt(b - a); } double[] channel = constants.oledChannelPower(); rcoef = channel[0] / 255 / 255; gcoef = channel[1] / 255 / 255; bcoef = channel[2] / 255 / 255; modul_coef = constants.oledModulation() / 255 / 255 / 3 / 3; for(int i = 0; i < BACKLIGHT_BRIGHTNESS_FILES.length; i++) { if(new File(BACKLIGHT_BRIGHTNESS_FILES[i]).exists()) { brightnessFile = BACKLIGHT_BRIGHTNESS_FILES[i]; } } } @Override protected void onExit() { context.unregisterReceiver(broadcastReceiver); super.onExit(); } @Override public IterationData calculateIteration(long iteration) { IterationData result = IterationData.obtain(); boolean screen; synchronized(this) { screen = screenOn; } int brightness; if(brightnessFile != null) { brightness = (int)SystemInfo.getInstance() .readLongFromFile(brightnessFile); } else { try { brightness = Settings.System.getInt(context.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS); } catch(Settings.SettingNotFoundException ex) { Log.w(TAG, "Could not retrieve brightness information"); return result; } } if(brightness < 0 || 255 < brightness) { Log.w(TAG, "Could not retrieve brightness information"); return result; } double pixPower = 0; if(screen && frameBufferFile.exists()) { if(NativeLoader.jniLoaded()) { pixPower = getScreenPixPower(rcoef, gcoef, bcoef, modul_coef); } else try { RandomAccessFile fin = new RandomAccessFile(frameBufferFile, "r"); for(int x : samples) { fin.seek(x * 4); int px = fin.readInt(); int b = px >> 8 & 0xFF; int g = px >> 16 & 0xFF; int r = px >> 24 & 0xFF; /* Calculate the power usage of this one pixel if it were at full * brightness. Linearly scale by brightness to get true power * consumption. To calculate whole screen compute average of sampled * region and multiply by number of pixels. */ int modul_val = r + g + b; pixPower += rcoef * (r * r) + gcoef * (g * g) + bcoef * (b * b) - modul_coef * (modul_val * modul_val); } fin.close(); } catch(FileNotFoundException e) { pixPower = -1; } catch(IOException e) { pixPower = -1; e.printStackTrace(); } if(pixPower >= 0) { pixPower *= 1.0 * screenWidth * screenHeight / NUMBER_OF_SAMPLES; } } OledData data = OledData.obtain(); if(!screen) { data.init(); } else { data.init(brightness, pixPower); } result.setPowerData(data); if(screen) { OledData uidData = OledData.obtain(); uidData.init(brightness, pixPower); result.addUidPowerData(foregroundDetector.getForegroundUid(), uidData); } return result; } @Override public boolean hasUidInformation() { return true; } @Override public String getComponentName() { return "OLED"; } public static native double getScreenPixPower(double rcoef, double gcoef, double bcoef, double modul_coef); }