Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 03b80dc8 authored by John Spurlock's avatar John Spurlock Committed by Android Git Automerger
Browse files

am e0de5bff: Merge "Fire "dreaming started" and "dreaming stopped" broadcasts." into jb-mr1-dev

* commit 'e0de5bff':
  Fire "dreaming started" and "dreaming stopped" broadcasts.
parents 9fc94c86 e0de5bff
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -20289,6 +20289,8 @@ package android.service.dreams {
    method public void setContentView(android.view.View);
    method public void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
    method public void setInteractive(boolean);
    field public static final java.lang.String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED";
    field public static final java.lang.String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED";
    field public static final java.lang.String METADATA_NAME_CONFIG_ACTIVITY = "android.service.dreams.config_activity";
    field public static final java.lang.String SERVICE_INTERFACE = "android.service.dreams.Dream";
  }
+160 −90
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
import android.view.ActionMode;
@@ -40,11 +39,15 @@ import android.view.accessibility.AccessibilityEvent;
import com.android.internal.policy.PolicyManager;

/**
 *  Extend this class to implement a custom screensaver.
 * Extend this class to implement a custom Dream.
 *
 * <p>Dreams are interactive screensavers launched when a charging device is idle, or docked in a
 * desk dock. Dreams provide another modality for apps to express themselves, tailored for
 * an exhibition/lean-back experience.</p>
 */
public class Dream extends Service implements Window.Callback {
    private final static boolean DEBUG = true;
    private final static String TAG = "Dream";
    private final String TAG = Dream.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";

    /**
     * The {@link Intent} that must be declared as handled by the service.
@@ -60,28 +63,43 @@ public class Dream extends Service implements Window.Callback {
    public static final String METADATA_NAME_CONFIG_ACTIVITY =
            "android.service.dreams.config_activity";

    private Window mWindow;
    /**
     * Broadcast Action: Sent after the system starts dreaming.
     *
     * <p class="note">This is a protected intent that can only be sent by the system.
     * It is only sent to registered receivers.</p>
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED";

    /**
     * Broadcast Action: Sent after the system stops dreaming.
     *
     * <p class="note">This is a protected intent that can only be sent by the system.
     * It is only sent to registered receivers.</p>
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED";

    private final Handler mHandler = new Handler();
    private IBinder mWindowToken;
    private Window mWindow;
    private WindowManager mWindowManager;
    private IDreamManager mSandman;
    
    private boolean mInteractive;
    
    final Handler mHandler = new Handler();
    
    boolean mFinished = false;
    private boolean mFinished;

    // begin Window.Callback methods
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK
        if (!mInteractive) {
            if (DEBUG) Slog.v(TAG, "finishing on keyEvent");
            finish();
            if (DEBUG) Slog.v(TAG, "Finishing on keyEvent");
            safelyFinish();
            return true;
        } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            if (DEBUG) Slog.v(TAG, "finishing on back key");
            finish();
            if (DEBUG) Slog.v(TAG, "Finishing on back key");
            safelyFinish();
            return true;
        }
        return mWindow.superDispatchKeyEvent(event);
@@ -90,8 +108,8 @@ public class Dream extends Service implements Window.Callback {
    @Override
    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
        if (!mInteractive) { 
            if (DEBUG) Slog.v(TAG, "finishing on keyShortcutEvent");
            finish();
            if (DEBUG) Slog.v(TAG, "Finishing on keyShortcutEvent");
            safelyFinish();
            return true;
        }
        return mWindow.superDispatchKeyShortcutEvent(event);
@@ -102,8 +120,8 @@ public class Dream extends Service implements Window.Callback {
        // TODO: create more flexible version of mInteractive that allows clicks 
        // but finish()es on any other kind of activity
        if (!mInteractive) { 
            if (DEBUG) Slog.v(TAG, "finishing on touchEvent");
            finish();
            if (DEBUG) Slog.v(TAG, "Finishing on touchEvent");
            safelyFinish();
            return true;
        }
        return mWindow.superDispatchTouchEvent(event);
@@ -112,8 +130,8 @@ public class Dream extends Service implements Window.Callback {
    @Override
    public boolean dispatchTrackballEvent(MotionEvent event) {
        if (!mInteractive) {
            if (DEBUG) Slog.v(TAG, "finishing on trackballEvent");
            finish();
            if (DEBUG) Slog.v(TAG, "Finishing on trackballEvent");
            safelyFinish();
            return true;
        }
        return mWindow.superDispatchTrackballEvent(event);
@@ -122,8 +140,8 @@ public class Dream extends Service implements Window.Callback {
    @Override
    public boolean dispatchGenericMotionEvent(MotionEvent event) {
        if (!mInteractive) { 
            if (DEBUG) Slog.v(TAG, "finishing on genericMotionEvent");
            finish();
            if (DEBUG) Slog.v(TAG, "Finishing on genericMotionEvent");
            safelyFinish();
            return true;
        }
        return mWindow.superDispatchGenericMotionEvent(event);
@@ -214,28 +232,7 @@ public class Dream extends Service implements Window.Callback {
    }

   /**
     * Called when this Dream is constructed. Place your initialization here.
     * 
     * Subclasses must call through to the superclass implementation.
     */
    @Override
    public void onCreate() {
        super.onCreate();

        if (DEBUG) Slog.v(TAG, "Dream created on thread " + Thread.currentThread().getId());

        mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams"));
    }

