package com.eolwral.osmonitor.processes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import com.eolwral.osmonitor.*;
public class ProcessInfoQuery extends Thread
{
//JNI를 통해서 리눅스 커널을 읽어오는 함수이다.
private static JNIInterface JNILibrary = JNIInterface.getInstance();
private static ProcessInfoQuery singletone = null; //자기 자신의 Type으로 가지고 있는다.
private static PackageManager AppInfo = null; // 설치된 App의 목록과 이름 아이콘을 얻어오는 클래스이다.
private static Resources ResInfo = null;
//Main ListView의 onCreate에서 이 메서드를 실행 시킴으로서 Thread를 시작 시킨다.
public static ProcessInfoQuery getInstance(Context context)
{
if(singletone == null)
{
singletone = new ProcessInfoQuery(); // 프로세스 정보를 저장할 객체이다.
AppInfo = context.getPackageManager(); // 페키지 매니저를 얻어온다.
ResInfo = context.getResources(); // 아이콘 출력을 위한 리소스를 얻어 온다.
singletone.start();//getInstance를 하는 순간 Thrad를 시작 시킨다. 즉 이것이 외부에서 이 Thread를 시작시키는 방법인것 같다.
}
return singletone; // null 아니라면 기존에 생성한 것을 사용 한다.
}
//각 프로세스별로 표시해줄 정보를 가지고 있는 클래스이다.
class ProcessInstance
{
public String Name;
public Drawable Icon;
public String Package;
public int CellTraffic; //내가 추가한것
public int wifiTraffic; //내가 추가한것
}
/*
* ListView에 채워진 내용을 업데이트 하기위해서 3가지 종류의 HashMap을 가지고 있다.
*/
private final HashMap<String, Boolean> CacheExpaned = new HashMap<String, Boolean>(); //Detail버튼을 눌렀는지 여부
private final HashMap<String, Boolean> CacheSelected = new HashMap<String, Boolean>(); // 선택이 되었는지 여부
// 라인당 프로세스 정보들의 집함, 따라서 특이하게 이름당 processInstance의 값을 가지고 있는 형태를 유지하고 있다. (결국 이엇을 통해서 모든것이 접근되어 지는 것이다. )
private final HashMap<String, ProcessInstance> ProcessCache = new HashMap<String, ProcessInstance>();
public void doCacheInfo(int position)
{
ProcessInstance CacheInstance = ProcessCache.get(JNILibrary.GetProcessName(position));
/*
* null 아니라면 리턴하는 것이다. 즉 쉽게 말해서 ListView의 데이터는 Native 자료구조에서 읽어온 데이터로 뿌린것이다.
* 그 데이터를 뿌리기전에 항시 캐쉬정보로 이곳에 넣어주는것이다. 이미 들어있다면 또다시 작업을 반복해 줄 필요가없는 것이다.
* 이렇게해서 자동적으로 ListView에 의해서 뿌려주지 않아도 정해진 시간마다 갱신되어지는 값을 Thread에 의해서 주기적으로
* 갱신되어 ListView에 출력해 줄 수가 있다.
*/
if(CacheInstance != null)
return;
try {
/*
* 세마포어를 하는 이유는 ListView즉 UI Thread에 의해서 접근이 되어 지기도 하지만.
* 자체적인 쿼리 Thread에 의해서도 접근되어진다. 따라서 크리티컬 섹션 영역으로 지정해 주었다.
*/
QueryQueueLock.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
//현재 포지션의 프로세스 정보를 삽입하여 준다. (프로세스 이름, 프로세스 오너, 프로세스 UID )
QueryQueue.add(new WaitCache(JNILibrary.GetProcessName(position),
JNILibrary.GetProcessOwner(position), JNILibrary.GetProcessUID(position)));
QueryQueueLock.release();
//중복된 작업을 막기위해서 HashMap에다가 현재 작업한 프로세스를 저장해 준다.
CacheInstance = new ProcessInstance();
CacheInstance.Name = JNILibrary.GetProcessName(position);
ProcessCache.put(JNILibrary.GetProcessName(position),
CacheInstance);
return;
}
private class WaitCache
{
private final String ItemName;
private final String ItemOwner;
private final int ItemUID;
public WaitCache(String Name, String Owner, int UID)
{
ItemName = Name;
ItemOwner = Owner;
ItemUID = UID;
}
public String getName()
{
return ItemName;
}
public String getOwner()
{
return ItemOwner;
}
public int getUID()
{
return ItemUID;
}
}
//링크드 리스트 타입으로 캐쉬를 저장하는 객체를 생성 한다.
private static LinkedList<WaitCache> QueryQueue = new LinkedList<WaitCache>();
//세마포어를 설정하기 위해서 설정 하였다.
private final Semaphore QueryQueueLock = new Semaphore(1, true);
@Override
public void run()
{
//이부분이 반복되어 수행 되어 진다.
while(true)
{ //true면 0.5sec delay를 줘서 기다리고, 아니라면 그냥 그대로 연산을 처리 한다.
//Chase에 내용이 아무것도 없다면 지체없이 데이터를 읽어 온다.
if(!getCacheInfo())
{
try {
/*
* 데이터가 없다면 아직 리스트뷰가 최워지기 전이므로 0.5초간 기다린다.
* 그렇다면 그동안 리스트뷰가 모두 갱신 되어서 hashMap안에 유효한 데이터가 모두 들어 있을 것이다.
*/
sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/*
* 여기서 해주는 주된 작업은 proc/stat를 통해서 읽어온 프로세스 정보들을
* 가공하여 보여준다. 좀더 명확하게 (e.g 아이콘도 표시하고, 이름도 페키지 이름이 아니라, 어플리케이션이름으로 바꾼다.
*/
public boolean getCacheInfo()
{
//링크드리스트의 큐가 Empty라면 false를 리턴한다.
if(QueryQueue.isEmpty())
return false;
try {
//큐에는 하나의 Thread만이 접근해야 하기 때문에 세마포어를 걸어 놓는다.
QueryQueueLock.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
//최근에 방문한 원소를 삭제 한다. 하여 돌려준다.
WaitCache SearchObj = QueryQueue.remove();
//세마포어를 해제 한다.
QueryQueueLock.release();
String PackageName = null;
PackageInfo appPackageInfo = null;
//이름에 ':'문자를 포함하고 있다면 :전까지의 내용을 PackageName으로 정의한다.
if(SearchObj.getName().contains(":")){
PackageName = SearchObj.getName().substring(0,SearchObj.getName().indexOf(":"));
}
else
PackageName = SearchObj.getName();
// for system user 시스템 기본 App을 목록에서 제거한다.
if(SearchObj.getOwner().contains("system") && SearchObj.getName().contains("system"))
PackageName = "android";
try {
//해당 PackageName에 대한 정보를 PackageManager로 부터 받아 온다.
appPackageInfo = AppInfo.getPackageInfo(PackageName, 0);
} catch (NameNotFoundException e) {}
//PackageName에 대한 정보가 없을경우 SearchObj에서 UID를 얻어온다.
if(appPackageInfo == null && SearchObj.getUID() >0)
{
//UID를 통해서 Package Name을 확보 한다.
String[] subPackageName = AppInfo.getPackagesForUid(SearchObj.getUID());
if(subPackageName != null)
{
for(int PackagePtr = 0; PackagePtr < subPackageName.length; PackagePtr++)
{
if (subPackageName[PackagePtr] == null)
continue;
try {
appPackageInfo = AppInfo.getPackageInfo(subPackageName[PackagePtr], 0);
PackagePtr = subPackageName.length;
} catch (NameNotFoundException e) {}
}
}
}
ProcessInstance CacheInstance = new ProcessInstance();
//획득한 packageName을 기록 한다.
CacheInstance.Package = PackageName;
if(appPackageInfo != null)
{
CacheInstance.Name = appPackageInfo.applicationInfo.loadLabel(AppInfo).toString();
CacheInstance.Icon = resizeImage(appPackageInfo.applicationInfo.loadIcon(AppInfo));
}
else if(PackageName.equals("System"))
{
CacheInstance.Name = PackageName;
CacheInstance.Icon = resizeImage(ResInfo.getDrawable(R.drawable.system));
}
else
CacheInstance.Name = PackageName;
ProcessCache.put(SearchObj.getName(), CacheInstance);
return true;
}
public Boolean getExpaned(int position)
{
Boolean Flag = CacheExpaned.get(JNILibrary.GetProcessPID(position)+"");
if(Flag == null)
Flag = false;
return Flag;
}
public void setExpaned(int position, Boolean Flag)
{
// ProcessInstance CacheInstance = ProcessCache.get(JNILibrary.GetProcessName(position));
// CacheInstance.Expaned = Flag;
//ProcessCache.put(JNILibrary.GetProcessName(position), CacheInstance);
CacheExpaned.put(JNILibrary.GetProcessPID(position)+"", Flag);
return;
}
public Boolean getSelected(int position)
{
Boolean Flag = CacheSelected.get(JNILibrary.GetProcessPID(position)+"");
if(Flag == null)
Flag = false;
return Flag;
}
public void setSelected(int position, Boolean Flag)
{
CacheSelected.put(JNILibrary.GetProcessPID(position)+"", Flag);
return;
}
public ArrayList<String> getSelected()
{
ArrayList<String> selectPID = new ArrayList<String>();
Iterator<String> It = CacheSelected.keySet().iterator();
while (It.hasNext())
{
String cacheKey = (String) It.next();
if(CacheSelected.get(cacheKey) == true)
selectPID.add(cacheKey);
}
return selectPID;
}
public void clearSelected()
{
CacheSelected.clear();
return;
}
public String getPackageName(int position)
{
return ProcessCache.get(JNILibrary.GetProcessName(position)).Name;
}
public String getPacakge(int position)
{
return ProcessCache.get(JNILibrary.GetProcessName(position)).Package;
}
public int getProcessPID(int position)
{
return JNILibrary.GetProcessPID(position);
}
public String getProcessThreads(int position)
{
return JNILibrary.GetProcessThreads(position)+"";
}
public String getProcessLoad(int position)
{
return JNILibrary.GetProcessLoad(position)+"%";
}
public String getProcessMem(int position)
{
if(JNILibrary.GetProcessRSS(position) > 1024)
return (JNILibrary.GetProcessRSS(position)/1024)+"M";
return JNILibrary.GetProcessRSS(position)+"K";
}
private StringBuilder appbuf = new StringBuilder();
//뭔가 받은 포지션 만큼 작업을 해주는 것 같다.
//position 값으로 데이터의 위치를 가져오는것 같다.
public String getAppInfo(int position) {
appbuf.setLength(0);
if(JNILibrary.GetProcessRSS(position) > 1024) {
appbuf.append("\tProcess: ")
.append(JNILibrary.GetProcessName(position))
.append("\n\tMemory: ")
.append(JNILibrary.GetProcessRSS(position)/1024)
.append("M\t Thread: ")
.append(JNILibrary.GetProcessThreads(position))
.append("\t Load: ")
.append(JNILibrary.GetProcessLoad(position))
.append("%\n\tSTime: ")
.append(JNILibrary.GetProcessSTime(position))
.append("\t UTime: ")
.append(JNILibrary.GetProcessUTime(position))
.append("\n\tUser: ")
.append(JNILibrary.GetProcessOwner(position))
.append("\t UID: ")
.append(JNILibrary.GetProcessUID(position))
.append("\t Status: ");
}
else {
appbuf.append("\tProcess: ")
.append(JNILibrary.GetProcessName(position))
.append("\n\tMemory: ")
.append(JNILibrary.GetProcessRSS(position))
.append("K\t Threads: ")
.append(JNILibrary.GetProcessThreads(position))
.append("\t Load: ")
.append(JNILibrary.GetProcessLoad(position))
.append("%\n\tSTime: ")
.append(JNILibrary.GetProcessSTime(position))
.append("\t UTime: ")
.append(JNILibrary.GetProcessUTime(position))
.append("\n\tUser: ")
.append(JNILibrary.GetProcessOwner(position))
.append("\t UID: ")
.append(JNILibrary.GetProcessUID(position))
.append("\t Status: ");
}
String Status = JNILibrary.GetProcessStatus(position).trim();
if(Status.compareTo("Z") == 0)
appbuf.append("Zombie");
else if(Status.compareTo("S") == 0)
appbuf.append("Sleep");
else if(Status.compareTo("R") == 0)
appbuf.append("Running");
else if(Status.compareTo("D") == 0)
appbuf.append("Wait IO");
else if(Status.compareTo("T") == 0)
appbuf.append("Stop");
else
appbuf.append("Unknown");
return appbuf.toString();
}
public Drawable getAppIcon(int position)
{
return ProcessCache.get(JNILibrary.GetProcessName(position)).Icon;
}
//applciation icon을 resize해줘야 같은 크기로 List에 출력 되어 지게된다.
private Drawable resizeImage(Drawable Icon) {
if(CompareFunc.getScreenSize() == 2)
{
Bitmap BitmapOrg = Bitmap.createBitmap(60, 60, Bitmap.Config.ARGB_8888);
Canvas BitmapCanvas = new Canvas(BitmapOrg);
Icon.setBounds(0, 0, 60, 60);
Icon.draw(BitmapCanvas);
return new BitmapDrawable(BitmapOrg);
}
else if (CompareFunc.getScreenSize() == 0)
{
Bitmap BitmapOrg = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
Canvas BitmapCanvas = new Canvas(BitmapOrg);
Icon.setBounds(0, 0, 10, 10);
Icon.draw(BitmapCanvas);
return new BitmapDrawable(BitmapOrg);
}
else
{
Bitmap BitmapOrg = Bitmap.createBitmap(22, 22, Bitmap.Config.ARGB_8888);
Canvas BitmapCanvas = new Canvas(BitmapOrg);
Icon.setBounds(0, 0, 22, 22);
Icon.draw(BitmapCanvas);
return new BitmapDrawable(BitmapOrg);
}
}
}