package com.ensoftcorp.open.android.essentials.ui.views; import java.io.IOException; import org.eclipse.debug.internal.ui.DebugPluginImages; import org.eclipse.debug.internal.ui.IInternalDebugUIConstants; import org.eclipse.jface.action.Action; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.ViewPart; import com.ensoftcorp.atlas.core.db.graph.Edge; import com.ensoftcorp.atlas.core.db.graph.GraphElement; import com.ensoftcorp.atlas.core.db.graph.Node; import com.ensoftcorp.atlas.core.query.Q; import com.ensoftcorp.atlas.core.xcsg.XCSG; import com.ensoftcorp.atlas.java.core.script.Common; import com.ensoftcorp.open.android.essentials.permissions.Permission; import com.ensoftcorp.open.android.essentials.permissions.PermissionGroup; import com.ensoftcorp.open.android.essentials.permissions.ProtectionLevel; import com.ensoftcorp.open.commons.analysis.StandardQueries; import com.ensoftcorp.open.commons.utilities.DisplayUtils; import com.ensoftcorp.open.commons.utilities.FormattedSourceCorrespondence; /** * An Eclipse view for searching and viewing apply permission mapping values in the Atlas index * @author Ben Holland */ @SuppressWarnings("restriction") public class PermissionUsageView extends ViewPart { public PermissionUsageView() {} private final int PERMISSION_USAGE_COLOR = SWT.COLOR_RED; /** * The ID of the view as specified by the extension. */ public static final String ID = "com.ensoftcorp.open.android.essentials.ui.views.PermissionUsageView"; private boolean usageFilterEnabled = false; private boolean expandTreeEnabled = true; @Override public void createPartControl(final Composite parent) { parent.setLayout(new GridLayout(1, false)); final Composite searchBarComposite = new Composite(parent, SWT.NONE); searchBarComposite.setLayout(new GridLayout(2, false)); searchBarComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); final Button searchBarEnabledCheckbox = new Button(searchBarComposite, SWT.CHECK); searchBarEnabledCheckbox.setText("Filter by Permission: "); final Combo searchBar = new Combo(searchBarComposite, SWT.NONE); searchBar.setEnabled(false); searchBar.setToolTipText("Search for a permission by typing part of the name and pressing return or selecting an autocomplete suggestion."); searchBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); final SashForm sashForm = new SashForm(parent, SWT.NONE); sashForm.setToolTipText("Click and drag to resize."); sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); final Composite treeViewComposite = new Composite(sashForm, SWT.NONE); treeViewComposite.setLayout(new GridLayout(1, false)); final Tree tree = new Tree(treeViewComposite, SWT.BORDER); tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); final Composite detailsComposite = new Composite(sashForm, SWT.NONE); detailsComposite.setLayout(new GridLayout(1, false)); final StyledText detailsText = new StyledText(detailsComposite, SWT.BORDER | SWT.WRAP); detailsText.setEditable(false); detailsText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); sashForm.setWeights(new int[] { 1, 1 }); searchBar.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if(searchBarEnabledCheckbox.getSelection()){ repopulatePermissionTreeWithSearchResults(tree, detailsText, searchBar); } } }); searchBarEnabledCheckbox.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if(searchBarEnabledCheckbox.getSelection()){ searchBar.setEnabled(true); repopulatePermissionTreeWithSearchResults(tree, detailsText, searchBar); } else { searchBar.setText(""); searchBar.setEnabled(false); repopulatePermissionsTree(tree, detailsText); } } }); searchBar.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent key) { if(key.character == '\r'){ if(searchBarEnabledCheckbox.getSelection()){ repopulatePermissionTreeWithSearchResults(tree, detailsText, searchBar); } else { repopulatePermissionsTree(tree, detailsText); } } else if(Character.isLetter(key.character)){ searchBar.setListVisible(false); // hide the list we are going to modify the values String searchText = searchBar.getText(); // remove all items // note: doing this the hard way because removeAll method also clears the text for(String item : searchBar.getItems()){ searchBar.remove(item); } // add the autocomplete suggestions for each matching permission for(Permission permission : Permission.getAllPermissions()){ if(permission.getQualifiedName().toLowerCase().contains(searchText.toLowerCase())){ searchBar.add(permission.getQualifiedName()); } } // for some reason the previous actions are clearing the search text on some OS's so restoring it now searchBar.setText(searchText); // make sure the cursor selection is at the end searchBar.setSelection(new Point(searchText.length(), searchText.length())); } // refresh the permission tree repopulatePermissionTreeWithSearchResults(tree, detailsText, searchBar); } }); // show Atlas graph on tree node double click event tree.addListener(SWT.MouseDoubleClick, new Listener() { public void handleEvent(Event event) { Object data = tree.getSelection()[0].getData(); if (data instanceof GraphElement) { Edge callEdge = (Edge) data; try { detailsText.setText(prettyPrintGraphElement(callEdge)); } catch (IOException e) { // unknown data, just clear out the text display detailsText.setText(""); } DisplayUtils.show(callEdge, callEdge.from().getAttr(XCSG.name) + " call to " + callEdge.to().getAttr(XCSG.name)); } } }); // update the details text when a tree item is clicked tree.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { Object data = tree.getSelection()[0].getData(); if (data instanceof ProtectionLevel) { ProtectionLevel protectionLevel = (ProtectionLevel) data; String displayText = "Protection Level Name: " + protectionLevel.getName() + "\n\nDescription: " + protectionLevel.getDescription() + "\n\nProtection Level: " + protectionLevel.getLevel(); detailsText.setText(displayText); } else if (data instanceof PermissionGroup) { PermissionGroup permissionGroup = (PermissionGroup) data; String displayText = "Permission Group Name: " + permissionGroup.getSimpleName() + "\n\nQualified Permission Group Name: " + permissionGroup.getQualifiedName() + "\n\nDescription: " + permissionGroup.getDescription() + "\n\nIntroduced in API version: " + permissionGroup.addedInAPILevel(); detailsText.setText(displayText); } else if (data instanceof Permission) { Permission permission = (Permission) data; String displayText = "Permission Name: " + permission.getSimpleName() + "\n\nQualified Permission Name: " + permission.getQualifiedName() + "\n\nDescription: " + permission.getDescription() + "\n\nIntroduced in API version: " + (permission.addedInAPILevel() == -1 ? "Unknown" : permission.addedInAPILevel()) + "\n\nIs Deprecated: " + permission.isDeprecated(); detailsText.setText(displayText); } else if (data instanceof GraphElement) { GraphElement graphElement = (GraphElement) data; try { detailsText.setText(prettyPrintGraphElement(graphElement)); } catch (IOException e) { // unknown data, just clear out the text display detailsText.setText(""); } } else { // unknown data, just clear out the text display detailsText.setText(""); } } }); // add a refresh button to rebuild the tree final Action refreshAction = new Action() { public void run() { if(searchBarEnabledCheckbox.getSelection()){ repopulatePermissionTreeWithSearchResults(tree, detailsText, searchBar); } else { repopulatePermissionsTree(tree, detailsText); } } }; refreshAction.setText("Refresh"); refreshAction.setToolTipText("Refresh"); refreshAction.setImageDescriptor(DebugPluginImages.getImageDescriptor(IInternalDebugUIConstants.IMG_ELCL_TERMINATE_AND_RELAUNCH)); refreshAction.setDisabledImageDescriptor(DebugPluginImages.getImageDescriptor(IInternalDebugUIConstants.IMG_DLCL_TERMINATE_AND_RELAUNCH)); refreshAction.setHoverImageDescriptor(DebugPluginImages.getImageDescriptor(IInternalDebugUIConstants.IMG_ELCL_TERMINATE_AND_RELAUNCH)); getViewSite().getActionBars().getToolBarManager().add(refreshAction); // add expand/collapse tree action final Action expandTreeToggleAction = new Action() { public void run() { if (expandTreeEnabled) { this.setText("Expand by usage"); this.setToolTipText("Expand by usage"); this.setImageDescriptor(DebugPluginImages.getImageDescriptor(IInternalDebugUIConstants.IMG_ELCL_SHOW_LOGICAL_STRUCTURE)); } else { this.setText("Collapse tree"); this.setToolTipText("Collapse tree"); this.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_COLLAPSEALL)); } expandTreeEnabled = !expandTreeEnabled; if(searchBarEnabledCheckbox.getSelection()){ repopulatePermissionTreeWithSearchResults(tree, detailsText, searchBar); } else { repopulatePermissionsTree(tree, detailsText); } } }; expandTreeToggleAction.setText("Collapse tree"); expandTreeToggleAction.setToolTipText("Collapse tree"); expandTreeToggleAction.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_COLLAPSEALL)); getViewSite().getActionBars().getToolBarManager().add(expandTreeToggleAction); // add active usage filter action final Action usageFilterToggleAction = new Action() { public void run() { if (usageFilterEnabled) { this.setText("Filter by usage"); this.setToolTipText("Filter by usage"); this.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_SYNCED_DISABLED)); } else { this.setText("Stop filtering"); this.setToolTipText("Stop filtering"); this.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_SYNCED)); } usageFilterEnabled = !usageFilterEnabled; if(searchBarEnabledCheckbox.getSelection()){ repopulatePermissionTreeWithSearchResults(tree, detailsText, searchBar); } else { repopulatePermissionsTree(tree, detailsText); } } }; usageFilterToggleAction.setText("Filter by usage"); usageFilterToggleAction.setToolTipText("Filter by usage"); usageFilterToggleAction.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_SYNCED_DISABLED)); getViewSite().getActionBars().getToolBarManager().add(usageFilterToggleAction); // populate the tree for the first time populatePermissionsTree(tree); } /** * Helper method for repopulating the permission group and protection level subtrees without the search results * @param tree * @param detailsText * @param searchBar */ private void repopulatePermissionTreeWithSearchResults(final Tree tree, final StyledText detailsText, final Combo searchBar){ detailsText.setText(""); // clear out the details view tree.removeAll(); // clear the tree contents for(Permission permission : Permission.getAllPermissions()){ // case insensitive search if(permission.getQualifiedName().toLowerCase().contains(searchBar.getText().toLowerCase())){ TreeItem permissionItem = new TreeItem(tree, SWT.NONE); permissionItem.setText(permission.getQualifiedName()); permissionItem.setData(permission); Q methods = Common.universe().nodesTaggedWithAny(permission.getQualifiedName()).retainNodes(); for (Node method : methods.eval().nodes()) { String qualifiedMethodName = StandardQueries.getQualifiedFunctionName(method); TreeItem methodItem = new TreeItem(permissionItem, SWT.NONE); methodItem.setText(qualifiedMethodName); methodItem.setData(method); // add call sites of the permission method Q methodQ = Common.toQ(Common.toGraph(method)); Q callEdges = Common.universe().edgesTaggedWithAny(XCSG.Call).retainEdges(); for (Edge call : callEdges.reverseStep(methodQ).eval().edges()) { String qualifiedCallerName = StandardQueries.getQualifiedFunctionName(call.from()); TreeItem item = new TreeItem(methodItem, SWT.NONE); item.setText(qualifiedCallerName); item.setData(call); permissionItem.setForeground(permissionItem.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); if(expandTreeEnabled) permissionItem.setExpanded(true); methodItem.setForeground(methodItem.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); if(expandTreeEnabled) methodItem.setExpanded(true); item.setForeground(item.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); } } } } if(usageFilterEnabled){ // removes unused items for (TreeItem subtree : tree.getItems()) { pruneUnusedElements(subtree); } // color tree back to black for (TreeItem subtree : tree.getItems()) { colorTree(subtree, SWT.COLOR_BLACK); } } tree.update(); // force an update, sometimes the tree needs a hint... } /** * Helper method for repopulating the permission group and protection level subtrees * @param tree * @param detailsText */ private void repopulatePermissionsTree(final Tree tree, final StyledText detailsText) { detailsText.setText(""); // clear out the details view tree.removeAll(); // clear the tree contents populatePermissionsTree(tree); } /** * Helper method for populating the permission group and protection level subtrees * @param tree */ private void populatePermissionsTree(final Tree tree) { // populate the protection level item sub tree TreeItem protectionLevelRootItem = new TreeItem(tree, SWT.NONE); protectionLevelRootItem.setText("Protection Level"); protectionLevelRootItem.setData(null); // populate the permission group item sub tree TreeItem permissionGroupRootItem = new TreeItem(tree, SWT.NONE); permissionGroupRootItem.setText("Permission Group"); permissionGroupRootItem.setData(null); for (ProtectionLevel protectionLevel : ProtectionLevel.getAllProtectionLevels()) { TreeItem protectionLevelItem = new TreeItem(protectionLevelRootItem, SWT.NONE); protectionLevelItem.setText(protectionLevel.getName()); protectionLevelItem.setData(protectionLevel); // populate the permission for each protection level for (Permission permission : protectionLevel.getPermissions()) { TreeItem permissionItem = new TreeItem(protectionLevelItem, SWT.NONE); permissionItem.setText(permission.getQualifiedName()); permissionItem.setData(permission); // populate the permission methods for each protection level boolean hasCallers = populatePermissionMethodsSubtree(permission, permissionItem); if (hasCallers) { permissionItem.setForeground(permissionItem.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); if(expandTreeEnabled) permissionItem.setExpanded(true); protectionLevelItem.setForeground(protectionLevelItem.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); if(expandTreeEnabled) protectionLevelItem.setExpanded(true); protectionLevelRootItem.setForeground(protectionLevelRootItem.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); if(expandTreeEnabled) protectionLevelRootItem.setExpanded(true); } } } for (PermissionGroup permissionGroup : PermissionGroup.getAllPermissionGroups()) { TreeItem permissionGroupItem = new TreeItem(permissionGroupRootItem, SWT.NONE); permissionGroupItem.setText(permissionGroup.getQualifiedName()); permissionGroupItem.setData(permissionGroup); // populate the permission for each protection level for (Permission permission : permissionGroup.getPermissions()) { TreeItem permissionItem = new TreeItem(permissionGroupItem, SWT.NONE); permissionItem.setText(permission.getQualifiedName()); permissionItem.setData(permission); // populate the permission methods for each protection level boolean hasCallers = populatePermissionMethodsSubtree(permission, permissionItem); if (hasCallers) { permissionItem.setForeground(permissionItem.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); if(expandTreeEnabled) permissionItem.setExpanded(true); permissionGroupItem.setForeground(permissionGroupItem.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); if(expandTreeEnabled) permissionGroupItem.setExpanded(true); permissionGroupRootItem.setForeground(permissionGroupRootItem.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); if(expandTreeEnabled) permissionGroupRootItem.setExpanded(true); } } } if(usageFilterEnabled){ // removes unused items for (TreeItem subtree : tree.getItems()) { pruneUnusedElements(subtree); } // color tree back to black for (TreeItem subtree : tree.getItems()) { colorTree(subtree, SWT.COLOR_BLACK); } } tree.update(); // force an update, sometimes the tree needs a hint... } private String prettyPrintGraphElement(GraphElement ge) throws IOException{ String tags = ge.tags().toString(); FormattedSourceCorrespondence sc = FormattedSourceCorrespondence.getSourceCorrespondent(ge); if(sc != null){ String lines = sc.getLineNumbers(); return "File: " + sc.getFile() + "\nLine number" + ((lines.contains("-") ? "s: " : ": ") + lines) + "\nTags: " + tags; } return ge.toString(); } /** * Helper method to prune the tree for nodes not colored PERMISSION_USAGE_COLOR * @param tree */ private void pruneUnusedElements(TreeItem tree){ if(tree.getForeground().equals(tree.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR))){ // tree has used elements for (TreeItem subtree : tree.getItems()) { pruneUnusedElements(subtree); } } else { // tree does not have used elements tree.removeAll(); tree.dispose(); } } /** * Helper method to color a tree or subtree the given color * @param tree * @param color */ private void colorTree(TreeItem tree, int color){ tree.setForeground(tree.getDisplay().getSystemColor(color)); int itemCount = tree.getItemCount(); for (int i=0; i<itemCount; i++) { TreeItem subtree = tree.getItem(i); colorTree(subtree, color); } } /** * Helper method for populating the permission restricted methods under a permission * @param permission * @param permissionItem * @return returns true if the permission methods are called */ private boolean populatePermissionMethodsSubtree(Permission permission, TreeItem permissionItem) { boolean hasCallsites = false; Q methods = Common.universe().nodesTaggedWithAny(permission.getQualifiedName()).retainNodes(); for (Node method : methods.eval().nodes()) { String qualifiedMethodName = StandardQueries.getQualifiedFunctionName(method); TreeItem methodItem = new TreeItem(permissionItem, SWT.NONE); methodItem.setText(qualifiedMethodName); methodItem.setData(method); // add call sites of the permission method Q methodQ = Common.toQ(Common.toGraph(method)); Q call = Common.universe().edgesTaggedWithAny(XCSG.Call).retainEdges(); for (Edge callEdge : call.reverseStep(methodQ).eval().edges()) { hasCallsites = true; methodItem.setForeground(methodItem.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); if(expandTreeEnabled) methodItem.setExpanded(true); String qualifiedCallerName = StandardQueries.getQualifiedFunctionName(callEdge.from()); TreeItem item = new TreeItem(methodItem, SWT.NONE); item.setText(qualifiedCallerName); item.setData(callEdge); item.setForeground(methodItem.getDisplay().getSystemColor(PERMISSION_USAGE_COLOR)); } } return hasCallsites; } @Override public void setFocus() {} }