    /**
     * Called when this Dream is started.
     */
    public void onStart() {
        // hook for subclasses
    }

   /**
     * Inflate a layout resource and set it to be the content view for this Dream.
     * Inflates a layout resource and set it to be the content view for this Dream.
     * Behaves similarly to {@link android.app.Activity#setContentView(int)}.
     *
     * @param layoutResID Resource ID to be inflated.
@@ -248,7 +245,7 @@ public class Dream extends Service implements Window.Callback {
    }

    /**
     * Set a view to be the content view for this Dream.
     * Sets a view to be the content view for this Dream.
     * Behaves similarly to {@link android.app.Activity#setContentView(android.view.View)},
     * including using {@link ViewGroup.LayoutParams#MATCH_PARENT} as the layout height and width of the view.
     * 
@@ -262,7 +259,7 @@ public class Dream extends Service implements Window.Callback {
    }

    /**
     * Set a view to be the content view for this Dream.
     * Sets a view to be the content view for this Dream.
     * Behaves similarly to 
     * {@link android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}.
     *
@@ -277,7 +274,7 @@ public class Dream extends Service implements Window.Callback {
    }

    /**
     * Add a view to the Dream's window, leaving other content views in place.
     * Adds a view to the Dream's window, leaving other content views in place.
     * 
     * @param view The desired content to display.
     * @param params Layout parameters for the view.
@@ -287,14 +284,20 @@ public class Dream extends Service implements Window.Callback {
    }

    /**
     * @param mInteractive the mInteractive to set
     * Marks this dream as interactive to receive input events.
     *
     * <p>Non-interactive dreams (default) will dismiss on the first input event.</p>
     *
     * <p>Interactive dreams should call {@link #finish()} to dismiss themselves.</p>
     *
     * @param interactive True if this dream will handle input events.
     */
    public void setInteractive(boolean mInteractive) {
        this.mInteractive = mInteractive;
    public void setInteractive(boolean interactive) {
        mInteractive = interactive;
    }

    /**
     * @return the mInteractive
     * Returns whether or not this dream is interactive.
     */
    public boolean isInteractive() {
        return mInteractive;
@@ -321,12 +324,27 @@ public class Dream extends Service implements Window.Callback {
    }

    /**
     * Called when this Dream is being removed from the screen and stopped.
     * Called when this Dream is constructed. Place your initialization here.
     *
     * Subclasses must call through to the superclass implementation.
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        mWindowManager.removeView(mWindow.getDecorView());
    public void onCreate() {
        if (DEBUG) Slog.v(TAG, "onCreate() on thread " + Thread.currentThread().getId());
        super.onCreate();
        loadSandman();
    }

    /**
     * Called when this Dream is started.
     */
    public void onStart() {
        // hook for subclasses
        Slog.v(TAG, "called Dream.onStart()");
    }

    private void loadSandman() {
        mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams"));
    }

    /**
@@ -335,16 +353,21 @@ public class Dream extends Service implements Window.Callback {
     * @param windowToken Binder to attach to the window to allow access to the correct window type.
     * @hide
     */
    final /*package*/ void attach(IBinder windowToken) {
        if (DEBUG) Slog.v(TAG, "Dream attached on thread " + Thread.currentThread().getId());
    private final void attach(IBinder windowToken) {
        if (DEBUG) Slog.v(TAG, "Attached on thread " + Thread.currentThread().getId());

        if (mSandman == null) {
            Slog.w(TAG, "No dream manager found, super.onCreate may not have been called");
            loadSandman();
        }
        mWindowToken = windowToken;
        mWindow = PolicyManager.makeNewWindow(this);
        mWindow.setCallback(this);
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));

        if (DEBUG) Slog.v(TAG, "attaching window token: " + windowToken 
                + " to window of type " + WindowManager.LayoutParams.TYPE_DREAM);
        if (DEBUG) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s",
                windowToken, WindowManager.LayoutParams.TYPE_DREAM));

        WindowManager.LayoutParams lp = mWindow.getAttributes();
        lp.type = WindowManager.LayoutParams.TYPE_DREAM;
@@ -356,57 +379,104 @@ public class Dream extends Service implements Window.Callback {
                    );
        mWindow.setAttributes(lp);

        //WindowManagerImpl.getDefault().addView(mWindow.getDecorView(), lp);
        
        if (DEBUG) Slog.v(TAG, "created and attached window: " + mWindow);
        if (DEBUG) Slog.v(TAG, "Created and attached window: " + mWindow);

        mWindow.setWindowManager(null, windowToken, "dream", true);
        mWindowManager = mWindow.getWindowManager();

        // now make it visible
        // now make it visible (on the ui thread)
        mHandler.post(new Runnable(){
            @Override
            public void run() {
                if (DEBUG) Slog.v(TAG, "Dream window added on thread " + Thread.currentThread().getId());
                if (DEBUG) Slog.v(TAG, "Window added on thread " + Thread.currentThread().getId());

                try {
                    getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
                } catch (Throwable t) {
                    Slog.w("Crashed adding window view", t);
                    safelyFinish();
                    return;
                }

                // start it up
                try {
                    onStart();
                } catch (Throwable t) {
                    Slog.w("Crashed in onStart()", t);
                    safelyFinish();
                }
            }});
    }

    private void safelyFinish() {
        if (DEBUG) Slog.v(TAG, "safelyFinish()");
        try {
            finish();
        } catch (Throwable t) {
            Slog.w(TAG, "Crashed in safelyFinish()", t);
            finishInternal();
            return;
        }

        if (!mFinished) {
            Slog.w(TAG, "Bad dream, did not call super.finish()");
            finishInternal();
        }
    }

    /**
     * Stop the dream and wake up.
     * Stops the dream, detaches from the window, and wakes up.
     *
     * After this method is called, the service will be stopped.
     * Subclasses must call through to the superclass implementation.
     *
     * <p>After this method is called, the service will be stopped.</p>
     */
    public void finish() {
        if (DEBUG) Slog.v(TAG, "finish()");
        finishInternal();
    }

    private void finishInternal() {
        if (DEBUG) Slog.v(TAG, "finishInternal() mFinished = " + mFinished);
        if (mFinished) return;
        try {
            mSandman.awaken(); // assuming we were started by the DreamManager
            stopSelf(); // if launched via any other means
            mFinished = true;
        } catch (RemoteException ex) {
            // sigh
        }

            if (mSandman != null) {
                mSandman.awakenSelf(mWindowToken);
            } else {
                Slog.w(TAG, "No dream manager found");
            }
            stopSelf(); // if launched via any other means

    class IDreamServiceWrapper extends IDreamService.Stub {
        public IDreamServiceWrapper() {
        } catch (Throwable t) {
            Slog.w(TAG, "Crashed in finishInternal()", t);
        }
    }

        public void attach(IBinder windowToken) {
            Dream.this.attach(windowToken);
    @Override
    public void onDestroy() {
        if (DEBUG) Slog.v(TAG, "onDestroy()");
        super.onDestroy();

        if (DEBUG) Slog.v(TAG, "Removing window");
        try {
            mWindowManager.removeView(mWindow.getDecorView());
        } catch (Throwable t) {
            Slog.w(TAG, "Crashed removing window view", t);
        }
    }

    /**
     * Implement to return the implementation of the internal accessibility
     * service interface.  Subclasses should not override.
     */
    @Override
    public final IBinder onBind(Intent intent) {
        return new IDreamServiceWrapper();
        if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent);
        return new DreamServiceWrapper();
    }

    private class DreamServiceWrapper extends IDreamService.Stub {
        public void attach(IBinder windowToken) {
            Dream.this.attach(windowToken);
        }
    }

}
+0 −247
Original line number Diff line number Diff line
package android.service.dreams;

import static android.provider.Settings.Secure.SCREENSAVER_COMPONENTS;
import static android.provider.Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT;
import java.io.FileDescriptor;
import java.io.PrintWriter;

import android.app.ActivityManagerNative;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.view.IWindowManager;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;

/**
 *
 * @hide
 *
 */

public class DreamManagerService
        extends IDreamManager.Stub
        implements ServiceConnection
{
    private static final boolean DEBUG = true;
    private static final String TAG = "DreamManagerService";

    final Object mLock = new Object[0];

    private Context mContext;
    private IWindowManager mIWindowManager;

    private ComponentName mCurrentDreamComponent;
    private IDreamService mCurrentDream;
    private Binder mCurrentDreamToken;
    private int mCurrentUserId;

    public DreamManagerService(Context context) {
        if (DEBUG) Slog.v(TAG, "DreamManagerService startup");
        mContext = context;
        mIWindowManager = WindowManagerGlobal.getWindowManagerService();
    }

    private void checkPermission(String permission) {
        if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(permission)) {
            throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
                    + ", must have permission " + permission);
        }
    }

    // IDreamManager method
    @Override
    public void dream() {
        ComponentName[] dreams = getDreamComponentsForUser(mCurrentUserId);
        ComponentName name = dreams != null && dreams.length > 0 ? dreams[0] : null;
        if (name != null) {
            synchronized (mLock) {
                final long ident = Binder.clearCallingIdentity();
                try {
                    bindDreamComponentL(name, false);
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
        }
    }

    // IDreamManager method
    @Override
    public void setDreamComponents(ComponentName[] componentNames) {
        Settings.Secure.putStringForUser(mContext.getContentResolver(),
                SCREENSAVER_COMPONENTS,
                componentsToString(componentNames),
                UserHandle.getCallingUserId());
    }

    private static String componentsToString(ComponentName[] componentNames) {
        StringBuilder names = new StringBuilder();
        if (componentNames != null) {
            for (ComponentName componentName : componentNames) {
                if (names.length() > 0)
                    names.append(',');
                names.append(componentName.flattenToString());
            }
        }
        return names.toString();
    }

    private static ComponentName[] componentsFromString(String names) {
        String[] namesArray = names.split(",");
        ComponentName[] componentNames = new ComponentName[namesArray.length];
        for (int i = 0; i < namesArray.length; i++)
            componentNames[i] = ComponentName.unflattenFromString(namesArray[i]);
        return componentNames;
    }

    // IDreamManager method
    @Override
    public ComponentName[] getDreamComponents() {
        return getDreamComponentsForUser(UserHandle.getCallingUserId());
    }

    private ComponentName[] getDreamComponentsForUser(int userId) {
        String names = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                SCREENSAVER_COMPONENTS,
                userId);
        return names == null ? null : componentsFromString(names);
    }

    // IDreamManager method
    @Override
    public ComponentName getDefaultDreamComponent() {
        String name = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                SCREENSAVER_DEFAULT_COMPONENT,
                UserHandle.getCallingUserId());
        return name == null ? null : ComponentName.unflattenFromString(name);
    }

    // IDreamManager method
    @Override
    public void testDream(ComponentName name) {
        if (DEBUG) Slog.v(TAG, "startDream name=" + name
                + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
//        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
        synchronized (mLock) {
            final long ident = Binder.clearCallingIdentity();
            try {
                bindDreamComponentL(name, true);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }

    // IDreamManager method
    @Override
    public void awaken() {
        if (DEBUG) Slog.v(TAG, "awaken()");
        synchronized (mLock) {
            if (mCurrentDream != null) {
                if (DEBUG) Slog.v(TAG, "disconnecting: " +  mCurrentDreamComponent + " service: " + mCurrentDream);
                mContext.unbindService(this);
                mCurrentDream = null;
                mCurrentDreamToken = null;
            }
        }
    }

    // IDreamManager method
    @Override
    public boolean isDreaming() {
        synchronized (mLock) {
            return mCurrentDreamToken != null;
        }
    }

    public void bindDreamComponentL(ComponentName componentName, boolean test) {
        if (DEBUG) Slog.v(TAG, "bindDreamComponent: componentName=" + componentName
                + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());

        Intent intent = new Intent(Intent.ACTION_MAIN)
            .setComponent(componentName)
            .addFlags(
                Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                )
            .putExtra("android.dreams.TEST", test);

        mCurrentDreamComponent = componentName;
        mCurrentDreamToken = new Binder();
        try {
            if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurrentDreamToken
                    + " for window type: " + WindowManager.LayoutParams.TYPE_DREAM);
            mIWindowManager.addWindowToken(mCurrentDreamToken,
                    WindowManager.LayoutParams.TYPE_DREAM);
        } catch (RemoteException e) {
            Slog.w(TAG, "Unable to add window token. Proceed at your own risk.");
        }

        if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
            Slog.w(TAG, "unable to bind service: " + componentName);
        }
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        if (DEBUG) Slog.v(TAG, "connected to dream: " + name + " binder=" + service + " thread=" + Thread.currentThread().getId());

        mCurrentDream = IDreamService.Stub.asInterface(service);
        try {
            if (DEBUG) Slog.v(TAG, "attaching with token:" + mCurrentDreamToken);
            mCurrentDream.attach(mCurrentDreamToken);
        } catch (RemoteException ex) {
            Slog.w(TAG, "Unable to send window token to dream:" + ex);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        if (DEBUG) Slog.v(TAG, "disconnected: " + name + " service: " + mCurrentDream);
        // Only happens in exceptional circumstances
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);

        pw.println("Dreamland:");
        pw.print("  component="); pw.println(mCurrentDreamComponent);
        pw.print("  token="); pw.println(mCurrentDreamToken);
        pw.print("  dream="); pw.println(mCurrentDream);
    }

    public void systemReady() {

        // dream settings are kept per user, so keep track of current user
        try {
            mCurrentUserId = ActivityManagerNative.getDefault().getCurrentUser().id;
        } catch (RemoteException e) {
            Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
        }
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_USER_SWITCHED);
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                    mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                    if (DEBUG) Slog.v(TAG, "userId " + mCurrentUserId + " is in the house");
                }
            }}, filter);

        if (DEBUG) Slog.v(TAG, "ready to dream!");
    }

}
+2 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.service.dreams;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.content.ComponentName;
import android.os.IBinder;

/** @hide */
interface IDreamManager {
@@ -29,4 +30,5 @@ interface IDreamManager {
    ComponentName getDefaultDreamComponent();
    void testDream(in ComponentName componentName);
    boolean isDreaming();
    void awakenSelf(in IBinder token);
}
 No newline at end of file
+15 −0
Original line number Diff line number Diff line
@@ -140,6 +140,9 @@

    <protected-broadcast android:name="android.os.UpdateLock.UPDATE_LOCK_CHANGED" />

    <protected-broadcast android:name="android.intent.action.DREAMING_STARTED" />
    <protected-broadcast android:name="android.intent.action.DREAMING_STOPPED" />

    <!-- ====================================== -->
    <!-- Permissions for things that cost money -->
    <!-- ====================================== -->
@@ -1929,6 +1932,18 @@
        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
        android:protectionLevel="signature|system" />

    <!-- Allows applications to read dream settings and dream state.
         @hide -->
    <permission android:name="android.permission.READ_DREAM_STATE"
        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
        android:protectionLevel="signature" />

    <!-- Allows applications to write dream settings, and start or stop dreaming.
         @hide -->
    <permission android:name="android.permission.WRITE_DREAM_STATE"
        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
        android:protectionLevel="signature" />

    <!-- Allow an application to read and write the cache partition.
         @hide -->
    <permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM"
Loading